rofi  1.5.1
drun.c
Go to the documentation of this file.
1 /*
2  * rofi
3  *
4  * MIT/X11 License
5  * Copyright © 2013-2017 Qball Cow <qball@gmpclient.org>
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining
8  * a copy of this software and associated documentation files (the
9  * "Software"), to deal in the Software without restriction, including
10  * without limitation the rights to use, copy, modify, merge, publish,
11  * distribute, sublicense, and/or sell copies of the Software, and to
12  * permit persons to whom the Software is furnished to do so, subject to
13  * the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be
16  * included in all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25  *
26  */
27 
28 #define G_LOG_DOMAIN "Dialogs.DRun"
29 
30 #include <config.h>
31 #ifdef ENABLE_DRUN
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <limits.h>
35 
36 #include <unistd.h>
37 #include <limits.h>
38 #include <signal.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <dirent.h>
42 #include <strings.h>
43 #include <string.h>
44 #include <errno.h>
45 
46 #include "rofi.h"
47 #include "settings.h"
48 #include "helper.h"
49 #include "timings.h"
50 #include "widgets/textbox.h"
51 #include "history.h"
52 #include "dialogs/drun.h"
53 #include "nkutils-xdg-theme.h"
54 #include "xcb.h"
55 
56 #define DRUN_CACHE_FILE "rofi3.druncache"
57 
58 #define DRUN_GROUP_NAME "Desktop Entry"
59 
64 typedef struct
65 {
66  /* Root */
67  char *root;
68  /* Path to desktop file */
69  char *path;
70  /* Application id (.desktop filename) */
71  char *app_id;
72  /* Desktop id */
73  char *desktop_id;
74  /* Icon stuff */
75  char *icon_name;
76  /* Icon size is used to indicate what size is requested by the gui.
77  * secondary it indicates if the request for a lookup has been issued (0 not issued )
78  */
79  int icon_size;
80  /* Surface holding the icon. */
81  cairo_surface_t *icon;
82  /* Executable */
83  char *exec;
84  /* Name of the Entry */
85  char *name;
86  /* Generic Name */
87  char *generic_name;
88  /* Categories */
89  char **categories;
90  /* Comments */
91  char *comment;
92 
93  GKeyFile *key_file;
94 
95  gint sort_index;
96 } DRunModeEntry;
97 
98 typedef struct
99 {
100  const char *entry_field_name;
101  gboolean enabled;
102 } DRunEntryField;
103 
104 typedef enum
105 {
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;
113 
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, }
120 };
121 
122 typedef struct
123 {
124  NkXdgThemeContext *xdg_context;
125  DRunModeEntry *entry_list;
126  unsigned int cmd_list_length;
127  unsigned int cmd_list_length_actual;
128  // List of disabled entries.
129  GHashTable *disabled_entries;
130  unsigned int disabled_entries_length;
131  GThreadPool *pool;
132  unsigned int expected_line_height;
133  DRunModeEntry quit_entry;
134 
135  // Theme
136  const gchar *icon_theme;
137  // DE
138  gchar **current_desktop_list;
139 } DRunModePrivateData;
140 
141 struct RegexEvalArg
142 {
143  DRunModeEntry *e;
144  gboolean success;
145 };
146 
147 static gboolean drun_helper_eval_cb ( const GMatchInfo *info, GString *res, gpointer data )
148 {
149  // TODO quoting is not right? Find description not very clear, need to check.
150  struct RegexEvalArg *e = (struct RegexEvalArg *) data;
151 
152  gchar *match;
153  // Get the match
154  match = g_match_info_fetch ( info, 0 );
155  if ( match != NULL ) {
156  switch ( match[1] )
157  {
158  // Unsupported
159  case 'f':
160  case 'F':
161  case 'u':
162  case 'U':
163  case 'i':
164  // Deprecated
165  case 'd':
166  case 'D':
167  case 'n':
168  case 'N':
169  case 'v':
170  case 'm':
171  break;
172  case 'k':
173  if ( e->e->path ) {
174  char *esc = g_shell_quote ( e->e->path );
175  g_string_append ( res, esc );
176  g_free ( esc );
177  }
178  break;
179  case 'c':
180  if ( e->e->name ) {
181  char *esc = g_shell_quote ( e->e->name );
182  g_string_append ( res, esc );
183  g_free ( esc );
184  }
185  break;
186  // Invalid, this entry should not be processed -> throw error.
187  default:
188  e->success = FALSE;
189  g_free ( match );
190  return TRUE;
191  }
192  g_free ( match );
193  }
194  // Continue replacement.
195  return FALSE;
196 }
197 static void exec_cmd_entry ( DRunModeEntry *e )
198 {
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 );
204  return;
205  }
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 );
211  return;
212  }
213  g_regex_unref ( reg );
214  if ( earg.success == FALSE ) {
215  g_warning ( "Invalid field code in Exec line: %s.", e->exec );;
216  return;
217  }
218  if ( str == NULL ) {
219  g_warning ( "Nothing to execute after processing: %s.", e->exec );;
220  return;
221  }
222 
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 ) {
226  // If it is empty, ignore this property. (#529)
227  g_free ( exec_path );
228  exec_path = NULL;
229  }
230 
231  RofiHelperExecuteContext context = {
232  .name = e->name,
233  .icon = e->icon_name,
234  .app_id = e->app_id,
235  };
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 );
240  }
241 
242  // Returns false if not found, if key not found, we don't want run in terminal.
243  gboolean terminal = g_key_file_get_boolean ( e->key_file, DRUN_GROUP_NAME, "Terminal", NULL );
244  if ( helper_execute_command ( exec_path, fp, terminal, sn ? &context : NULL ) ) {
245  char *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL );
246  // Store it based on the unique identifiers (desktop_id).
247  history_set ( path, e->desktop_id );
248  g_free ( path );
249  }
250  g_free ( wmclass );
251  g_free ( exec_path );
252  g_free ( str );
253 }
257 static gboolean read_desktop_file ( DRunModePrivateData *pd, const char *root, const char *path, const gchar *basename )
258 {
259  // Create ID on stack.
260  // We know strlen (path ) > strlen(root)+1
261  const ssize_t id_len = strlen ( path ) - strlen ( root );
262  char id[id_len];
263  g_strlcpy ( id, &( path[strlen ( root ) + 1] ), id_len );
264  for ( int index = 0; index < id_len; index++ ) {
265  if ( id[index] == '/' ) {
266  id[index] = '-';
267  }
268  }
269 
270  // Check if item is on disabled list.
271  if ( g_hash_table_contains ( pd->disabled_entries, id ) ) {
272  g_debug ( "[%s] [%s] Skipping, was previously seen.", id, path );
273  return TRUE;
274  }
275  GKeyFile *kf = g_key_file_new ();
276  GError *error = NULL;
277  gboolean res = g_key_file_load_from_file ( kf, path, 0, &error );
278  // If error, skip to next entry
279  if ( !res ) {
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 );
283  return FALSE;
284  }
285  // Skip non Application entries.
286  gchar *key = g_key_file_get_string ( kf, DRUN_GROUP_NAME, "Type", NULL );
287  if ( key == NULL ) {
288  // No type? ignore.
289  g_debug ( "[%s] [%s] Invalid desktop file: No type indicated", id, path );
290  g_key_file_free ( kf );
291  return FALSE;
292  }
293  if ( g_strcmp0 ( key, "Application" ) ) {
294  g_debug ( "[%s] [%s] Skipping desktop file: Not of type application (%s)", id, path, key );
295  g_free ( key );
296  g_key_file_free ( kf );
297  return FALSE;
298  }
299  g_free ( key );
300 
301  // Name key is required.
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 );
305  return FALSE;
306  }
307 
308  // Skip hidden entries.
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 ) );
313  return FALSE;
314  }
315  if ( pd->current_desktop_list ) {
316  gboolean show = TRUE;
317  // If the DE is set, check the keys.
318  if ( g_key_file_has_key ( kf, DRUN_GROUP_NAME, "OnlyShowIn", NULL ) ) {
319  gsize llength = 0;
320  show = FALSE;
321  gchar **list = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME, "OnlyShowIn", &llength, NULL );
322  if ( list ) {
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 );
326  }
327  }
328  g_strfreev ( list );
329  }
330  }
331  if ( show && g_key_file_has_key ( kf, DRUN_GROUP_NAME, "NotShowIn", NULL ) ) {
332  gsize llength = 0;
333  gchar **list = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME, "NotShowIn", &llength, NULL );
334  if ( list ) {
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 );
338  }
339  }
340  g_strfreev ( list );
341  }
342  }
343 
344  if ( !show ) {
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 ) );
348  return FALSE;
349  }
350  }
351  // Skip entries that have NoDisplay set.
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 ) );
356  return FALSE;
357  }
358  // We need Exec, don't support DBusActivatable
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 );
362  return FALSE;
363  }
364 
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 );
369  if ( fp == NULL ) {
370  g_free ( te );
371  g_key_file_free ( kf );
372  return FALSE;
373  }
374  g_free ( fp );
375  }
376  else {
377  if ( g_file_test ( te, G_FILE_TEST_IS_EXECUTABLE ) == FALSE ) {
378  g_free ( te );
379  g_key_file_free ( kf );
380  return FALSE;
381  }
382  }
383  g_free ( te );
384  }
385 
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 ) ) );
390  }
391  // Make sure order is preserved, this will break when cmd_list_length is bigger then INT_MAX.
392  // This is not likely to happen.
393  if ( G_UNLIKELY ( pd->cmd_list_length > INT_MAX ) ) {
394  // Default to smallest value.
395  pd->entry_list[pd->cmd_list_length].sort_index = INT_MIN;
396  }
397  else {
398  pd->entry_list[pd->cmd_list_length].sort_index = -pd->cmd_list_length;
399  }
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 );
411  }
412  else {
413  pd->entry_list[pd->cmd_list_length].categories = NULL;
414  }
415  pd->entry_list[pd->cmd_list_length].exec = g_key_file_get_string ( kf, DRUN_GROUP_NAME, "Exec", NULL );
416 
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 );
420  }
421  else {
422  pd->entry_list[pd->cmd_list_length].comment = NULL;
423  }
424  if ( config.show_icons ) {
425  pd->entry_list[pd->cmd_list_length].icon_name = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME, "Icon", NULL, NULL );
426  }
427  else{
428  pd->entry_list[pd->cmd_list_length].icon_name = NULL;
429  }
430  pd->entry_list[pd->cmd_list_length].icon = NULL;
431 
432  // Keep keyfile around.
433  pd->entry_list[pd->cmd_list_length].key_file = kf;
434  // We don't want to parse items with this id anymore.
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 )++;
438  return TRUE;
439 }
440 
444 static void walk_dir ( DRunModePrivateData *pd, const char *root, const char *dirname )
445 {
446  DIR *dir;
447 
448  g_debug ( "Checking directory %s for desktop files.", dirname );
449  dir = opendir ( dirname );
450  if ( dir == NULL ) {
451  return;
452  }
453 
454  struct dirent *file;
455  gchar *filename = NULL;
456  struct stat st;
457  while ( ( file = readdir ( dir ) ) != NULL ) {
458  if ( file->d_name[0] == '.' ) {
459  continue;
460  }
461  switch ( file->d_type )
462  {
463  case DT_LNK:
464  case DT_REG:
465  case DT_DIR:
466  case DT_UNKNOWN:
467  filename = g_build_filename ( dirname, file->d_name, NULL );
468  break;
469  default:
470  continue;
471  }
472 
473  // On a link, or if FS does not support providing this information
474  // Fallback to stat method.
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;
480  }
481  else if ( S_ISREG ( st.st_mode ) ) {
482  file->d_type = DT_REG;
483  }
484  }
485  }
486 
487  switch ( file->d_type )
488  {
489  case DT_REG:
490  // Skip files not ending on .desktop.
491  if ( g_str_has_suffix ( file->d_name, ".desktop" ) ) {
492  read_desktop_file ( pd, root, filename, file->d_name );
493  }
494  break;
495  case DT_DIR:
496  walk_dir ( pd, root, filename );
497  break;
498  default:
499  break;
500  }
501  g_free ( filename );
502  }
503  closedir ( dir );
504 }
510 static void delete_entry_history ( const DRunModeEntry *entry )
511 {
512  char *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL );
513  history_remove ( path, entry->desktop_id );
514  g_free ( path );
515 }
516 
517 static void get_apps_history ( DRunModePrivateData *pd )
518 {
519  TICK_N ( "Start drun history" );
520  unsigned int length = 0;
521  gchar *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL );
522  gchar **retv = history_get_list ( path, &length );
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;
529  }
530  else {
531  // This won't sort right anymore, but never gonna hit it anyway.
532  pd->entry_list[i].sort_index = INT_MAX;
533  }
534  }
535  }
536  }
537  g_strfreev ( retv );
538  g_free ( path );
539  TICK_N ( "Stop drun history" );
540 }
541 
542 static gint drun_int_sort_list ( gconstpointer a, gconstpointer b, G_GNUC_UNUSED gpointer user_data )
543 {
544  DRunModeEntry *da = (DRunModeEntry *) a;
545  DRunModeEntry *db = (DRunModeEntry *) b;
546 
547  return db->sort_index - da->sort_index;
548 }
549 
550 static void get_apps ( DRunModePrivateData *pd )
551 {
552  TICK_N ( "Get Desktop apps (start)" );
553 
554  gchar *dir;
555  // First read the user directory.
556  dir = g_build_filename ( g_get_user_data_dir (), "applications", NULL );
557  walk_dir ( pd, dir, dir );
558  g_free ( dir );
559  TICK_N ( "Get Desktop apps (user dir)" );
560  // Then read thee system data dirs.
561  const gchar * const * sys = g_get_system_data_dirs ();
562  for ( const gchar * const *iter = sys; *iter != NULL; ++iter ) {
563  gboolean unique = TRUE;
564  // Stupid duplicate detection, better then walking dir.
565  for ( const gchar *const *iterd = sys; iterd != iter; ++iterd ) {
566  if ( g_strcmp0 ( *iter, *iterd ) == 0 ) {
567  unique = FALSE;
568  }
569  }
570  // Check, we seem to be getting empty string...
571  if ( unique && ( **iter ) != '\0' ) {
572  dir = g_build_filename ( *iter, "applications", NULL );
573  walk_dir ( pd, dir, dir );
574  g_free ( dir );
575  }
576  }
577  TICK_N ( "Get Desktop apps (system dirs)" );
578  get_apps_history ( pd );
579 
580  g_qsort_with_data ( pd->entry_list, pd->cmd_list_length, sizeof ( DRunModeEntry ), drun_int_sort_list, NULL );
581 
582  TICK_N ( "Sorting done." );
583 }
584 
585 static void drun_icon_fetch ( gpointer data, gpointer user_data )
586 {
587  g_debug ( "Starting up icon fetching thread." );
588  // as long as dr->icon is updated atomicly.. (is a pointer write atomic?)
589  // this should be fine running in another thread.
590  DRunModePrivateData *pd = (DRunModePrivateData *) user_data;
591  DRunModeEntry *dr = (DRunModeEntry *) data;
592  const gchar *themes[2] = {
594  NULL
595  };
596 
597  if ( dr->icon_name == NULL ) {
598  return;
599  }
600  const gchar *icon_path;
601  gchar *icon_path_ = NULL;
602 
603  if ( g_path_is_absolute ( dr->icon_name ) ) {
604  icon_path = dr->icon_name;
605  }
606  else {
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 );
610  return;
611  }
612  else{
613  g_debug ( "Found Icon %s(%d): %s", dr->icon_name, dr->icon_size, icon_path );
614  }
615  }
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 );
619  }
620  else if ( g_str_has_suffix ( icon_path, ".svg" ) ) {
621  icon_surf = cairo_image_surface_create_from_svg ( icon_path, dr->icon_size );
622  }
623  else {
624  g_debug ( "Icon type not yet supported: %s", icon_path );
625  }
626  if ( icon_surf ) {
627  // Check if surface is valid.
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 );
631  icon_surf = NULL;
632  }
633  dr->icon = icon_surf;
634  }
635  g_free ( icon_path_ );
636  rofi_view_reload ();
637 }
638 
639 static void drun_mode_parse_entry_fields ()
640 {
641  char *savept = NULL;
642  // Make a copy, as strtok will modify it.
643  char *switcher_str = g_strdup ( config.drun_match_fields );
644  const char * const sep = ",#";
645  // Split token on ','. This modifies switcher_str.
646  for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
647  matching_entry_fields[i].enabled = FALSE;
648  }
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;
654  }
655  break;
656  }
657  else {
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;
663  matched = TRUE;
664  }
665  }
666  if ( !matched ) {
667  g_warning ( "Invalid entry name :%s", token );
668  }
669  }
670  }
671  // Free string that was modified by strtok_r
672  g_free ( switcher_str );
673 }
674 
675 static int drun_mode_init ( Mode *sw )
676 {
677  if ( mode_get_private_data ( sw ) != NULL ) {
678  return TRUE;
679  }
680 
681  static const gchar * const drun_icon_fallback_themes[] = {
682  "Adwaita",
683  "gnome",
684  NULL
685  };
686  const gchar *themes[2] = {
688  NULL
689  };
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 );
692  mode_set_private_data ( sw, (void *) pd );
693  // current destkop
694  const char *current_desktop = g_getenv ( "XDG_CURRENT_DESKTOP" );
695  pd->current_desktop_list = current_desktop ? g_strsplit ( current_desktop, ":", 0 ) : NULL;
696 
697  // Theme
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 ();
701  get_apps ( pd );
702  return TRUE;
703 }
704 static void drun_entry_clear ( DRunModeEntry *e )
705 {
706  g_free ( e->root );
707  g_free ( e->path );
708  g_free ( e->app_id );
709  g_free ( e->desktop_id );
710  if ( e->icon != NULL ) {
711  cairo_surface_destroy ( e->icon );
712  }
713  g_free ( e->icon_name );
714  g_free ( e->exec );
715  g_free ( e->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 );
720 }
721 
722 static ModeMode drun_mode_result ( Mode *sw, int mretv, char **input, unsigned int selected_line )
723 {
724  DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( sw );
725  ModeMode retv = MODE_EXIT;
726 
727  gboolean run_in_term = ( ( mretv & MENU_CUSTOM_ACTION ) == MENU_CUSTOM_ACTION );
728 
729  if ( mretv & MENU_NEXT ) {
730  retv = NEXT_DIALOG;
731  }
732  else if ( mretv & MENU_PREVIOUS ) {
733  retv = PREVIOUS_DIALOG;
734  }
735  else if ( mretv & MENU_QUICK_SWITCH ) {
736  retv = ( mretv & MENU_LOWER_MASK );
737  }
738  else if ( ( mretv & MENU_OK ) ) {
739  exec_cmd_entry ( &( rmpd->entry_list[selected_line] ) );
740  }
741  else if ( ( mretv & MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] != '\0' ) {
742  RofiHelperExecuteContext context = { .name = NULL };
743  // FIXME: We assume startup notification in terminals, not in others
744  helper_execute_command ( NULL, *input, run_in_term, run_in_term ? &context : NULL );
745  }
746  else if ( ( mretv & MENU_ENTRY_DELETE ) && selected_line < rmpd->cmd_list_length ) {
747  // Possitive sort index means it is in history.
748  if ( rmpd->entry_list[selected_line].sort_index >= 0 ) {
749  if ( rmpd->pool ) {
750  g_thread_pool_free ( rmpd->pool, TRUE, TRUE );
751  rmpd->pool = NULL;
752  }
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--;
758  }
759  retv = RELOAD_DIALOG;
760  }
761  return retv;
762 }
763 static void drun_mode_destroy ( Mode *sw )
764 {
765  DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( sw );
766  if ( rmpd != NULL ) {
767  if ( rmpd->pool ) {
768  g_thread_pool_free ( rmpd->pool, TRUE, TRUE );
769  rmpd->pool = NULL;
770  }
771  for ( size_t i = 0; i < rmpd->cmd_list_length; i++ ) {
772  drun_entry_clear ( &( rmpd->entry_list[i] ) );
773  }
774  g_hash_table_destroy ( rmpd->disabled_entries );
775  g_free ( rmpd->entry_list );
776  nk_xdg_theme_context_free ( rmpd->xdg_context );
777 
778  g_strfreev ( rmpd->current_desktop_list );
779  g_free ( rmpd );
780  mode_set_private_data ( sw, NULL );
781  }
782 }
783 
784 static char *_get_display_value ( const Mode *sw, unsigned int selected_line, int *state, G_GNUC_UNUSED GList **list, int get_entry )
785 {
786  DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw );
787  *state |= MARKUP;
788  if ( !get_entry ) {
789  return NULL;
790  }
791  if ( pd->entry_list == NULL ) {
792  // Should never get here.
793  return g_strdup ( "Failed" );
794  }
795  /* Free temp storage. */
796  DRunModeEntry *dr = &( pd->entry_list[selected_line] );
797  if ( dr->generic_name == NULL ) {
798  return g_markup_printf_escaped ( "%s", dr->name );
799  }
800  else {
801  return g_markup_printf_escaped ( "%s <span weight='light' size='small'><i>(%s)</i></span>", dr->name,
802  dr->generic_name );
803  }
804 }
805 
806 static cairo_surface_t *_get_icon ( const Mode *sw, unsigned int selected_line, int height )
807 {
808  DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw );
809  g_return_val_if_fail ( pd->entry_list != NULL, NULL );
810  DRunModeEntry *dr = &( pd->entry_list[selected_line] );
811  if ( pd->pool == NULL ) {
812  /* TODO: 4 threads good? */
813  pd->pool = g_thread_pool_new ( drun_icon_fetch, pd, 4, FALSE, NULL );
814  }
815  if ( dr->icon_size == 0 ) {
816  dr->icon_size = height;
817  //g_async_queue_push ( pd->icon_fetch_queue, dr );
818  g_thread_pool_push ( pd->pool, dr, NULL );
819  }
820  return dr->icon;
821 }
822 
823 static char *drun_get_completion ( const Mode *sw, unsigned int index )
824 {
825  DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw );
826  /* Free temp storage. */
827  DRunModeEntry *dr = &( pd->entry_list[index] );
828  if ( dr->generic_name == NULL ) {
829  return g_strdup ( dr->name );
830  }
831  else {
832  return g_strdup_printf ( "%s", dr->name );
833  }
834 }
835 
836 static int drun_token_match ( const Mode *data, rofi_int_matcher **tokens, unsigned int index )
837 {
838  DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( data );
839  int match = 1;
840  if ( tokens ) {
841  for ( int j = 0; match && tokens != NULL && tokens[j] != NULL; j++ ) {
842  int test = 0;
843  rofi_int_matcher *ftokens[2] = { tokens[j], NULL };
844  // Match name
845  if ( matching_entry_fields[DRUN_MATCH_FIELD_NAME].enabled ) {
846  if ( rmpd->entry_list[index].name ) {
847  test = helper_token_match ( ftokens, rmpd->entry_list[index].name );
848  }
849  }
850  if ( matching_entry_fields[DRUN_MATCH_FIELD_GENERIC].enabled ) {
851  // Match generic name
852  if ( test == tokens[j]->invert && rmpd->entry_list[index].generic_name ) {
853  test = helper_token_match ( ftokens, rmpd->entry_list[index].generic_name );
854  }
855  }
856  if ( matching_entry_fields[DRUN_MATCH_FIELD_EXEC].enabled ) {
857  // Match executable name.
858  if ( test == tokens[j]->invert ) {
859  test = helper_token_match ( ftokens, rmpd->entry_list[index].exec );
860  }
861  }
862  if ( matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled ) {
863  // Match against category.
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++ ) {
867  test = helper_token_match ( ftokens, list[iter] );
868  }
869  }
870  }
871  if ( matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled ) {
872  // Match executable name.
873  if ( test == tokens[j]->invert && rmpd->entry_list[index].comment ) {
874  test = helper_token_match ( ftokens, rmpd->entry_list[index].comment );
875  }
876  }
877  if ( test == 0 ) {
878  match = 0;
879  }
880  }
881  }
882 
883  return match;
884 }
885 
886 static unsigned int drun_mode_get_num_entries ( const Mode *sw )
887 {
888  const DRunModePrivateData *pd = (const DRunModePrivateData *) mode_get_private_data ( sw );
889  return pd->cmd_list_length;
890 }
891 #include "mode-private.h"
892 Mode drun_mode =
893 {
894  .name = "drun",
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,
902  ._get_display_value = _get_display_value,
903  ._get_icon = _get_icon,
904  ._preprocess_input = NULL,
905  .private_data = NULL,
906  .free = NULL
907 };
908 
909 #endif // ENABLE_DRUN
const char * cache_dir
Definition: rofi.c:80
const gchar * wmclass
Definition: helper.h:279
Definition: mode.h:69
void rofi_view_reload(void)
Definition: view.c:424
cairo_surface_t * cairo_image_surface_create_from_svg(const gchar *file, int height)
Definition: helper.c:1085
void history_set(const char *filename, const char *entry)
Definition: history.c:175
char ** history_get_list(const char *filename, unsigned int *length)
Definition: history.c:308
ModeMode
Definition: mode.h:49
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
Definition: helper.c:1000
Definition: mode.h:52
static void get_apps(KeysHelpModePrivateData *pd)
Definition: help-keys.c:54
void * mode_get_private_data(const Mode *mode)
Definition: mode.c:128
void history_remove(const char *filename, const char *entry)
Definition: history.c:242
char * drun_match_fields
Definition: settings.h:108
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition: helper.c:473
void mode_set_private_data(Mode *mode, void *pd)
Definition: mode.c:134
gboolean show_icons
Definition: settings.h:72
const gchar * name
Definition: helper.h:269
Definition: mode.h:73
Settings config
#define TICK_N(a)
Definition: timings.h:94
char * drun_icon_theme
Definition: settings.h:91
char * name
Definition: mode-private.h:156
static char * _get_display_value(const Mode *sw, unsigned int selected_line, int *state, G_GNUC_UNUSED GList **list, int get_entry)
Definition: help-keys.c:97