28 #define G_LOG_DOMAIN "Dialogs.DRun" 39 #include <sys/types.h> 53 #include "nkutils-xdg-theme.h" 56 #define DRUN_CACHE_FILE "rofi3.druncache" 58 #define DRUN_GROUP_NAME "Desktop Entry" 81 cairo_surface_t *icon;
100 const char *entry_field_name;
106 DRUN_MATCH_FIELD_NAME,
107 DRUN_MATCH_FIELD_GENERIC,
108 DRUN_MATCH_FIELD_EXEC,
109 DRUN_MATCH_FIELD_CATEGORIES,
110 DRUN_MATCH_FIELD_COMMENT,
111 DRUN_MATCH_NUM_FIELDS,
112 } DRunMatchingFields;
114 static DRunEntryField matching_entry_fields[DRUN_MATCH_NUM_FIELDS] = {
115 { .entry_field_name =
"name", .enabled = TRUE, },
116 { .entry_field_name =
"generic", .enabled = TRUE, },
117 { .entry_field_name =
"exec", .enabled = TRUE, },
118 { .entry_field_name =
"categories", .enabled = TRUE, },
119 { .entry_field_name =
"comment", .enabled = FALSE, }
124 NkXdgThemeContext *xdg_context;
125 DRunModeEntry *entry_list;
126 unsigned int cmd_list_length;
127 unsigned int cmd_list_length_actual;
129 GHashTable *disabled_entries;
130 unsigned int disabled_entries_length;
132 unsigned int expected_line_height;
133 DRunModeEntry quit_entry;
136 const gchar *icon_theme;
138 gchar **current_desktop_list;
139 } DRunModePrivateData;
147 static gboolean drun_helper_eval_cb (
const GMatchInfo *info, GString *res, gpointer data )
150 struct RegexEvalArg *e = (
struct RegexEvalArg *) data;
154 match = g_match_info_fetch ( info, 0 );
155 if ( match != NULL ) {
174 char *esc = g_shell_quote ( e->e->path );
175 g_string_append ( res, esc );
181 char *esc = g_shell_quote ( e->e->name );
182 g_string_append ( res, esc );
197 static void exec_cmd_entry ( DRunModeEntry *e )
199 GError *error = NULL;
200 GRegex *reg = g_regex_new (
"%[a-zA-Z]", 0, 0, &error );
201 if ( error != NULL ) {
202 g_warning (
"Internal error, failed to create regex: %s.", error->message );
203 g_error_free ( error );
206 struct RegexEvalArg earg = { .e = e, .success = TRUE };
207 char *str = g_regex_replace_eval ( reg, e->exec, -1, 0, 0, drun_helper_eval_cb, &earg, &error );
208 if ( error != NULL ) {
209 g_warning (
"Internal error, failed replace field codes: %s.", error->message );
210 g_error_free ( error );
213 g_regex_unref ( reg );
214 if ( earg.success == FALSE ) {
215 g_warning (
"Invalid field code in Exec line: %s.", e->exec );;
219 g_warning (
"Nothing to execute after processing: %s.", e->exec );;
223 const gchar *fp = g_strstrip ( str );
224 gchar *exec_path = g_key_file_get_string ( e->key_file, DRUN_GROUP_NAME,
"Path", NULL );
225 if ( exec_path != NULL && strlen ( exec_path ) == 0 ) {
227 g_free ( exec_path );
233 .icon = e->icon_name,
236 gboolean sn = g_key_file_get_boolean ( e->key_file, DRUN_GROUP_NAME,
"StartupNotify", NULL );
237 gchar *wmclass = NULL;
238 if ( sn && g_key_file_has_key ( e->key_file, DRUN_GROUP_NAME,
"StartupWMClass", NULL ) ) {
239 context.
wmclass = wmclass = g_key_file_get_string ( e->key_file, DRUN_GROUP_NAME,
"StartupWMClass", NULL );
243 gboolean terminal = g_key_file_get_boolean ( e->key_file, DRUN_GROUP_NAME,
"Terminal", NULL );
245 char *path = g_build_filename (
cache_dir, DRUN_CACHE_FILE, NULL );
251 g_free ( exec_path );
257 static gboolean read_desktop_file ( DRunModePrivateData *pd,
const char *root,
const char *path,
const gchar *basename )
261 const ssize_t id_len = strlen ( path ) - strlen ( root );
263 g_strlcpy (
id, &( path[strlen ( root ) + 1] ), id_len );
264 for (
int index = 0; index < id_len; index++ ) {
265 if (
id[index] ==
'/' ) {
271 if ( g_hash_table_contains ( pd->disabled_entries,
id ) ) {
272 g_debug (
"[%s] [%s] Skipping, was previously seen.",
id, path );
275 GKeyFile *kf = g_key_file_new ();
276 GError *error = NULL;
277 gboolean res = g_key_file_load_from_file ( kf, path, 0, &error );
280 g_debug (
"[%s] [%s] Failed to parse desktop file because: %s.",
id, path, error->message );
281 g_error_free ( error );
282 g_key_file_free ( kf );
286 gchar *key = g_key_file_get_string ( kf, DRUN_GROUP_NAME,
"Type", NULL );
289 g_debug (
"[%s] [%s] Invalid desktop file: No type indicated",
id, path );
290 g_key_file_free ( kf );
293 if ( g_strcmp0 ( key,
"Application" ) ) {
294 g_debug (
"[%s] [%s] Skipping desktop file: Not of type application (%s)",
id, path, key );
296 g_key_file_free ( kf );
302 if ( !g_key_file_has_key ( kf, DRUN_GROUP_NAME,
"Name", NULL ) ) {
303 g_debug (
"[%s] [%s] Invalid desktop file: no 'Name' key present.",
id, path );
304 g_key_file_free ( kf );
309 if ( g_key_file_get_boolean ( kf, DRUN_GROUP_NAME,
"Hidden", NULL ) ) {
310 g_debug (
"[%s] [%s] Adding desktop file to disabled list: 'Hidden' key is true",
id, path );
311 g_key_file_free ( kf );
312 g_hash_table_add ( pd->disabled_entries, g_strdup (
id ) );
315 if ( pd->current_desktop_list ) {
316 gboolean show = TRUE;
318 if ( g_key_file_has_key ( kf, DRUN_GROUP_NAME,
"OnlyShowIn", NULL ) ) {
321 gchar **list = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME,
"OnlyShowIn", &llength, NULL );
323 for ( gsize lcd = 0; !show && pd->current_desktop_list[lcd]; lcd++ ) {
324 for ( gsize lle = 0; !show && lle < llength; lle++ ) {
325 show = ( g_strcmp0 ( pd->current_desktop_list[lcd], list[lle] ) == 0 );
331 if ( show && g_key_file_has_key ( kf, DRUN_GROUP_NAME,
"NotShowIn", NULL ) ) {
333 gchar **list = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME,
"NotShowIn", &llength, NULL );
335 for ( gsize lcd = 0; show && pd->current_desktop_list[lcd]; lcd++ ) {
336 for ( gsize lle = 0; show && lle < llength; lle++ ) {
337 show = !( g_strcmp0 ( pd->current_desktop_list[lcd], list[lle] ) == 0 );
345 g_debug (
"[%s] [%s] Adding desktop file to disabled list: 'OnlyShowIn'/'NotShowIn' keys don't match current desktop",
id, path );
346 g_key_file_free ( kf );
347 g_hash_table_add ( pd->disabled_entries, g_strdup (
id ) );
352 if ( g_key_file_get_boolean ( kf, DRUN_GROUP_NAME,
"NoDisplay", NULL ) ) {
353 g_debug (
"[%s] [%s] Adding desktop file to disabled list: 'NoDisplay' key is true",
id, path );
354 g_key_file_free ( kf );
355 g_hash_table_add ( pd->disabled_entries, g_strdup (
id ) );
359 if ( !g_key_file_has_key ( kf, DRUN_GROUP_NAME,
"Exec", NULL ) ) {
360 g_debug (
"[%s] [%s] Unsupported desktop file: no 'Exec' key present.",
id, path );
361 g_key_file_free ( kf );
365 if ( g_key_file_has_key ( kf, DRUN_GROUP_NAME,
"TryExec", NULL ) ) {
366 char *te = g_key_file_get_string ( kf, DRUN_GROUP_NAME,
"TryExec", NULL );
367 if ( !g_path_is_absolute ( te ) ) {
368 char *fp = g_find_program_in_path ( te );
371 g_key_file_free ( kf );
377 if ( g_file_test ( te, G_FILE_TEST_IS_EXECUTABLE ) == FALSE ) {
379 g_key_file_free ( kf );
386 size_t nl = ( ( pd->cmd_list_length ) + 1 );
387 if ( nl >= pd->cmd_list_length_actual ) {
388 pd->cmd_list_length_actual += 256;
389 pd->entry_list = g_realloc ( pd->entry_list, pd->cmd_list_length_actual * sizeof ( *( pd->entry_list ) ) );
393 if ( G_UNLIKELY ( pd->cmd_list_length > INT_MAX ) ) {
395 pd->entry_list[pd->cmd_list_length].sort_index = INT_MIN;
398 pd->entry_list[pd->cmd_list_length].sort_index = -pd->cmd_list_length;
400 pd->entry_list[pd->cmd_list_length].icon_size = 0;
401 pd->entry_list[pd->cmd_list_length].root = g_strdup ( root );
402 pd->entry_list[pd->cmd_list_length].path = g_strdup ( path );
403 pd->entry_list[pd->cmd_list_length].desktop_id = g_strdup (
id );
404 pd->entry_list[pd->cmd_list_length].app_id = g_strndup ( basename, strlen ( basename ) - strlen (
".desktop" ) );
405 gchar *n = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME,
"Name", NULL, NULL );
406 pd->entry_list[pd->cmd_list_length].name = n;
407 gchar *gn = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME,
"GenericName", NULL, NULL );
408 pd->entry_list[pd->cmd_list_length].generic_name = gn;
409 if ( matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled ) {
410 pd->entry_list[pd->cmd_list_length].categories = g_key_file_get_locale_string_list ( kf, DRUN_GROUP_NAME,
"Categories", NULL, NULL, NULL );
413 pd->entry_list[pd->cmd_list_length].categories = NULL;
415 pd->entry_list[pd->cmd_list_length].exec = g_key_file_get_string ( kf, DRUN_GROUP_NAME,
"Exec", NULL );
417 if ( matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled ) {
418 pd->entry_list[pd->cmd_list_length].comment = g_key_file_get_locale_string ( kf,
419 DRUN_GROUP_NAME,
"Comment", NULL, NULL );
422 pd->entry_list[pd->cmd_list_length].comment = NULL;
425 pd->entry_list[pd->cmd_list_length].icon_name = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME,
"Icon", NULL, NULL );
428 pd->entry_list[pd->cmd_list_length].icon_name = NULL;
430 pd->entry_list[pd->cmd_list_length].icon = NULL;
433 pd->entry_list[pd->cmd_list_length].key_file = kf;
435 g_hash_table_add ( pd->disabled_entries, g_strdup (
id ) );
436 g_debug (
"[%s] Using file %s.",
id, path );
437 ( pd->cmd_list_length )++;
444 static void walk_dir ( DRunModePrivateData *pd,
const char *root,
const char *dirname )
448 g_debug (
"Checking directory %s for desktop files.", dirname );
449 dir = opendir ( dirname );
455 gchar *filename = NULL;
457 while ( ( file = readdir ( dir ) ) != NULL ) {
458 if ( file->d_name[0] ==
'.' ) {
461 switch ( file->d_type )
467 filename = g_build_filename ( dirname, file->d_name, NULL );
475 if ( file->d_type == DT_LNK || file->d_type == DT_UNKNOWN ) {
476 file->d_type = DT_UNKNOWN;
477 if ( stat ( filename, &st ) == 0 ) {
478 if ( S_ISDIR ( st.st_mode ) ) {
479 file->d_type = DT_DIR;
481 else if ( S_ISREG ( st.st_mode ) ) {
482 file->d_type = DT_REG;
487 switch ( file->d_type )
491 if ( g_str_has_suffix ( file->d_name,
".desktop" ) ) {
492 read_desktop_file ( pd, root, filename, file->d_name );
496 walk_dir ( pd, root, filename );
510 static void delete_entry_history (
const DRunModeEntry *entry )
512 char *path = g_build_filename (
cache_dir, DRUN_CACHE_FILE, NULL );
517 static void get_apps_history ( DRunModePrivateData *pd )
519 TICK_N (
"Start drun history" );
520 unsigned int length = 0;
521 gchar *path = g_build_filename (
cache_dir, DRUN_CACHE_FILE, NULL );
523 for (
unsigned int index = 0; index < length; index++ ) {
524 for (
size_t i = 0; i < pd->cmd_list_length; i++ ) {
525 if ( g_strcmp0 ( pd->entry_list[i].desktop_id, retv[index] ) == 0 ) {
526 unsigned int sort_index = length - index;
527 if ( G_LIKELY ( sort_index < INT_MAX ) ) {
528 pd->entry_list[i].sort_index = sort_index;
532 pd->entry_list[i].sort_index = INT_MAX;
539 TICK_N (
"Stop drun history" );
542 static gint drun_int_sort_list ( gconstpointer a, gconstpointer b, G_GNUC_UNUSED gpointer user_data )
544 DRunModeEntry *da = (DRunModeEntry *) a;
545 DRunModeEntry *db = (DRunModeEntry *) b;
547 return db->sort_index - da->sort_index;
550 static void get_apps ( DRunModePrivateData *pd )
552 TICK_N (
"Get Desktop apps (start)" );
556 dir = g_build_filename ( g_get_user_data_dir (),
"applications", NULL );
557 walk_dir ( pd, dir, dir );
559 TICK_N (
"Get Desktop apps (user dir)" );
561 const gchar *
const * sys = g_get_system_data_dirs ();
562 for (
const gchar *
const *iter = sys; *iter != NULL; ++iter ) {
563 gboolean unique = TRUE;
565 for (
const gchar *
const *iterd = sys; iterd != iter; ++iterd ) {
566 if ( g_strcmp0 ( *iter, *iterd ) == 0 ) {
571 if ( unique && ( **iter ) !=
'\0' ) {
572 dir = g_build_filename ( *iter,
"applications", NULL );
573 walk_dir ( pd, dir, dir );
577 TICK_N (
"Get Desktop apps (system dirs)" );
578 get_apps_history ( pd );
580 g_qsort_with_data ( pd->entry_list, pd->cmd_list_length, sizeof ( DRunModeEntry ), drun_int_sort_list, NULL );
582 TICK_N (
"Sorting done." );
585 static void drun_icon_fetch ( gpointer data, gpointer user_data )
587 g_debug (
"Starting up icon fetching thread." );
590 DRunModePrivateData *pd = (DRunModePrivateData *) user_data;
591 DRunModeEntry *dr = (DRunModeEntry *) data;
592 const gchar *themes[2] = {
597 if ( dr->icon_name == NULL ) {
600 const gchar *icon_path;
601 gchar *icon_path_ = NULL;
603 if ( g_path_is_absolute ( dr->icon_name ) ) {
604 icon_path = dr->icon_name;
607 icon_path = icon_path_ = nk_xdg_theme_get_icon ( pd->xdg_context, themes, NULL, dr->icon_name, dr->icon_size, 1, TRUE );
608 if ( icon_path_ == NULL ) {
609 g_debug (
"Failed to get Icon %s(%d): n/a", dr->icon_name, dr->icon_size );
613 g_debug (
"Found Icon %s(%d): %s", dr->icon_name, dr->icon_size, icon_path );
616 cairo_surface_t *icon_surf = NULL;
617 if ( g_str_has_suffix ( icon_path,
".png" ) ) {
618 icon_surf = cairo_image_surface_create_from_png ( icon_path );
620 else if ( g_str_has_suffix ( icon_path,
".svg" ) ) {
624 g_debug (
"Icon type not yet supported: %s", icon_path );
628 if ( cairo_surface_status ( icon_surf ) != CAIRO_STATUS_SUCCESS ) {
629 g_debug (
"Icon failed to open: %s(%d): %s", dr->icon_name, dr->icon_size, icon_path );
630 cairo_surface_destroy ( icon_surf );
633 dr->icon = icon_surf;
635 g_free ( icon_path_ );
639 static void drun_mode_parse_entry_fields ()
644 const char *
const sep =
",#";
646 for (
unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
647 matching_entry_fields[i].enabled = FALSE;
649 for (
char *token = strtok_r ( switcher_str, sep, &savept ); token != NULL;
650 token = strtok_r ( NULL, sep, &savept ) ) {
651 if ( strcmp ( token,
"all" ) == 0 ) {
652 for (
unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
653 matching_entry_fields[i].enabled = TRUE;
658 gboolean matched = FALSE;
659 for (
unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
660 const char * entry_name = matching_entry_fields[i].entry_field_name;
661 if ( g_ascii_strcasecmp ( token, entry_name ) == 0 ) {
662 matching_entry_fields[i].enabled = TRUE;
667 g_warning (
"Invalid entry name :%s", token );
672 g_free ( switcher_str );
675 static int drun_mode_init (
Mode *sw )
681 static const gchar *
const drun_icon_fallback_themes[] = {
686 const gchar *themes[2] = {
690 DRunModePrivateData *pd = g_malloc0 (
sizeof ( *pd ) );
691 pd->disabled_entries = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
694 const char *current_desktop = g_getenv (
"XDG_CURRENT_DESKTOP" );
695 pd->current_desktop_list = current_desktop ? g_strsplit ( current_desktop,
":", 0 ) : NULL;
698 pd->xdg_context = nk_xdg_theme_context_new ( drun_icon_fallback_themes, NULL );
699 nk_xdg_theme_preload_themes_icon ( pd->xdg_context, themes );
700 drun_mode_parse_entry_fields ();
704 static void drun_entry_clear ( DRunModeEntry *e )
708 g_free ( e->app_id );
709 g_free ( e->desktop_id );
710 if ( e->icon != NULL ) {
711 cairo_surface_destroy ( e->icon );
713 g_free ( e->icon_name );
716 g_free ( e->generic_name );
717 g_free ( e->comment );
718 g_strfreev ( e->categories );
719 g_key_file_free ( e->key_file );
722 static ModeMode drun_mode_result (
Mode *sw,
int mretv,
char **input,
unsigned int selected_line )
738 else if ( ( mretv &
MENU_OK ) ) {
739 exec_cmd_entry ( &( rmpd->entry_list[selected_line] ) );
741 else if ( ( mretv &
MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] !=
'\0' ) {
746 else if ( ( mretv &
MENU_ENTRY_DELETE ) && selected_line < rmpd->cmd_list_length ) {
748 if ( rmpd->entry_list[selected_line].sort_index >= 0 ) {
750 g_thread_pool_free ( rmpd->pool, TRUE, TRUE );
753 delete_entry_history ( &( rmpd->entry_list[selected_line] ) );
754 drun_entry_clear ( &( rmpd->entry_list[selected_line] ) );
755 memmove ( &( rmpd->entry_list[selected_line] ), &rmpd->entry_list[selected_line + 1],
756 sizeof ( DRunModeEntry ) * ( rmpd->cmd_list_length - selected_line - 1 ) );
757 rmpd->cmd_list_length--;
763 static void drun_mode_destroy (
Mode *sw )
766 if ( rmpd != NULL ) {
768 g_thread_pool_free ( rmpd->pool, TRUE, TRUE );
771 for (
size_t i = 0; i < rmpd->cmd_list_length; i++ ) {
772 drun_entry_clear ( &( rmpd->entry_list[i] ) );
774 g_hash_table_destroy ( rmpd->disabled_entries );
775 g_free ( rmpd->entry_list );
776 nk_xdg_theme_context_free ( rmpd->xdg_context );
778 g_strfreev ( rmpd->current_desktop_list );
784 static char *
_get_display_value (
const Mode *sw,
unsigned int selected_line,
int *state, G_GNUC_UNUSED GList **list,
int get_entry )
791 if ( pd->entry_list == NULL ) {
793 return g_strdup (
"Failed" );
796 DRunModeEntry *dr = &( pd->entry_list[selected_line] );
797 if ( dr->generic_name == NULL ) {
798 return g_markup_printf_escaped (
"%s", dr->name );
801 return g_markup_printf_escaped (
"%s <span weight='light' size='small'><i>(%s)</i></span>", dr->name,
806 static cairo_surface_t *_get_icon (
const Mode *sw,
unsigned int selected_line,
int height )
809 g_return_val_if_fail ( pd->entry_list != NULL, NULL );
810 DRunModeEntry *dr = &( pd->entry_list[selected_line] );
811 if ( pd->pool == NULL ) {
813 pd->pool = g_thread_pool_new ( drun_icon_fetch, pd, 4, FALSE, NULL );
815 if ( dr->icon_size == 0 ) {
816 dr->icon_size = height;
818 g_thread_pool_push ( pd->pool, dr, NULL );
823 static char *drun_get_completion (
const Mode *sw,
unsigned int index )
827 DRunModeEntry *dr = &( pd->entry_list[index] );
828 if ( dr->generic_name == NULL ) {
829 return g_strdup ( dr->name );
832 return g_strdup_printf (
"%s", dr->name );
841 for (
int j = 0; match && tokens != NULL && tokens[j] != NULL; j++ ) {
845 if ( matching_entry_fields[DRUN_MATCH_FIELD_NAME].enabled ) {
846 if ( rmpd->entry_list[index].name ) {
850 if ( matching_entry_fields[DRUN_MATCH_FIELD_GENERIC].enabled ) {
852 if ( test == tokens[j]->invert && rmpd->entry_list[index].generic_name ) {
856 if ( matching_entry_fields[DRUN_MATCH_FIELD_EXEC].enabled ) {
858 if ( test == tokens[j]->invert ) {
862 if ( matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled ) {
864 if ( test == tokens[j]->invert ) {
865 gchar **list = rmpd->entry_list[index].categories;
866 for (
int iter = 0; test == tokens[j]->
invert && list && list[iter]; iter++ ) {
871 if ( matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled ) {
873 if ( test == tokens[j]->invert && rmpd->entry_list[index].comment ) {
886 static unsigned int drun_mode_get_num_entries (
const Mode *sw )
889 return pd->cmd_list_length;
895 .cfg_name_key =
"display-drun",
896 ._init = drun_mode_init,
897 ._get_num_entries = drun_mode_get_num_entries,
898 ._result = drun_mode_result,
899 ._destroy = drun_mode_destroy,
900 ._token_match = drun_token_match,
901 ._get_completion = drun_get_completion,
903 ._get_icon = _get_icon,
904 ._preprocess_input = NULL,
905 .private_data = NULL,
909 #endif // ENABLE_DRUN
void rofi_view_reload(void)
cairo_surface_t * cairo_image_surface_create_from_svg(const gchar *file, int height)
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)
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)
void mode_set_private_data(Mode *mode, void *pd)
static char * _get_display_value(const Mode *sw, unsigned int selected_line, int *state, G_GNUC_UNUSED GList **list, int get_entry)