rofi  1.5.4
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 
29 #define G_LOG_DOMAIN "Dialogs.DRun"
30 
31 #include <config.h>
32 #ifdef ENABLE_DRUN
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <limits.h>
36 
37 #include <unistd.h>
38 #include <limits.h>
39 #include <signal.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <dirent.h>
43 #include <strings.h>
44 #include <string.h>
45 #include <errno.h>
46 
47 #include "rofi.h"
48 #include "settings.h"
49 #include "helper.h"
50 #include "timings.h"
51 #include "widgets/textbox.h"
52 #include "history.h"
53 #include "dialogs/drun.h"
54 #include "xcb.h"
55 
56 #include "rofi-icon-fetcher.h"
57 
58 #define DRUN_CACHE_FILE "rofi3.druncache"
59 
60 char *DRUN_GROUP_NAME = "Desktop Entry";
61 
62 typedef struct _DRunModePrivateData DRunModePrivateData;
67 typedef struct
68 {
69  thread_state st;
70  DRunModePrivateData *pd;
71  /* category */
72  char *action;
73  /* Root */
74  char *root;
75  /* Path to desktop file */
76  char *path;
77  /* Application id (.desktop filename) */
78  char *app_id;
79  /* Desktop id */
80  char *desktop_id;
81  /* Icon stuff */
82  char *icon_name;
83  /* Icon size is used to indicate what size is requested by the gui.
84  * secondary it indicates if the request for a lookup has been issued (0 not issued )
85  */
86  int icon_size;
87  /* Surface holding the icon. */
88  cairo_surface_t *icon;
89  /* Executable */
90  char *exec;
91  /* Name of the Entry */
92  char *name;
93  /* Generic Name */
94  char *generic_name;
95  /* Categories */
96  char **categories;
97  /* Comments */
98  char *comment;
99 
100  GKeyFile *key_file;
101 
102  gint sort_index;
103 
104  uint32_t icon_fetch_uid;
105 } DRunModeEntry;
106 
107 typedef struct
108 {
109  const char *entry_field_name;
110  gboolean enabled;
111 } DRunEntryField;
112 
113 typedef enum
114 {
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;
122 
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, }
129 };
130 
131 struct _DRunModePrivateData
132 {
133  DRunModeEntry *entry_list;
134  unsigned int cmd_list_length;
135  unsigned int cmd_list_length_actual;
136  // List of disabled entries.
137  GHashTable *disabled_entries;
138  unsigned int disabled_entries_length;
139  unsigned int expected_line_height;
140 
141  // Theme
142  const gchar *icon_theme;
143  // DE
144  gchar **current_desktop_list;
145 };
146 
147 struct RegexEvalArg
148 {
149  DRunModeEntry *e;
150  gboolean success;
151 };
152 
153 static gboolean drun_helper_eval_cb ( const GMatchInfo *info, GString *res, gpointer data )
154 {
155  // TODO quoting is not right? Find description not very clear, need to check.
156  struct RegexEvalArg *e = (struct RegexEvalArg *) data;
157 
158  gchar *match;
159  // Get the match
160  match = g_match_info_fetch ( info, 0 );
161  if ( match != NULL ) {
162  switch ( match[1] )
163  {
164  // Unsupported
165  case 'f':
166  case 'F':
167  case 'u':
168  case 'U':
169  case 'i':
170  // Deprecated
171  case 'd':
172  case 'D':
173  case 'n':
174  case 'N':
175  case 'v':
176  case 'm':
177  break;
178  case 'k':
179  if ( e->e->path ) {
180  char *esc = g_shell_quote ( e->e->path );
181  g_string_append ( res, esc );
182  g_free ( esc );
183  }
184  break;
185  case 'c':
186  if ( e->e->name ) {
187  char *esc = g_shell_quote ( e->e->name );
188  g_string_append ( res, esc );
189  g_free ( esc );
190  }
191  break;
192  // Invalid, this entry should not be processed -> throw error.
193  default:
194  e->success = FALSE;
195  g_free ( match );
196  return TRUE;
197  }
198  g_free ( match );
199  }
200  // Continue replacement.
201  return FALSE;
202 }
203 static void exec_cmd_entry ( DRunModeEntry *e )
204 {
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 );
210  return;
211  }
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 );
217  return;
218  }
219  g_regex_unref ( reg );
220  if ( earg.success == FALSE ) {
221  g_warning ( "Invalid field code in Exec line: %s.", e->exec );;
222  return;
223  }
224  if ( str == NULL ) {
225  g_warning ( "Nothing to execute after processing: %s.", e->exec );;
226  return;
227  }
228 
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 ) {
232  // If it is empty, ignore this property. (#529)
233  g_free ( exec_path );
234  exec_path = NULL;
235  }
236 
237  RofiHelperExecuteContext context = {
238  .name = e->name,
239  .icon = e->icon_name,
240  .app_id = e->app_id,
241  };
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 );
246  }
247 
248  // Returns false if not found, if key not found, we don't want run in terminal.
249  gboolean terminal = g_key_file_get_boolean ( e->key_file, e->action, "Terminal", NULL );
250  if ( helper_execute_command ( exec_path, fp, terminal, sn ? &context : NULL ) ) {
251  char *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL );
252  // Store it based on the unique identifiers (desktop_id).
253  history_set ( path, e->desktop_id );
254  g_free ( path );
255  }
256  g_free ( wmclass );
257  g_free ( exec_path );
258  g_free ( str );
259 }
263 static void read_desktop_file ( DRunModePrivateData *pd, const char *root, const char *path, const gchar *basename, const char *action )
264 {
265  int parse_action = ( config.drun_show_actions && action != DRUN_GROUP_NAME );
266  // Create ID on stack.
267  // We know strlen (path ) > strlen(root)+1
268  const ssize_t id_len = strlen ( path ) - strlen ( root );
269  char id[id_len];
270  g_strlcpy ( id, &( path[strlen ( root ) + 1] ), id_len );
271  for ( int index = 0; index < id_len; index++ ) {
272  if ( id[index] == '/' ) {
273  id[index] = '-';
274  }
275  }
276 
277  // Check if item is on disabled list.
278  if ( g_hash_table_contains ( pd->disabled_entries, id ) && !parse_action ) {
279  g_debug ( "[%s] [%s] Skipping, was previously seen.", id, path );
280  return ;
281  }
282  GKeyFile *kf = g_key_file_new ();
283  GError *error = NULL;
284  gboolean res = g_key_file_load_from_file ( kf, path, 0, &error );
285  // If error, skip to next entry
286  if ( !res ) {
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 );
290  return ;
291  }
292 
293  if ( g_key_file_has_group ( kf, action ) == FALSE ) {
294  // No type? ignore.
295  g_debug ( "[%s] [%s] Invalid desktop file: No %s group", id, path, action );
296  g_key_file_free ( kf );
297  return ;
298  }
299  // Skip non Application entries.
300  gchar *key = g_key_file_get_string ( kf, DRUN_GROUP_NAME, "Type", NULL );
301  if ( key == NULL ) {
302  // No type? ignore.
303  g_debug ( "[%s] [%s] Invalid desktop file: No type indicated", id, path );
304  g_key_file_free ( kf );
305  return ;
306  }
307  if ( g_strcmp0 ( key, "Application" ) ) {
308  g_debug ( "[%s] [%s] Skipping desktop file: Not of type application (%s)", id, path, key );
309  g_free ( key );
310  g_key_file_free ( kf );
311  return ;
312  }
313  g_free ( key );
314 
315  // Name key is required.
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 );
319  return ;
320  }
321 
322  // Skip hidden entries.
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 ) );
327  return ;
328  }
329  if ( pd->current_desktop_list ) {
330  gboolean show = TRUE;
331  // If the DE is set, check the keys.
332  if ( g_key_file_has_key ( kf, DRUN_GROUP_NAME, "OnlyShowIn", NULL ) ) {
333  gsize llength = 0;
334  show = FALSE;
335  gchar **list = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME, "OnlyShowIn", &llength, NULL );
336  if ( list ) {
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 );
340  }
341  }
342  g_strfreev ( list );
343  }
344  }
345  if ( show && g_key_file_has_key ( kf, DRUN_GROUP_NAME, "NotShowIn", NULL ) ) {
346  gsize llength = 0;
347  gchar **list = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME, "NotShowIn", &llength, NULL );
348  if ( list ) {
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 );
352  }
353  }
354  g_strfreev ( list );
355  }
356  }
357 
358  if ( !show ) {
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 ) );
362  return ;
363  }
364  }
365  // Skip entries that have NoDisplay set.
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 ) );
370  return ;
371  }
372  // We need Exec, don't support DBusActivatable
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 );
376  return ;
377  }
378 
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 );
383  if ( fp == NULL ) {
384  g_free ( te );
385  g_key_file_free ( kf );
386  return ;
387  }
388  g_free ( fp );
389  }
390  else {
391  if ( g_file_test ( te, G_FILE_TEST_IS_EXECUTABLE ) == FALSE ) {
392  g_free ( te );
393  g_key_file_free ( kf );
394  return ;
395  }
396  }
397  g_free ( te );
398  }
399 
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 ) ) );
404  }
405  // Make sure order is preserved, this will break when cmd_list_length is bigger then INT_MAX.
406  // This is not likely to happen.
407  if ( G_UNLIKELY ( pd->cmd_list_length > INT_MAX ) ) {
408  // Default to smallest value.
409  pd->entry_list[pd->cmd_list_length].sort_index = INT_MIN;
410  }
411  else {
412  pd->entry_list[pd->cmd_list_length].sort_index = -nl;
413  }
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 );
421 
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 );
425  g_free ( n );
426  n = l;
427  }
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 );
434  }
435  else {
436  pd->entry_list[pd->cmd_list_length].categories = NULL;
437  }
438  pd->entry_list[pd->cmd_list_length].exec = g_key_file_get_string ( kf, action, "Exec", NULL );
439 
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 );
443  }
444  else {
445  pd->entry_list[pd->cmd_list_length].comment = NULL;
446  }
447  if ( config.show_icons ) {
448  pd->entry_list[pd->cmd_list_length].icon_name = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME, "Icon", NULL, NULL );
449  }
450  else{
451  pd->entry_list[pd->cmd_list_length].icon_name = NULL;
452  }
453  pd->entry_list[pd->cmd_list_length].icon = NULL;
454 
455  // Keep keyfile around.
456  pd->entry_list[pd->cmd_list_length].key_file = kf;
457  // We don't want to parse items with this id anymore.
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 )++;
461 
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  read_desktop_file ( pd, root, path, basename, new_action );
468  g_free ( new_action );
469  }
470  g_strfreev ( actions );
471  }
472  return ;
473 }
474 
478 static void walk_dir ( DRunModePrivateData *pd, const char *root, const char *dirname )
479 {
480  DIR *dir;
481 
482  g_debug ( "Checking directory %s for desktop files.", dirname );
483  dir = opendir ( dirname );
484  if ( dir == NULL ) {
485  return;
486  }
487 
488  struct dirent *file;
489  gchar *filename = NULL;
490  struct stat st;
491  while ( ( file = readdir ( dir ) ) != NULL ) {
492  if ( file->d_name[0] == '.' ) {
493  continue;
494  }
495  switch ( file->d_type )
496  {
497  case DT_LNK:
498  case DT_REG:
499  case DT_DIR:
500  case DT_UNKNOWN:
501  filename = g_build_filename ( dirname, file->d_name, NULL );
502  break;
503  default:
504  continue;
505  }
506 
507  // On a link, or if FS does not support providing this information
508  // Fallback to stat method.
509  if ( file->d_type == DT_LNK || file->d_type == DT_UNKNOWN ) {
510  file->d_type = DT_UNKNOWN;
511  if ( stat ( filename, &st ) == 0 ) {
512  if ( S_ISDIR ( st.st_mode ) ) {
513  file->d_type = DT_DIR;
514  }
515  else if ( S_ISREG ( st.st_mode ) ) {
516  file->d_type = DT_REG;
517  }
518  }
519  }
520 
521  switch ( file->d_type )
522  {
523  case DT_REG:
524  // Skip files not ending on .desktop.
525  if ( g_str_has_suffix ( file->d_name, ".desktop" ) ) {
526  read_desktop_file ( pd, root, filename, file->d_name, DRUN_GROUP_NAME );
527  }
528  break;
529  case DT_DIR:
530  walk_dir ( pd, root, filename );
531  break;
532  default:
533  break;
534  }
535  g_free ( filename );
536  }
537  closedir ( dir );
538 }
544 static void delete_entry_history ( const DRunModeEntry *entry )
545 {
546  char *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL );
547  history_remove ( path, entry->desktop_id );
548  g_free ( path );
549 }
550 
551 static void get_apps_history ( DRunModePrivateData *pd )
552 {
553  TICK_N ( "Start drun history" );
554  unsigned int length = 0;
555  gchar *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL );
556  gchar **retv = history_get_list ( path, &length );
557  for ( unsigned int index = 0; index < length; index++ ) {
558  for ( size_t i = 0; i < pd->cmd_list_length; i++ ) {
559  if ( g_strcmp0 ( pd->entry_list[i].desktop_id, retv[index] ) == 0 ) {
560  unsigned int sort_index = length - index;
561  if ( G_LIKELY ( sort_index < INT_MAX ) ) {
562  pd->entry_list[i].sort_index = sort_index;
563  }
564  else {
565  // This won't sort right anymore, but never gonna hit it anyway.
566  pd->entry_list[i].sort_index = INT_MAX;
567  }
568  }
569  }
570  }
571  g_strfreev ( retv );
572  g_free ( path );
573  TICK_N ( "Stop drun history" );
574 }
575 
576 static gint drun_int_sort_list ( gconstpointer a, gconstpointer b, G_GNUC_UNUSED gpointer user_data )
577 {
578  DRunModeEntry *da = (DRunModeEntry *) a;
579  DRunModeEntry *db = (DRunModeEntry *) b;
580 
581  if ( da->sort_index < 0 && db->sort_index < 0 ) {
582  return g_utf8_collate ( da->name, db->name );
583  } else {
584  return db->sort_index - da->sort_index;
585  }
586 }
587 
588 static void get_apps ( DRunModePrivateData *pd )
589 {
590  TICK_N ( "Get Desktop apps (start)" );
591 
592  gchar *dir;
593  // First read the user directory.
594  dir = g_build_filename ( g_get_user_data_dir (), "applications", NULL );
595  walk_dir ( pd, dir, dir );
596  g_free ( dir );
597  TICK_N ( "Get Desktop apps (user dir)" );
598  // Then read thee system data dirs.
599  const gchar * const * sys = g_get_system_data_dirs ();
600  for ( const gchar * const *iter = sys; *iter != NULL; ++iter ) {
601  gboolean unique = TRUE;
602  // Stupid duplicate detection, better then walking dir.
603  for ( const gchar *const *iterd = sys; iterd != iter; ++iterd ) {
604  if ( g_strcmp0 ( *iter, *iterd ) == 0 ) {
605  unique = FALSE;
606  }
607  }
608  // Check, we seem to be getting empty string...
609  if ( unique && ( **iter ) != '\0' ) {
610  dir = g_build_filename ( *iter, "applications", NULL );
611  walk_dir ( pd, dir, dir );
612  g_free ( dir );
613  }
614  }
615  TICK_N ( "Get Desktop apps (system dirs)" );
616  get_apps_history ( pd );
617 
618  g_qsort_with_data ( pd->entry_list, pd->cmd_list_length, sizeof ( DRunModeEntry ), drun_int_sort_list, NULL );
619 
620  TICK_N ( "Sorting done." );
621 }
622 
623 static void drun_mode_parse_entry_fields ()
624 {
625  char *savept = NULL;
626  // Make a copy, as strtok will modify it.
627  char *switcher_str = g_strdup ( config.drun_match_fields );
628  const char * const sep = ",#";
629  // Split token on ','. This modifies switcher_str.
630  for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
631  matching_entry_fields[i].enabled = FALSE;
632  }
633  for ( char *token = strtok_r ( switcher_str, sep, &savept ); token != NULL;
634  token = strtok_r ( NULL, sep, &savept ) ) {
635  if ( strcmp ( token, "all" ) == 0 ) {
636  for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
637  matching_entry_fields[i].enabled = TRUE;
638  }
639  break;
640  }
641  else {
642  gboolean matched = FALSE;
643  for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
644  const char * entry_name = matching_entry_fields[i].entry_field_name;
645  if ( g_ascii_strcasecmp ( token, entry_name ) == 0 ) {
646  matching_entry_fields[i].enabled = TRUE;
647  matched = TRUE;
648  }
649  }
650  if ( !matched ) {
651  g_warning ( "Invalid entry name :%s", token );
652  }
653  }
654  }
655  // Free string that was modified by strtok_r
656  g_free ( switcher_str );
657 }
658 
659 static int drun_mode_init ( Mode *sw )
660 {
661  if ( mode_get_private_data ( sw ) != NULL ) {
662  return TRUE;
663  }
664  DRunModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) );
665  pd->disabled_entries = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
666  mode_set_private_data ( sw, (void *) pd );
667  // current destkop
668  const char *current_desktop = g_getenv ( "XDG_CURRENT_DESKTOP" );
669  pd->current_desktop_list = current_desktop ? g_strsplit ( current_desktop, ":", 0 ) : NULL;
670 
671  drun_mode_parse_entry_fields ();
672  get_apps ( pd );
673  return TRUE;
674 }
675 static void drun_entry_clear ( DRunModeEntry *e )
676 {
677  g_free ( e->root );
678  g_free ( e->path );
679  g_free ( e->app_id );
680  g_free ( e->desktop_id );
681  if ( e->icon != NULL ) {
682  cairo_surface_destroy ( e->icon );
683  }
684  g_free ( e->icon_name );
685  g_free ( e->exec );
686  g_free ( e->name );
687  g_free ( e->generic_name );
688  g_free ( e->comment );
689  if ( e->action != DRUN_GROUP_NAME ) {
690  g_free ( e->action );
691  }
692  g_strfreev ( e->categories );
693  g_key_file_free ( e->key_file );
694 }
695 
696 static ModeMode drun_mode_result ( Mode *sw, int mretv, char **input, unsigned int selected_line )
697 {
698  DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( sw );
699  ModeMode retv = MODE_EXIT;
700 
701  gboolean run_in_term = ( ( mretv & MENU_CUSTOM_ACTION ) == MENU_CUSTOM_ACTION );
702 
703  if ( mretv & MENU_NEXT ) {
704  retv = NEXT_DIALOG;
705  }
706  else if ( mretv & MENU_PREVIOUS ) {
707  retv = PREVIOUS_DIALOG;
708  }
709  else if ( mretv & MENU_QUICK_SWITCH ) {
710  retv = ( mretv & MENU_LOWER_MASK );
711  }
712  else if ( ( mretv & MENU_OK ) ) {
713  exec_cmd_entry ( &( rmpd->entry_list[selected_line] ) );
714  }
715  else if ( ( mretv & MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] != '\0' ) {
716  RofiHelperExecuteContext context = { .name = NULL };
717  // FIXME: We assume startup notification in terminals, not in others
718  helper_execute_command ( NULL, *input, run_in_term, run_in_term ? &context : NULL );
719  }
720  else if ( ( mretv & MENU_ENTRY_DELETE ) && selected_line < rmpd->cmd_list_length ) {
721  // Possitive sort index means it is in history.
722  if ( rmpd->entry_list[selected_line].sort_index >= 0 ) {
723  delete_entry_history ( &( rmpd->entry_list[selected_line] ) );
724  drun_entry_clear ( &( rmpd->entry_list[selected_line] ) );
725  memmove ( &( rmpd->entry_list[selected_line] ), &rmpd->entry_list[selected_line + 1],
726  sizeof ( DRunModeEntry ) * ( rmpd->cmd_list_length - selected_line - 1 ) );
727  rmpd->cmd_list_length--;
728  }
729  retv = RELOAD_DIALOG;
730  }
731  return retv;
732 }
733 static void drun_mode_destroy ( Mode *sw )
734 {
735  DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( sw );
736  if ( rmpd != NULL ) {
737  for ( size_t i = 0; i < rmpd->cmd_list_length; i++ ) {
738  drun_entry_clear ( &( rmpd->entry_list[i] ) );
739  }
740  g_hash_table_destroy ( rmpd->disabled_entries );
741  g_free ( rmpd->entry_list );
742 
743  g_strfreev ( rmpd->current_desktop_list );
744  g_free ( rmpd );
745  mode_set_private_data ( sw, NULL );
746  }
747 }
748 
749 static char *_get_display_value ( const Mode *sw, unsigned int selected_line, int *state, G_GNUC_UNUSED GList **list, int get_entry )
750 {
751  DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw );
752  *state |= MARKUP;
753  if ( !get_entry ) {
754  return NULL;
755  }
756  if ( pd->entry_list == NULL ) {
757  // Should never get here.
758  return g_strdup ( "Failed" );
759  }
760  /* Free temp storage. */
761  DRunModeEntry *dr = &( pd->entry_list[selected_line] );
762  gchar *cats = NULL;
763  if ( dr->categories ){
764  char *tcats = g_strjoinv(",", dr->categories);
765  if ( tcats ) {
766  cats = g_markup_escape_text ( tcats, -1 );
767  g_free (tcats);
768  }
769  }
770  // Needed for display.
771  char *egn = NULL;
772  char *en = NULL;
773  char *ec = NULL;
774  if ( dr->generic_name ) {
775  egn = g_markup_escape_text ( dr->generic_name, -1 );
776  }
777  if ( dr->name ) {
778  en = g_markup_escape_text ( dr->name, -1 );
779  }
780  if ( dr->comment ) {
781  ec = g_markup_escape_text ( dr->comment , -1 );
782  }
783 
784 
786  "{generic}", egn,
787  "{name}", en,
788  "{comment}", ec,
789  "{exec}", dr->exec,
790  "{categories}", cats,
791  NULL);
792  g_free ( egn );
793  g_free ( en );
794  g_free ( ec );
795  g_free(cats);
796  return retv;
797 }
798 
799 static cairo_surface_t *_get_icon ( const Mode *sw, unsigned int selected_line, int height )
800 {
801  DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw );
802  g_return_val_if_fail ( pd->entry_list != NULL, NULL );
803  DRunModeEntry *dr = &( pd->entry_list[selected_line] );
804  if ( dr->icon_name == NULL ) {
805  return NULL;
806  }
807  if ( dr->icon_fetch_uid > 0 ) {
808  return rofi_icon_fetcher_get ( dr->icon_fetch_uid );
809  }
810  dr->icon_fetch_uid = rofi_icon_fetcher_query ( dr->icon_name, height );
811  return rofi_icon_fetcher_get ( dr->icon_fetch_uid );
812 }
813 
814 static char *drun_get_completion ( const Mode *sw, unsigned int index )
815 {
816  DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw );
817  /* Free temp storage. */
818  DRunModeEntry *dr = &( pd->entry_list[index] );
819  if ( dr->generic_name == NULL ) {
820  return g_strdup ( dr->name );
821  }
822  else {
823  return g_strdup_printf ( "%s", dr->name );
824  }
825 }
826 
827 static int drun_token_match ( const Mode *data, rofi_int_matcher **tokens, unsigned int index )
828 {
829  DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( data );
830  int match = 1;
831  if ( tokens ) {
832  for ( int j = 0; match && tokens != NULL && tokens[j] != NULL; j++ ) {
833  int test = 0;
834  rofi_int_matcher *ftokens[2] = { tokens[j], NULL };
835  // Match name
836  if ( matching_entry_fields[DRUN_MATCH_FIELD_NAME].enabled ) {
837  if ( rmpd->entry_list[index].name ) {
838  test = helper_token_match ( ftokens, rmpd->entry_list[index].name );
839  }
840  }
841  if ( matching_entry_fields[DRUN_MATCH_FIELD_GENERIC].enabled ) {
842  // Match generic name
843  if ( test == tokens[j]->invert && rmpd->entry_list[index].generic_name ) {
844  test = helper_token_match ( ftokens, rmpd->entry_list[index].generic_name );
845  }
846  }
847  if ( matching_entry_fields[DRUN_MATCH_FIELD_EXEC].enabled ) {
848  // Match executable name.
849  if ( test == tokens[j]->invert ) {
850  test = helper_token_match ( ftokens, rmpd->entry_list[index].exec );
851  }
852  }
853  if ( matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled ) {
854  // Match against category.
855  if ( test == tokens[j]->invert ) {
856  gchar **list = rmpd->entry_list[index].categories;
857  for ( int iter = 0; test == tokens[j]->invert && list && list[iter]; iter++ ) {
858  test = helper_token_match ( ftokens, list[iter] );
859  }
860  }
861  }
862  if ( matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled ) {
863  // Match executable name.
864  if ( test == tokens[j]->invert && rmpd->entry_list[index].comment ) {
865  test = helper_token_match ( ftokens, rmpd->entry_list[index].comment );
866  }
867  }
868  if ( test == 0 ) {
869  match = 0;
870  }
871  }
872  }
873 
874  return match;
875 }
876 
877 static unsigned int drun_mode_get_num_entries ( const Mode *sw )
878 {
879  const DRunModePrivateData *pd = (const DRunModePrivateData *) mode_get_private_data ( sw );
880  return pd->cmd_list_length;
881 }
882 #include "mode-private.h"
883 Mode drun_mode =
884 {
885  .name = "drun",
886  .cfg_name_key = "display-drun",
887  ._init = drun_mode_init,
888  ._get_num_entries = drun_mode_get_num_entries,
889  ._result = drun_mode_result,
890  ._destroy = drun_mode_destroy,
891  ._token_match = drun_token_match,
892  ._get_completion = drun_get_completion,
893  ._get_display_value = _get_display_value,
894  ._get_icon = _get_icon,
895  ._preprocess_input = NULL,
896  .private_data = NULL,
897  .free = NULL
898 };
899 
900 #endif // ENABLE_DRUN
MENU_QUICK_SWITCH
@ MENU_QUICK_SWITCH
Definition: mode.h:79
history.h
cache_dir
const char * cache_dir
Definition: rofi.c:84
_thread_state
Definition: rofi-types.h:245
rofi_mode::name
char * name
Definition: mode-private.h:156
settings.h
MARKUP
@ MARKUP
Definition: textbox.h:105
MENU_OK
@ MENU_OK
Definition: mode.h:69
rofi_int_matcher_t
Definition: rofi-types.h:235
Settings::drun_match_fields
char * drun_match_fields
Definition: settings.h:120
mode_set_private_data
void mode_set_private_data(Mode *mode, void *pd)
Definition: mode.c:134
RofiHelperExecuteContext
Definition: helper.h:267
PREVIOUS_DIALOG
@ PREVIOUS_DIALOG
Definition: mode.h:58
MENU_PREVIOUS
@ MENU_PREVIOUS
Definition: mode.h:81
NEXT_DIALOG
@ NEXT_DIALOG
Definition: mode.h:54
timings.h
mode-private.h
MODE_EXIT
@ MODE_EXIT
Definition: mode.h:52
Settings::drun_display_format
char * drun_display_format
Definition: settings.h:124
MENU_ENTRY_DELETE
@ MENU_ENTRY_DELETE
Definition: mode.h:77
TICK_N
#define TICK_N(a)
Definition: timings.h:68
rofi_icon_fetcher_get
cairo_surface_t * rofi_icon_fetcher_get(const uint32_t uid)
Definition: rofi-icon-fetcher.c:216
rofi_int_matcher_t::invert
gboolean invert
Definition: rofi-types.h:237
rofi_mode
Definition: mode-private.h:152
rofi.h
drun.h
helper_string_replace_if_exists
char * helper_string_replace_if_exists(char *string,...)
Definition: helper.c:1265
xcb.h
history_set
void history_set(const char *filename, const char *entry)
Definition: history.c:178
history_get_list
char ** history_get_list(const char *filename, unsigned int *length)
Definition: history.c:325
MENU_CUSTOM_ACTION
@ MENU_CUSTOM_ACTION
Definition: mode.h:83
icon
struct _icon icon
Definition: icon.h:44
MENU_CUSTOM_INPUT
@ MENU_CUSTOM_INPUT
Definition: mode.h:75
helper_execute_command
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
Definition: helper.c:989
mode_get_private_data
void * mode_get_private_data(const Mode *mode)
Definition: mode.c:128
MENU_NEXT
@ MENU_NEXT
Definition: mode.h:73
rofi_icon_fetcher_query
uint32_t rofi_icon_fetcher_query(const char *name, const int size)
Definition: rofi-icon-fetcher.c:182
RofiHelperExecuteContext::wmclass
const gchar * wmclass
Definition: helper.h:279
get_apps
static void get_apps(KeysHelpModePrivateData *pd)
Definition: help-keys.c:54
helper_token_match
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition: helper.c:445
textbox.h
rofi-icon-fetcher.h
ModeMode
ModeMode
Definition: mode.h:50
RELOAD_DIALOG
@ RELOAD_DIALOG
Definition: mode.h:56
Settings::show_icons
gboolean show_icons
Definition: settings.h:78
MENU_LOWER_MASK
@ MENU_LOWER_MASK
Definition: mode.h:85
helper.h
config
Settings config
history_remove
void history_remove(const char *filename, const char *entry)
Definition: history.c:259
RofiHelperExecuteContext::name
const gchar * name
Definition: helper.h:269
_get_display_value
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
Settings::drun_show_actions
unsigned int drun_show_actions
Definition: settings.h:122