29 #define G_LOG_DOMAIN "Dialogs.DRun" 40 #include <sys/types.h> 58 #define DRUN_CACHE_FILE "rofi3.druncache" 60 char *DRUN_GROUP_NAME =
"Desktop Entry";
62 typedef struct _DRunModePrivateData DRunModePrivateData;
70 DRunModePrivateData *pd;
88 cairo_surface_t *
icon;
104 uint32_t icon_fetch_uid;
109 const char *entry_field_name;
115 DRUN_MATCH_FIELD_NAME,
116 DRUN_MATCH_FIELD_GENERIC,
117 DRUN_MATCH_FIELD_EXEC,
118 DRUN_MATCH_FIELD_CATEGORIES,
119 DRUN_MATCH_FIELD_COMMENT,
120 DRUN_MATCH_NUM_FIELDS,
121 } DRunMatchingFields;
123 static DRunEntryField matching_entry_fields[DRUN_MATCH_NUM_FIELDS] = {
124 { .entry_field_name =
"name", .enabled = TRUE, },
125 { .entry_field_name =
"generic", .enabled = TRUE, },
126 { .entry_field_name =
"exec", .enabled = TRUE, },
127 { .entry_field_name =
"categories", .enabled = TRUE, },
128 { .entry_field_name =
"comment", .enabled = FALSE, }
131 struct _DRunModePrivateData
133 DRunModeEntry *entry_list;
134 unsigned int cmd_list_length;
135 unsigned int cmd_list_length_actual;
137 GHashTable *disabled_entries;
138 unsigned int disabled_entries_length;
139 unsigned int expected_line_height;
142 const gchar *icon_theme;
144 gchar **current_desktop_list;
153 static gboolean drun_helper_eval_cb (
const GMatchInfo *info, GString *res, gpointer data )
156 struct RegexEvalArg *e = (
struct RegexEvalArg *) data;
160 match = g_match_info_fetch ( info, 0 );
161 if ( match != NULL ) {
180 char *esc = g_shell_quote ( e->e->path );
181 g_string_append ( res, esc );
187 char *esc = g_shell_quote ( e->e->name );
188 g_string_append ( res, esc );
203 static void exec_cmd_entry ( DRunModeEntry *e )
205 GError *error = NULL;
206 GRegex *reg = g_regex_new (
"%[a-zA-Z]", 0, 0, &error );
207 if ( error != NULL ) {
208 g_warning (
"Internal error, failed to create regex: %s.", error->message );
209 g_error_free ( error );
212 struct RegexEvalArg earg = { .e = e, .success = TRUE };
213 char *str = g_regex_replace_eval ( reg, e->exec, -1, 0, 0, drun_helper_eval_cb, &earg, &error );
214 if ( error != NULL ) {
215 g_warning (
"Internal error, failed replace field codes: %s.", error->message );
216 g_error_free ( error );
219 g_regex_unref ( reg );
220 if ( earg.success == FALSE ) {
221 g_warning (
"Invalid field code in Exec line: %s.", e->exec );;
225 g_warning (
"Nothing to execute after processing: %s.", e->exec );;
229 const gchar *fp = g_strstrip ( str );
230 gchar *exec_path = g_key_file_get_string ( e->key_file, e->action,
"Path", NULL );
231 if ( exec_path != NULL && strlen ( exec_path ) == 0 ) {
233 g_free ( exec_path );
239 .icon = e->icon_name,
242 gboolean sn = g_key_file_get_boolean ( e->key_file, e->action,
"StartupNotify", NULL );
243 gchar *wmclass = NULL;
244 if ( sn && g_key_file_has_key ( e->key_file, e->action,
"StartupWMClass", NULL ) ) {
245 context.
wmclass = wmclass = g_key_file_get_string ( e->key_file, e->action,
"StartupWMClass", NULL );
249 gboolean terminal = g_key_file_get_boolean ( e->key_file, e->action,
"Terminal", NULL );
251 char *path = g_build_filename (
cache_dir, DRUN_CACHE_FILE, NULL );
257 g_free ( exec_path );
263 static gboolean read_desktop_file ( DRunModePrivateData *pd,
const char *root,
const char *path,
const gchar *basename,
char *action )
268 const ssize_t id_len = strlen ( path ) - strlen ( root );
270 g_strlcpy (
id, &( path[strlen ( root ) + 1] ), id_len );
271 for (
int index = 0; index < id_len; index++ ) {
272 if (
id[index] ==
'/' ) {
278 if ( g_hash_table_contains ( pd->disabled_entries,
id ) && !parse_action ) {
279 g_debug (
"[%s] [%s] Skipping, was previously seen.",
id, path );
282 GKeyFile *kf = g_key_file_new ();
283 GError *error = NULL;
284 gboolean res = g_key_file_load_from_file ( kf, path, 0, &error );
287 g_debug (
"[%s] [%s] Failed to parse desktop file because: %s.",
id, path, error->message );
288 g_error_free ( error );
289 g_key_file_free ( kf );
293 if ( g_key_file_has_group ( kf, action ) == FALSE ) {
295 g_debug (
"[%s] [%s] Invalid desktop file: No %s group",
id, path, action );
296 g_key_file_free ( kf );
300 gchar *key = g_key_file_get_string ( kf, DRUN_GROUP_NAME,
"Type", NULL );
303 g_debug (
"[%s] [%s] Invalid desktop file: No type indicated",
id, path );
304 g_key_file_free ( kf );
307 if ( g_strcmp0 ( key,
"Application" ) ) {
308 g_debug (
"[%s] [%s] Skipping desktop file: Not of type application (%s)",
id, path, key );
310 g_key_file_free ( kf );
316 if ( !g_key_file_has_key ( kf, DRUN_GROUP_NAME,
"Name", NULL ) ) {
317 g_debug (
"[%s] [%s] Invalid desktop file: no 'Name' key present.",
id, path );
318 g_key_file_free ( kf );
323 if ( g_key_file_get_boolean ( kf, DRUN_GROUP_NAME,
"Hidden", NULL ) ) {
324 g_debug (
"[%s] [%s] Adding desktop file to disabled list: 'Hidden' key is true",
id, path );
325 g_key_file_free ( kf );
326 g_hash_table_add ( pd->disabled_entries, g_strdup (
id ) );
329 if ( pd->current_desktop_list ) {
330 gboolean show = TRUE;
332 if ( g_key_file_has_key ( kf, DRUN_GROUP_NAME,
"OnlyShowIn", NULL ) ) {
335 gchar **list = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME,
"OnlyShowIn", &llength, NULL );
337 for ( gsize lcd = 0; !show && pd->current_desktop_list[lcd]; lcd++ ) {
338 for ( gsize lle = 0; !show && lle < llength; lle++ ) {
339 show = ( g_strcmp0 ( pd->current_desktop_list[lcd], list[lle] ) == 0 );
345 if ( show && g_key_file_has_key ( kf, DRUN_GROUP_NAME,
"NotShowIn", NULL ) ) {
347 gchar **list = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME,
"NotShowIn", &llength, NULL );
349 for ( gsize lcd = 0; show && pd->current_desktop_list[lcd]; lcd++ ) {
350 for ( gsize lle = 0; show && lle < llength; lle++ ) {
351 show = !( g_strcmp0 ( pd->current_desktop_list[lcd], list[lle] ) == 0 );
359 g_debug (
"[%s] [%s] Adding desktop file to disabled list: 'OnlyShowIn'/'NotShowIn' keys don't match current desktop",
id, path );
360 g_key_file_free ( kf );
361 g_hash_table_add ( pd->disabled_entries, g_strdup (
id ) );
366 if ( g_key_file_get_boolean ( kf, DRUN_GROUP_NAME,
"NoDisplay", NULL ) ) {
367 g_debug (
"[%s] [%s] Adding desktop file to disabled list: 'NoDisplay' key is true",
id, path );
368 g_key_file_free ( kf );
369 g_hash_table_add ( pd->disabled_entries, g_strdup (
id ) );
373 if ( !g_key_file_has_key ( kf, DRUN_GROUP_NAME,
"Exec", NULL ) ) {
374 g_debug (
"[%s] [%s] Unsupported desktop file: no 'Exec' key present.",
id, path );
375 g_key_file_free ( kf );
379 if ( g_key_file_has_key ( kf, DRUN_GROUP_NAME,
"TryExec", NULL ) ) {
380 char *te = g_key_file_get_string ( kf, DRUN_GROUP_NAME,
"TryExec", NULL );
381 if ( !g_path_is_absolute ( te ) ) {
382 char *fp = g_find_program_in_path ( te );
385 g_key_file_free ( kf );
391 if ( g_file_test ( te, G_FILE_TEST_IS_EXECUTABLE ) == FALSE ) {
393 g_key_file_free ( kf );
400 size_t nl = ( ( pd->cmd_list_length ) + 1 );
401 if ( nl >= pd->cmd_list_length_actual ) {
402 pd->cmd_list_length_actual += 256;
403 pd->entry_list = g_realloc ( pd->entry_list, pd->cmd_list_length_actual * sizeof ( *( pd->entry_list ) ) );
407 if ( G_UNLIKELY ( pd->cmd_list_length > INT_MAX ) ) {
409 pd->entry_list[pd->cmd_list_length].sort_index = INT_MIN;
412 pd->entry_list[pd->cmd_list_length].sort_index = -pd->cmd_list_length;
414 pd->entry_list[pd->cmd_list_length].icon_size = 0;
415 pd->entry_list[pd->cmd_list_length].icon_fetch_uid = 0;
416 pd->entry_list[pd->cmd_list_length].root = g_strdup ( root );
417 pd->entry_list[pd->cmd_list_length].path = g_strdup ( path );
418 pd->entry_list[pd->cmd_list_length].desktop_id = g_strdup (
id );
419 pd->entry_list[pd->cmd_list_length].app_id = g_strndup ( basename, strlen ( basename ) - strlen (
".desktop" ) );
420 gchar *n = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME,
"Name", NULL, NULL );
422 if ( action != DRUN_GROUP_NAME ) {
423 gchar *na = g_key_file_get_locale_string ( kf, action,
"Name", NULL, NULL );
424 gchar *l = g_strdup_printf (
"%s - %s", n, na );
428 pd->entry_list[pd->cmd_list_length].name = n;
429 pd->entry_list[pd->cmd_list_length].action = DRUN_GROUP_NAME;
430 gchar *gn = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME,
"GenericName", NULL, NULL );
431 pd->entry_list[pd->cmd_list_length].generic_name = gn;
432 if ( matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled ) {
433 pd->entry_list[pd->cmd_list_length].categories = g_key_file_get_locale_string_list ( kf, DRUN_GROUP_NAME,
"Categories", NULL, NULL, NULL );
436 pd->entry_list[pd->cmd_list_length].categories = NULL;
438 pd->entry_list[pd->cmd_list_length].exec = g_key_file_get_string ( kf, action,
"Exec", NULL );
440 if ( matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled ) {
441 pd->entry_list[pd->cmd_list_length].comment = g_key_file_get_locale_string ( kf,
442 DRUN_GROUP_NAME,
"Comment", NULL, NULL );
445 pd->entry_list[pd->cmd_list_length].comment = NULL;
448 pd->entry_list[pd->cmd_list_length].icon_name = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME,
"Icon", NULL, NULL );
451 pd->entry_list[pd->cmd_list_length].icon_name = NULL;
453 pd->entry_list[pd->cmd_list_length].icon = NULL;
456 pd->entry_list[pd->cmd_list_length].key_file = kf;
458 g_hash_table_add ( pd->disabled_entries, g_strdup (
id ) );
459 g_debug (
"[%s] Using file %s.",
id, path );
460 ( pd->cmd_list_length )++;
462 if ( !parse_action ) {
463 gsize actions_length = 0;
464 char **actions = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME,
"Actions", &actions_length, NULL );
465 for ( gsize iter = 0; iter < actions_length; iter++ ) {
466 char *new_action = g_strdup_printf (
"Desktop Action %s", actions[iter] );
467 if ( !read_desktop_file ( pd, root, path, basename, new_action ) ) {
468 g_free ( new_action );
471 g_strfreev ( actions );
479 static void walk_dir ( DRunModePrivateData *pd,
const char *root,
const char *dirname )
483 g_debug (
"Checking directory %s for desktop files.", dirname );
484 dir = opendir ( dirname );
490 gchar *filename = NULL;
492 while ( ( file = readdir ( dir ) ) != NULL ) {
493 if ( file->d_name[0] ==
'.' ) {
496 switch ( file->d_type )
502 filename = g_build_filename ( dirname, file->d_name, NULL );
510 if ( file->d_type == DT_LNK || file->d_type == DT_UNKNOWN ) {
511 file->d_type = DT_UNKNOWN;
512 if ( stat ( filename, &st ) == 0 ) {
513 if ( S_ISDIR ( st.st_mode ) ) {
514 file->d_type = DT_DIR;
516 else if ( S_ISREG ( st.st_mode ) ) {
517 file->d_type = DT_REG;
522 switch ( file->d_type )
526 if ( g_str_has_suffix ( file->d_name,
".desktop" ) ) {
527 read_desktop_file ( pd, root, filename, file->d_name, DRUN_GROUP_NAME );
531 walk_dir ( pd, root, filename );
545 static void delete_entry_history (
const DRunModeEntry *entry )
547 char *path = g_build_filename (
cache_dir, DRUN_CACHE_FILE, NULL );
552 static void get_apps_history ( DRunModePrivateData *pd )
554 TICK_N (
"Start drun history" );
555 unsigned int length = 0;
556 gchar *path = g_build_filename (
cache_dir, DRUN_CACHE_FILE, NULL );
558 for (
unsigned int index = 0; index < length; index++ ) {
559 for (
size_t i = 0; i < pd->cmd_list_length; i++ ) {
560 if ( g_strcmp0 ( pd->entry_list[i].desktop_id, retv[index] ) == 0 ) {
561 unsigned int sort_index = length - index;
562 if ( G_LIKELY ( sort_index < INT_MAX ) ) {
563 pd->entry_list[i].sort_index = sort_index;
567 pd->entry_list[i].sort_index = INT_MAX;
574 TICK_N (
"Stop drun history" );
577 static gint drun_int_sort_list ( gconstpointer a, gconstpointer b, G_GNUC_UNUSED gpointer user_data )
579 DRunModeEntry *da = (DRunModeEntry *) a;
580 DRunModeEntry *db = (DRunModeEntry *) b;
582 return db->sort_index - da->sort_index;
585 static void get_apps ( DRunModePrivateData *pd )
587 TICK_N (
"Get Desktop apps (start)" );
591 dir = g_build_filename ( g_get_user_data_dir (),
"applications", NULL );
592 walk_dir ( pd, dir, dir );
594 TICK_N (
"Get Desktop apps (user dir)" );
596 const gchar *
const * sys = g_get_system_data_dirs ();
597 for (
const gchar *
const *iter = sys; *iter != NULL; ++iter ) {
598 gboolean unique = TRUE;
600 for (
const gchar *
const *iterd = sys; iterd != iter; ++iterd ) {
601 if ( g_strcmp0 ( *iter, *iterd ) == 0 ) {
606 if ( unique && ( **iter ) !=
'\0' ) {
607 dir = g_build_filename ( *iter,
"applications", NULL );
608 walk_dir ( pd, dir, dir );
612 TICK_N (
"Get Desktop apps (system dirs)" );
613 get_apps_history ( pd );
615 g_qsort_with_data ( pd->entry_list, pd->cmd_list_length, sizeof ( DRunModeEntry ), drun_int_sort_list, NULL );
617 TICK_N (
"Sorting done." );
620 static void drun_mode_parse_entry_fields ()
625 const char *
const sep =
",#";
627 for (
unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
628 matching_entry_fields[i].enabled = FALSE;
630 for (
char *token = strtok_r ( switcher_str, sep, &savept ); token != NULL;
631 token = strtok_r ( NULL, sep, &savept ) ) {
632 if ( strcmp ( token,
"all" ) == 0 ) {
633 for (
unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
634 matching_entry_fields[i].enabled = TRUE;
639 gboolean matched = FALSE;
640 for (
unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
641 const char * entry_name = matching_entry_fields[i].entry_field_name;
642 if ( g_ascii_strcasecmp ( token, entry_name ) == 0 ) {
643 matching_entry_fields[i].enabled = TRUE;
648 g_warning (
"Invalid entry name :%s", token );
653 g_free ( switcher_str );
656 static int drun_mode_init (
Mode *sw )
661 DRunModePrivateData *pd = g_malloc0 (
sizeof ( *pd ) );
662 pd->disabled_entries = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
665 const char *current_desktop = g_getenv (
"XDG_CURRENT_DESKTOP" );
666 pd->current_desktop_list = current_desktop ? g_strsplit ( current_desktop,
":", 0 ) : NULL;
668 drun_mode_parse_entry_fields ();
672 static void drun_entry_clear ( DRunModeEntry *e )
676 g_free ( e->app_id );
677 g_free ( e->desktop_id );
678 if ( e->icon != NULL ) {
679 cairo_surface_destroy ( e->icon );
681 g_free ( e->icon_name );
684 g_free ( e->generic_name );
685 g_free ( e->comment );
686 if ( e->action != DRUN_GROUP_NAME ) {
687 g_free ( e->action );
689 g_strfreev ( e->categories );
690 g_key_file_free ( e->key_file );
693 static ModeMode drun_mode_result (
Mode *sw,
int mretv,
char **input,
unsigned int selected_line )
709 else if ( ( mretv &
MENU_OK ) ) {
710 exec_cmd_entry ( &( rmpd->entry_list[selected_line] ) );
712 else if ( ( mretv &
MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] !=
'\0' ) {
717 else if ( ( mretv &
MENU_ENTRY_DELETE ) && selected_line < rmpd->cmd_list_length ) {
719 if ( rmpd->entry_list[selected_line].sort_index >= 0 ) {
720 delete_entry_history ( &( rmpd->entry_list[selected_line] ) );
721 drun_entry_clear ( &( rmpd->entry_list[selected_line] ) );
722 memmove ( &( rmpd->entry_list[selected_line] ), &rmpd->entry_list[selected_line + 1],
723 sizeof ( DRunModeEntry ) * ( rmpd->cmd_list_length - selected_line - 1 ) );
724 rmpd->cmd_list_length--;
730 static void drun_mode_destroy (
Mode *sw )
733 if ( rmpd != NULL ) {
734 for (
size_t i = 0; i < rmpd->cmd_list_length; i++ ) {
735 drun_entry_clear ( &( rmpd->entry_list[i] ) );
737 g_hash_table_destroy ( rmpd->disabled_entries );
738 g_free ( rmpd->entry_list );
740 g_strfreev ( rmpd->current_desktop_list );
746 static char *
_get_display_value (
const Mode *sw,
unsigned int selected_line,
int *state, G_GNUC_UNUSED GList **list,
int get_entry )
753 if ( pd->entry_list == NULL ) {
755 return g_strdup (
"Failed" );
758 DRunModeEntry *dr = &( pd->entry_list[selected_line] );
760 if ( dr->categories ){
761 cats = g_strjoinv(
",", dr->categories);
764 "{generic}", dr->generic_name,
766 "{comment}", dr->comment,
768 "{categories}", cats,
774 static cairo_surface_t *_get_icon (
const Mode *sw,
unsigned int selected_line,
int height )
777 g_return_val_if_fail ( pd->entry_list != NULL, NULL );
778 DRunModeEntry *dr = &( pd->entry_list[selected_line] );
779 if ( dr->icon_name == NULL ) {
782 if ( dr->icon_fetch_uid > 0 ) {
789 static char *drun_get_completion (
const Mode *sw,
unsigned int index )
793 DRunModeEntry *dr = &( pd->entry_list[index] );
794 if ( dr->generic_name == NULL ) {
795 return g_strdup ( dr->name );
798 return g_strdup_printf (
"%s", dr->name );
807 for (
int j = 0; match && tokens != NULL && tokens[j] != NULL; j++ ) {
811 if ( matching_entry_fields[DRUN_MATCH_FIELD_NAME].enabled ) {
812 if ( rmpd->entry_list[index].name ) {
816 if ( matching_entry_fields[DRUN_MATCH_FIELD_GENERIC].enabled ) {
818 if ( test == tokens[j]->invert && rmpd->entry_list[index].generic_name ) {
822 if ( matching_entry_fields[DRUN_MATCH_FIELD_EXEC].enabled ) {
824 if ( test == tokens[j]->invert ) {
828 if ( matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled ) {
830 if ( test == tokens[j]->invert ) {
831 gchar **list = rmpd->entry_list[index].categories;
832 for (
int iter = 0; test == tokens[j]->
invert && list && list[iter]; iter++ ) {
837 if ( matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled ) {
839 if ( test == tokens[j]->invert && rmpd->entry_list[index].comment ) {
852 static unsigned int drun_mode_get_num_entries (
const Mode *sw )
855 return pd->cmd_list_length;
861 .cfg_name_key =
"display-drun",
862 ._init = drun_mode_init,
863 ._get_num_entries = drun_mode_get_num_entries,
864 ._result = drun_mode_result,
865 ._destroy = drun_mode_destroy,
866 ._token_match = drun_token_match,
867 ._get_completion = drun_get_completion,
869 ._get_icon = _get_icon,
870 ._preprocess_input = NULL,
871 .private_data = NULL,
875 #endif // ENABLE_DRUN
char * drun_display_format
void history_set(const char *filename, const char *entry)
char ** history_get_list(const char *filename, unsigned int *length)
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
cairo_surface_t * rofi_icon_fetcher_get(const uint32_t uid)
static void get_apps(KeysHelpModePrivateData *pd)
void * mode_get_private_data(const Mode *mode)
void history_remove(const char *filename, const char *entry)
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
uint32_t rofi_icon_fetcher_query(const char *name, const int size)
unsigned int drun_show_actions
void mode_set_private_data(Mode *mode, void *pd)
char * helper_string_replace_if_exists(char *string,...)
static char * _get_display_value(const Mode *sw, unsigned int selected_line, int *state, G_GNUC_UNUSED GList **list, int get_entry)