rofi  1.6.0
drun.c
Go to the documentation of this file.
1 /*
2  * rofi
3  *
4  * MIT/X11 License
5  * Copyright © 2013-2020 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 #define DRUN_DESKTOP_CACHE_FILE "rofi-drun-desktop.cache"
60 
61 char *DRUN_GROUP_NAME = "Desktop Entry";
62 
63 typedef struct _DRunModePrivateData DRunModePrivateData;
64 
65 typedef enum
66 {
67  DRUN_DESKTOP_ENTRY_TYPE_UNDETERMINED = 0,
68  DRUN_DESKTOP_ENTRY_TYPE_APPLICATION,
69  DRUN_DESKTOP_ENTRY_TYPE_LINK,
70  DRUN_DESKTOP_ENTRY_TYPE_DIRECTORY,
71 } DRunDesktopEntryType;
72 
77 typedef struct
78 {
79  DRunModePrivateData *pd;
80  /* category */
81  char *action;
82  /* Root */
83  char *root;
84  /* Path to desktop file */
85  char *path;
86  /* Application id (.desktop filename) */
87  char *app_id;
88  /* Desktop id */
89  char *desktop_id;
90  /* Icon stuff */
91  char *icon_name;
92  /* Icon size is used to indicate what size is requested by the gui.
93  * secondary it indicates if the request for a lookup has been issued (0 not issued )
94  */
95  int icon_size;
96  /* Surface holding the icon. */
97  cairo_surface_t *icon;
98  /* Executable - for Application entries only */
99  char *exec;
100  /* Name of the Entry */
101  char *name;
102  /* Generic Name */
103  char *generic_name;
104  /* Categories */
105  char **categories;
106  /* Keywords */
107  char **keywords;
108  /* Comments */
109  char *comment;
110 
111  GKeyFile *key_file;
112 
113  gint sort_index;
114 
115  uint32_t icon_fetch_uid;
116 
117  DRunDesktopEntryType type;
118 } DRunModeEntry;
119 
120 typedef struct
121 {
122  const char *entry_field_name;
123  gboolean enabled;
124 } DRunEntryField;
125 
126 typedef enum
127 {
128  DRUN_MATCH_FIELD_NAME,
129  DRUN_MATCH_FIELD_GENERIC,
130  DRUN_MATCH_FIELD_EXEC,
131  DRUN_MATCH_FIELD_CATEGORIES,
132  DRUN_MATCH_FIELD_KEYWORDS,
133  DRUN_MATCH_FIELD_COMMENT,
134  DRUN_MATCH_NUM_FIELDS,
135 } DRunMatchingFields;
136 
137 static DRunEntryField matching_entry_fields[DRUN_MATCH_NUM_FIELDS] = {
138  { .entry_field_name = "name", .enabled = TRUE, },
139  { .entry_field_name = "generic", .enabled = TRUE, },
140  { .entry_field_name = "exec", .enabled = TRUE, },
141  { .entry_field_name = "categories", .enabled = TRUE, },
142  { .entry_field_name = "keywords", .enabled = TRUE, },
143  { .entry_field_name = "comment", .enabled = FALSE, }
144 };
145 
146 struct _DRunModePrivateData
147 {
148  DRunModeEntry *entry_list;
149  unsigned int cmd_list_length;
150  unsigned int cmd_list_length_actual;
151  // List of disabled entries.
152  GHashTable *disabled_entries;
153  unsigned int disabled_entries_length;
154  unsigned int expected_line_height;
155 
156  char **show_categories;
157 
158  // Theme
159  const gchar *icon_theme;
160  // DE
161  gchar **current_desktop_list;
162 };
163 
164 struct RegexEvalArg
165 {
166  DRunModeEntry *e;
167  gboolean success;
168 };
169 
170 static gboolean drun_helper_eval_cb ( const GMatchInfo *info, GString *res, gpointer data )
171 {
172  // TODO quoting is not right? Find description not very clear, need to check.
173  struct RegexEvalArg *e = (struct RegexEvalArg *) data;
174 
175  gchar *match;
176  // Get the match
177  match = g_match_info_fetch ( info, 0 );
178  if ( match != NULL ) {
179  switch ( match[1] )
180  {
181  // Unsupported
182  case 'f':
183  case 'F':
184  case 'u':
185  case 'U':
186  case 'i':
187  // Deprecated
188  case 'd':
189  case 'D':
190  case 'n':
191  case 'N':
192  case 'v':
193  case 'm':
194  break;
195  case '%':
196  g_string_append ( res, "%" );
197  break;
198  case 'k':
199  if ( e->e->path ) {
200  char *esc = g_shell_quote ( e->e->path );
201  g_string_append ( res, esc );
202  g_free ( esc );
203  }
204  break;
205  case 'c':
206  if ( e->e->name ) {
207  char *esc = g_shell_quote ( e->e->name );
208  g_string_append ( res, esc );
209  g_free ( esc );
210  }
211  break;
212  // Invalid, this entry should not be processed -> throw error.
213  default:
214  e->success = FALSE;
215  g_free ( match );
216  return TRUE;
217  }
218  g_free ( match );
219  }
220  // Continue replacement.
221  return FALSE;
222 }
223 static void launch_link_entry ( DRunModeEntry *e )
224 {
225  if ( e->key_file == NULL ) {
226  GKeyFile *kf = g_key_file_new ();
227  GError *error = NULL;
228  gboolean res = g_key_file_load_from_file ( kf, e->path, 0, &error );
229  if ( res ) {
230  e->key_file = kf;
231  }
232  else {
233  g_warning ( "[%s] [%s] Failed to parse desktop file because: %s.", e->app_id, e->path, error->message );
234  g_error_free ( error );
235  g_key_file_free ( kf );
236  return;
237  }
238  }
239 
240  gchar *url = g_key_file_get_string ( e->key_file, e->action, "URL", NULL );
241  if ( url == NULL || strlen ( url ) == 0 ) {
242  g_warning ( "[%s] [%s] No URL found.", e->app_id, e->path );
243  g_free ( url );
244  return;
245  }
246 
247  gsize command_len = strlen ( config.drun_url_launcher ) + strlen ( url ) + 2; // space + terminator = 2
248  gchar *command = g_newa ( gchar, command_len );
249  g_snprintf ( command, command_len, "%s %s", config.drun_url_launcher, url );
250  g_free ( url );
251 
252  g_debug ( "Link launch command: |%s|", command );
253  if ( helper_execute_command ( NULL, command, FALSE, NULL ) ) {
254  char *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL );
255  // Store it based on the unique identifiers (desktop_id).
256  history_set ( path, e->desktop_id );
257  g_free ( path );
258  }
259 }
260 static void exec_cmd_entry ( DRunModeEntry *e )
261 {
262  GError *error = NULL;
263  GRegex *reg = g_regex_new ( "%[a-zA-Z%]", 0, 0, &error );
264  if ( error != NULL ) {
265  g_warning ( "Internal error, failed to create regex: %s.", error->message );
266  g_error_free ( error );
267  return;
268  }
269  struct RegexEvalArg earg = { .e = e, .success = TRUE };
270  char *str = g_regex_replace_eval ( reg, e->exec, -1, 0, 0, drun_helper_eval_cb, &earg, &error );
271  if ( error != NULL ) {
272  g_warning ( "Internal error, failed replace field codes: %s.", error->message );
273  g_error_free ( error );
274  return;
275  }
276  g_regex_unref ( reg );
277  if ( earg.success == FALSE ) {
278  g_warning ( "Invalid field code in Exec line: %s.", e->exec );;
279  return;
280  }
281  if ( str == NULL ) {
282  g_warning ( "Nothing to execute after processing: %s.", e->exec );;
283  return;
284  }
285  g_debug ( "Parsed command: |%s| into |%s|.", e->exec, str );
286 
287  if ( e->key_file == NULL ) {
288  GKeyFile *kf = g_key_file_new ();
289  GError *error = NULL;
290  gboolean res = g_key_file_load_from_file ( kf, e->path, 0, &error );
291  if ( res ) {
292  e->key_file = kf;
293  }
294  else {
295  g_warning ( "[%s] [%s] Failed to parse desktop file because: %s.", e->app_id, e->path, error->message );
296  g_error_free ( error );
297  g_key_file_free ( kf );
298 
299  return;
300  }
301  }
302 
303  const gchar *fp = g_strstrip ( str );
304  gchar *exec_path = g_key_file_get_string ( e->key_file, e->action, "Path", NULL );
305  if ( exec_path != NULL && strlen ( exec_path ) == 0 ) {
306  // If it is empty, ignore this property. (#529)
307  g_free ( exec_path );
308  exec_path = NULL;
309  }
310 
311  RofiHelperExecuteContext context = {
312  .name = e->name,
313  .icon = e->icon_name,
314  .app_id = e->app_id,
315  };
316  gboolean sn = g_key_file_get_boolean ( e->key_file, e->action, "StartupNotify", NULL );
317  gchar *wmclass = NULL;
318  if ( sn && g_key_file_has_key ( e->key_file, e->action, "StartupWMClass", NULL ) ) {
319  context.wmclass = wmclass = g_key_file_get_string ( e->key_file, e->action, "StartupWMClass", NULL );
320  }
321 
322  // Returns false if not found, if key not found, we don't want run in terminal.
323  gboolean terminal = g_key_file_get_boolean ( e->key_file, e->action, "Terminal", NULL );
324  if ( helper_execute_command ( exec_path, fp, terminal, sn ? &context : NULL ) ) {
325  char *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL );
326  // Store it based on the unique identifiers (desktop_id).
327  history_set ( path, e->desktop_id );
328  g_free ( path );
329  }
330  g_free ( wmclass );
331  g_free ( exec_path );
332  g_free ( str );
333 }
334 
335 static gboolean rofi_strv_contains ( const char * const *categories, const char *const *field )
336 {
337  for ( int i = 0; categories && categories[i]; i++ ) {
338  for ( int j = 0; field[j]; j++ ) {
339  if ( g_str_equal ( categories[i], field[j] ) ) {
340  return TRUE;
341  }
342  }
343  }
344  return FALSE;
345 }
349 static void read_desktop_file ( DRunModePrivateData *pd, const char *root, const char *path, const gchar *basename, const char *action )
350 {
351  DRunDesktopEntryType desktop_entry_type = DRUN_DESKTOP_ENTRY_TYPE_UNDETERMINED;
352  int parse_action = ( config.drun_show_actions && action != DRUN_GROUP_NAME );
353  // Create ID on stack.
354  // We know strlen (path ) > strlen(root)+1
355  const ssize_t id_len = strlen ( path ) - strlen ( root );
356  char id[id_len];
357  g_strlcpy ( id, &( path[strlen ( root ) + 1] ), id_len );
358  for ( int index = 0; index < id_len; index++ ) {
359  if ( id[index] == '/' ) {
360  id[index] = '-';
361  }
362  }
363 
364  // Check if item is on disabled list.
365  if ( g_hash_table_contains ( pd->disabled_entries, id ) && !parse_action ) {
366  g_debug ( "[%s] [%s] Skipping, was previously seen.", id, path );
367  return;
368  }
369  GKeyFile *kf = g_key_file_new ();
370  GError *error = NULL;
371  gboolean res = g_key_file_load_from_file ( kf, path, 0, &error );
372  // If error, skip to next entry
373  if ( !res ) {
374  g_debug ( "[%s] [%s] Failed to parse desktop file because: %s.", id, path, error->message );
375  g_error_free ( error );
376  g_key_file_free ( kf );
377  return;
378  }
379 
380  if ( g_key_file_has_group ( kf, action ) == FALSE ) {
381  // No type? ignore.
382  g_debug ( "[%s] [%s] Invalid desktop file: No %s group", id, path, action );
383  g_key_file_free ( kf );
384  return;
385  }
386  // Skip non Application entries.
387  gchar *key = g_key_file_get_string ( kf, DRUN_GROUP_NAME, "Type", NULL );
388  if ( key == NULL ) {
389  // No type? ignore.
390  g_debug ( "[%s] [%s] Invalid desktop file: No type indicated", id, path );
391  g_key_file_free ( kf );
392  return;
393  }
394  if ( !g_strcmp0 ( key, "Application" ) ) {
395  desktop_entry_type = DRUN_DESKTOP_ENTRY_TYPE_APPLICATION;
396  }
397  else if ( !g_strcmp0 ( key, "Link" ) ) {
398  desktop_entry_type = DRUN_DESKTOP_ENTRY_TYPE_LINK;
399  }
400  else {
401  g_debug ( "[%s] [%s] Skipping desktop file: Not of type Application or Link (%s)", id, path, key );
402  g_free ( key );
403  g_key_file_free ( kf );
404  return;
405  }
406  g_free ( key );
407 
408  // Name key is required.
409  if ( !g_key_file_has_key ( kf, DRUN_GROUP_NAME, "Name", NULL ) ) {
410  g_debug ( "[%s] [%s] Invalid desktop file: no 'Name' key present.", id, path );
411  g_key_file_free ( kf );
412  return;
413  }
414 
415  // Skip hidden entries.
416  if ( g_key_file_get_boolean ( kf, DRUN_GROUP_NAME, "Hidden", NULL ) ) {
417  g_debug ( "[%s] [%s] Adding desktop file to disabled list: 'Hidden' key is true", id, path );
418  g_key_file_free ( kf );
419  g_hash_table_add ( pd->disabled_entries, g_strdup ( id ) );
420  return;
421  }
422  if ( pd->current_desktop_list ) {
423  gboolean show = TRUE;
424  // If the DE is set, check the keys.
425  if ( g_key_file_has_key ( kf, DRUN_GROUP_NAME, "OnlyShowIn", NULL ) ) {
426  gsize llength = 0;
427  show = FALSE;
428  gchar **list = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME, "OnlyShowIn", &llength, NULL );
429  if ( list ) {
430  for ( gsize lcd = 0; !show && pd->current_desktop_list[lcd]; lcd++ ) {
431  for ( gsize lle = 0; !show && lle < llength; lle++ ) {
432  show = ( g_strcmp0 ( pd->current_desktop_list[lcd], list[lle] ) == 0 );
433  }
434  }
435  g_strfreev ( list );
436  }
437  }
438  if ( show && g_key_file_has_key ( kf, DRUN_GROUP_NAME, "NotShowIn", NULL ) ) {
439  gsize llength = 0;
440  gchar **list = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME, "NotShowIn", &llength, NULL );
441  if ( list ) {
442  for ( gsize lcd = 0; show && pd->current_desktop_list[lcd]; lcd++ ) {
443  for ( gsize lle = 0; show && lle < llength; lle++ ) {
444  show = !( g_strcmp0 ( pd->current_desktop_list[lcd], list[lle] ) == 0 );
445  }
446  }
447  g_strfreev ( list );
448  }
449  }
450 
451  if ( !show ) {
452  g_debug ( "[%s] [%s] Adding desktop file to disabled list: 'OnlyShowIn'/'NotShowIn' keys don't match current desktop", id, path );
453  g_key_file_free ( kf );
454  g_hash_table_add ( pd->disabled_entries, g_strdup ( id ) );
455  return;
456  }
457  }
458  // Skip entries that have NoDisplay set.
459  if ( g_key_file_get_boolean ( kf, DRUN_GROUP_NAME, "NoDisplay", NULL ) ) {
460  g_debug ( "[%s] [%s] Adding desktop file to disabled list: 'NoDisplay' key is true", id, path );
461  g_key_file_free ( kf );
462  g_hash_table_add ( pd->disabled_entries, g_strdup ( id ) );
463  return;
464  }
465 
466  // We need Exec, don't support DBusActivatable
467  if ( desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_APPLICATION
468  && !g_key_file_has_key ( kf, DRUN_GROUP_NAME, "Exec", NULL ) ) {
469  g_debug ( "[%s] [%s] Unsupported desktop file: no 'Exec' key present for type Application.", id, path );
470  g_key_file_free ( kf );
471  return;
472  }
473  if ( desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_LINK
474  && !g_key_file_has_key ( kf, DRUN_GROUP_NAME, "URL", NULL ) ) {
475  g_debug ( "[%s] [%s] Unsupported desktop file: no 'URL' key present for type Link.", id, path );
476  g_key_file_free ( kf );
477  return;
478  }
479 
480  if ( g_key_file_has_key ( kf, DRUN_GROUP_NAME, "TryExec", NULL ) ) {
481  char *te = g_key_file_get_string ( kf, DRUN_GROUP_NAME, "TryExec", NULL );
482  if ( !g_path_is_absolute ( te ) ) {
483  char *fp = g_find_program_in_path ( te );
484  if ( fp == NULL ) {
485  g_free ( te );
486  g_key_file_free ( kf );
487  return;
488  }
489  g_free ( fp );
490  }
491  else {
492  if ( g_file_test ( te, G_FILE_TEST_IS_EXECUTABLE ) == FALSE ) {
493  g_free ( te );
494  g_key_file_free ( kf );
495  return;
496  }
497  }
498  g_free ( te );
499  }
500 
501  char **categories = NULL;
502  if ( pd->show_categories ) {
503  categories = g_key_file_get_locale_string_list ( kf, DRUN_GROUP_NAME, "Categories", NULL, NULL, NULL );
504  if ( !rofi_strv_contains ( (const char * const *) categories, (const char * const *) pd->show_categories ) ) {
505  g_strfreev ( categories );
506  g_key_file_free ( kf );
507  return;
508  }
509  }
510 
511  size_t nl = ( ( pd->cmd_list_length ) + 1 );
512  if ( nl >= pd->cmd_list_length_actual ) {
513  pd->cmd_list_length_actual += 256;
514  pd->entry_list = g_realloc ( pd->entry_list, pd->cmd_list_length_actual * sizeof ( *( pd->entry_list ) ) );
515  }
516  // Make sure order is preserved, this will break when cmd_list_length is bigger then INT_MAX.
517  // This is not likely to happen.
518  if ( G_UNLIKELY ( pd->cmd_list_length > INT_MAX ) ) {
519  // Default to smallest value.
520  pd->entry_list[pd->cmd_list_length].sort_index = INT_MIN;
521  }
522  else {
523  pd->entry_list[pd->cmd_list_length].sort_index = -nl;
524  }
525  pd->entry_list[pd->cmd_list_length].icon_size = 0;
526  pd->entry_list[pd->cmd_list_length].icon_fetch_uid = 0;
527  pd->entry_list[pd->cmd_list_length].root = g_strdup ( root );
528  pd->entry_list[pd->cmd_list_length].path = g_strdup ( path );
529  pd->entry_list[pd->cmd_list_length].desktop_id = g_strdup ( id );
530  pd->entry_list[pd->cmd_list_length].app_id = g_strndup ( basename, strlen ( basename ) - strlen ( ".desktop" ) );
531  gchar *n = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME, "Name", NULL, NULL );
532 
533  if ( action != DRUN_GROUP_NAME ) {
534  gchar *na = g_key_file_get_locale_string ( kf, action, "Name", NULL, NULL );
535  gchar *l = g_strdup_printf ( "%s - %s", n, na );
536  g_free ( n );
537  n = l;
538  }
539  pd->entry_list[pd->cmd_list_length].name = n;
540  pd->entry_list[pd->cmd_list_length].action = DRUN_GROUP_NAME;
541  gchar *gn = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME, "GenericName", NULL, NULL );
542  pd->entry_list[pd->cmd_list_length].generic_name = gn;
543 
544  if ( matching_entry_fields[DRUN_MATCH_FIELD_KEYWORDS].enabled ) {
545  pd->entry_list[pd->cmd_list_length].keywords = g_key_file_get_locale_string_list ( kf, DRUN_GROUP_NAME, "Keywords", NULL, NULL, NULL );
546  }
547  else {
548  pd->entry_list[pd->cmd_list_length].keywords = NULL;
549  }
550 
551  if ( matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled ) {
552  if ( categories ) {
553  pd->entry_list[pd->cmd_list_length].categories = categories;
554  categories = NULL;
555  }
556  else {
557  pd->entry_list[pd->cmd_list_length].categories = g_key_file_get_locale_string_list ( kf, DRUN_GROUP_NAME, "Categories", NULL, NULL, NULL );
558  }
559  }
560  else {
561  pd->entry_list[pd->cmd_list_length].categories = NULL;
562  }
563  g_strfreev ( categories );
564 
565  pd->entry_list[pd->cmd_list_length].type = desktop_entry_type;
566  if ( desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_APPLICATION ) {
567  pd->entry_list[pd->cmd_list_length].exec = g_key_file_get_string ( kf, action, "Exec", NULL );
568  }
569  else {
570  pd->entry_list[pd->cmd_list_length].exec = NULL;
571  }
572 
573  if ( matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled ) {
574  pd->entry_list[pd->cmd_list_length].comment = g_key_file_get_locale_string ( kf,
575  DRUN_GROUP_NAME, "Comment", NULL, NULL );
576  }
577  else {
578  pd->entry_list[pd->cmd_list_length].comment = NULL;
579  }
580  if ( config.show_icons ) {
581  pd->entry_list[pd->cmd_list_length].icon_name = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME, "Icon", NULL, NULL );
582  }
583  else{
584  pd->entry_list[pd->cmd_list_length].icon_name = NULL;
585  }
586  pd->entry_list[pd->cmd_list_length].icon = NULL;
587 
588  // Keep keyfile around.
589  pd->entry_list[pd->cmd_list_length].key_file = kf;
590  // We don't want to parse items with this id anymore.
591  g_hash_table_add ( pd->disabled_entries, g_strdup ( id ) );
592  g_debug ( "[%s] Using file %s.", id, path );
593  ( pd->cmd_list_length )++;
594 
595  if ( !parse_action ) {
596  gsize actions_length = 0;
597  char **actions = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME, "Actions", &actions_length, NULL );
598  for ( gsize iter = 0; iter < actions_length; iter++ ) {
599  char *new_action = g_strdup_printf ( "Desktop Action %s", actions[iter] );
600  read_desktop_file ( pd, root, path, basename, new_action );
601  g_free ( new_action );
602  }
603  g_strfreev ( actions );
604  }
605  return;
606 }
607 
611 static void walk_dir ( DRunModePrivateData *pd, const char *root, const char *dirname )
612 {
613  DIR *dir;
614 
615  g_debug ( "Checking directory %s for desktop files.", dirname );
616  dir = opendir ( dirname );
617  if ( dir == NULL ) {
618  return;
619  }
620 
621  struct dirent *file;
622  gchar *filename = NULL;
623  struct stat st;
624  while ( ( file = readdir ( dir ) ) != NULL ) {
625  if ( file->d_name[0] == '.' ) {
626  continue;
627  }
628  switch ( file->d_type )
629  {
630  case DT_LNK:
631  case DT_REG:
632  case DT_DIR:
633  case DT_UNKNOWN:
634  filename = g_build_filename ( dirname, file->d_name, NULL );
635  break;
636  default:
637  continue;
638  }
639 
640  // On a link, or if FS does not support providing this information
641  // Fallback to stat method.
642  if ( file->d_type == DT_LNK || file->d_type == DT_UNKNOWN ) {
643  file->d_type = DT_UNKNOWN;
644  if ( stat ( filename, &st ) == 0 ) {
645  if ( S_ISDIR ( st.st_mode ) ) {
646  file->d_type = DT_DIR;
647  }
648  else if ( S_ISREG ( st.st_mode ) ) {
649  file->d_type = DT_REG;
650  }
651  }
652  }
653 
654  switch ( file->d_type )
655  {
656  case DT_REG:
657  // Skip files not ending on .desktop.
658  if ( g_str_has_suffix ( file->d_name, ".desktop" ) ) {
659  read_desktop_file ( pd, root, filename, file->d_name, DRUN_GROUP_NAME );
660  }
661  break;
662  case DT_DIR:
663  walk_dir ( pd, root, filename );
664  break;
665  default:
666  break;
667  }
668  g_free ( filename );
669  }
670  closedir ( dir );
671 }
677 static void delete_entry_history ( const DRunModeEntry *entry )
678 {
679  char *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL );
680  history_remove ( path, entry->desktop_id );
681  g_free ( path );
682 }
683 
684 static void get_apps_history ( DRunModePrivateData *pd )
685 {
686  TICK_N ( "Start drun history" );
687  unsigned int length = 0;
688  gchar *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL );
689  gchar **retv = history_get_list ( path, &length );
690  for ( unsigned int index = 0; index < length; index++ ) {
691  for ( size_t i = 0; i < pd->cmd_list_length; i++ ) {
692  if ( g_strcmp0 ( pd->entry_list[i].desktop_id, retv[index] ) == 0 ) {
693  unsigned int sort_index = length - index;
694  if ( G_LIKELY ( sort_index < INT_MAX ) ) {
695  pd->entry_list[i].sort_index = sort_index;
696  }
697  else {
698  // This won't sort right anymore, but never gonna hit it anyway.
699  pd->entry_list[i].sort_index = INT_MAX;
700  }
701  }
702  }
703  }
704  g_strfreev ( retv );
705  g_free ( path );
706  TICK_N ( "Stop drun history" );
707 }
708 
709 static gint drun_int_sort_list ( gconstpointer a, gconstpointer b, G_GNUC_UNUSED gpointer user_data )
710 {
711  DRunModeEntry *da = (DRunModeEntry *) a;
712  DRunModeEntry *db = (DRunModeEntry *) b;
713 
714  if ( da->sort_index < 0 && db->sort_index < 0 ) {
715  return g_utf8_collate ( da->name, db->name );
716  }
717  else {
718  return db->sort_index - da->sort_index;
719  }
720 }
721 
722 /*******************************************
723 * Cache voodoo *
724 *******************************************/
725 
726 #define CACHE_VERSION 1
727 static void drun_write_str ( FILE *fd, const char *str )
728 {
729  size_t l = ( str == NULL ? 0 : strlen ( str ) );
730  fwrite ( &l, sizeof ( l ), 1, fd );
731  // Only write string if it is not NULL or empty.
732  if ( l > 0 ) {
733  // Also writeout terminating '\0'
734  fwrite ( str, 1, l + 1, fd );
735  }
736 }
737 static void drun_read_string ( FILE *fd, char **str )
738 {
739  size_t l = 0;
740 
741  if ( fread ( &l, sizeof ( l ), 1, fd ) != 1 ) {
742  g_warning ( "Failed to read entry, cache corrupt?" );
743  return;
744  }
745  ( *str ) = NULL;
746  if ( l > 0 ) {
747  // Include \0
748  l++;
749  ( *str ) = g_malloc ( l );
750  if ( fread ( ( *str ), 1, l, fd ) != l ) {
751  g_warning ( "Failed to read entry, cache corrupt?" );
752  }
753  }
754 }
755 static void drun_write_strv ( FILE *fd, char **str )
756 {
757  guint vl = ( str == NULL ? 0 : g_strv_length ( str ) );
758  fwrite ( &vl, sizeof ( vl ), 1, fd );
759  for ( guint index = 0; index < vl; index++ ) {
760  drun_write_str ( fd, str[index] );
761  }
762 }
763 static void drun_read_stringv ( FILE *fd, char ***str )
764 {
765  guint vl = 0;
766  ( *str ) = NULL;
767  if ( fread ( &vl, sizeof ( vl ), 1, fd ) != 1 ) {
768  g_warning ( "Failed to read entry, cache corrupt?" );
769  return;
770  }
771  if ( vl > 0 ) {
772  // Include terminating NULL entry.
773  ( *str ) = g_malloc0 ( ( vl + 1 ) * sizeof ( **str ) );
774  for ( guint index = 0; index < vl; index++ ) {
775  drun_read_string ( fd, &( ( *str )[index] ) );
776  }
777  }
778 }
779 
780 static void write_cache ( DRunModePrivateData *pd, const char *cache_file )
781 {
782  if ( cache_file == NULL || config.drun_use_desktop_cache == FALSE ) {
783  return;
784  }
785  TICK_N ( "DRUN Write CACHE: start" );
786 
787  FILE *fd = fopen ( cache_file, "w" );
788  if ( fd == NULL ) {
789  g_warning ( "Failed to write to cache file" );
790  return;
791  }
792  uint8_t version = CACHE_VERSION;
793  fwrite ( &version, sizeof ( version ), 1, fd );
794 
795  fwrite ( &( pd->cmd_list_length ), sizeof ( pd->cmd_list_length ), 1, fd );
796  for ( unsigned int index = 0; index < pd->cmd_list_length; index++ ) {
797  DRunModeEntry *entry = &( pd->entry_list[index] );
798 
799  drun_write_str ( fd, entry->action );
800  drun_write_str ( fd, entry->root );
801  drun_write_str ( fd, entry->path );
802  drun_write_str ( fd, entry->app_id );
803  drun_write_str ( fd, entry->desktop_id );
804  drun_write_str ( fd, entry->icon_name );
805  drun_write_str ( fd, entry->exec );
806  drun_write_str ( fd, entry->name );
807  drun_write_str ( fd, entry->generic_name );
808 
809  drun_write_strv ( fd, entry->categories );
810  drun_write_strv ( fd, entry->keywords );
811 
812  drun_write_str ( fd, entry->comment );
813  }
814 
815  fclose ( fd );
816  TICK_N ( "DRUN Write CACHE: end" );
817 }
818 
822 static gboolean drun_read_cache ( DRunModePrivateData *pd, const char *cache_file )
823 {
824  if ( cache_file == NULL || config.drun_use_desktop_cache == FALSE ) {
825  return TRUE;
826  }
827 
829  return TRUE;
830  }
831  TICK_N ( "DRUN Read CACHE: start" );
832  FILE *fd = fopen ( cache_file, "r" );
833  if ( fd == NULL ) {
834  TICK_N ( "DRUN Read CACHE: stop" );
835  return TRUE;
836  }
837 
838  // Read version.
839  uint8_t version = 0;
840 
841  if ( fread ( &version, sizeof ( version ), 1, fd ) != 1 ) {
842  fclose ( fd );
843  g_warning ( "Cache corrupt, ignoring." );
844  TICK_N ( "DRUN Read CACHE: stop" );
845  return FALSE;
846  }
847 
848  if ( version != CACHE_VERSION ) {
849  fclose ( fd );
850  g_warning ( "Cache file wrong version, ignoring." );
851  TICK_N ( "DRUN Read CACHE: stop" );
852  return FALSE;
853  }
854 
855  if ( fread ( &( pd->cmd_list_length ), sizeof ( pd->cmd_list_length ), 1, fd ) != 1 ) {
856  fclose ( fd );
857  g_warning ( "Cache corrupt, ignoring." );
858  TICK_N ( "DRUN Read CACHE: stop" );
859  return FALSE;
860  }
861  // set actual length to length;
862  pd->cmd_list_length_actual = pd->cmd_list_length;
863 
864  pd->entry_list = g_malloc0 ( pd->cmd_list_length_actual * sizeof ( *( pd->entry_list ) ) );
865 
866  for ( unsigned int index = 0; index < pd->cmd_list_length; index++ ) {
867  DRunModeEntry *entry = &( pd->entry_list[index] );
868 
869  drun_read_string ( fd, &( entry->action ) );
870  drun_read_string ( fd, &( entry->root ) );
871  drun_read_string ( fd, &( entry->path ) );
872  drun_read_string ( fd, &( entry->app_id ) );
873  drun_read_string ( fd, &( entry->desktop_id ) );
874  drun_read_string ( fd, &( entry->icon_name ) );
875  drun_read_string ( fd, &( entry->exec ) );
876  drun_read_string ( fd, &( entry->name ) );
877  drun_read_string ( fd, &( entry->generic_name ) );
878 
879  drun_read_stringv ( fd, &( entry->categories ) );
880  drun_read_stringv ( fd, &( entry->keywords ) );
881 
882  drun_read_string ( fd, &( entry->comment ) );
883  }
884 
885  fclose ( fd );
886  TICK_N ( "DRUN Read CACHE: stop" );
887  return FALSE;
888 }
889 
890 static void get_apps ( DRunModePrivateData *pd )
891 {
892  char *cache_file = g_build_filename ( cache_dir, DRUN_DESKTOP_CACHE_FILE, NULL );
893  TICK_N ( "Get Desktop apps (start)" );
894  if ( drun_read_cache ( pd, cache_file ) ) {
895  gchar *dir;
896  // First read the user directory.
897  dir = g_build_filename ( g_get_user_data_dir (), "applications", NULL );
898  walk_dir ( pd, dir, dir );
899  g_free ( dir );
900  TICK_N ( "Get Desktop apps (user dir)" );
901  // Then read thee system data dirs.
902  const gchar * const * sys = g_get_system_data_dirs ();
903  for ( const gchar * const *iter = sys; *iter != NULL; ++iter ) {
904  gboolean unique = TRUE;
905  // Stupid duplicate detection, better then walking dir.
906  for ( const gchar *const *iterd = sys; iterd != iter; ++iterd ) {
907  if ( g_strcmp0 ( *iter, *iterd ) == 0 ) {
908  unique = FALSE;
909  }
910  }
911  // Check, we seem to be getting empty string...
912  if ( unique && ( **iter ) != '\0' ) {
913  dir = g_build_filename ( *iter, "applications", NULL );
914  walk_dir ( pd, dir, dir );
915  g_free ( dir );
916  }
917  }
918  TICK_N ( "Get Desktop apps (system dirs)" );
919  get_apps_history ( pd );
920 
921  g_qsort_with_data ( pd->entry_list, pd->cmd_list_length, sizeof ( DRunModeEntry ), drun_int_sort_list, NULL );
922 
923  TICK_N ( "Sorting done." );
924 
925  write_cache ( pd, cache_file );
926  }
927  g_free ( cache_file );
928 }
929 
930 static void drun_mode_parse_entry_fields ()
931 {
932  char *savept = NULL;
933  // Make a copy, as strtok will modify it.
934  char *switcher_str = g_strdup ( config.drun_match_fields );
935  const char * const sep = ",#";
936  // Split token on ','. This modifies switcher_str.
937  for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
938  matching_entry_fields[i].enabled = FALSE;
939  }
940  for ( char *token = strtok_r ( switcher_str, sep, &savept ); token != NULL;
941  token = strtok_r ( NULL, sep, &savept ) ) {
942  if ( strcmp ( token, "all" ) == 0 ) {
943  for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
944  matching_entry_fields[i].enabled = TRUE;
945  }
946  break;
947  }
948  else {
949  gboolean matched = FALSE;
950  for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
951  const char * entry_name = matching_entry_fields[i].entry_field_name;
952  if ( g_ascii_strcasecmp ( token, entry_name ) == 0 ) {
953  matching_entry_fields[i].enabled = TRUE;
954  matched = TRUE;
955  }
956  }
957  if ( !matched ) {
958  g_warning ( "Invalid entry name :%s", token );
959  }
960  }
961  }
962  // Free string that was modified by strtok_r
963  g_free ( switcher_str );
964 }
965 
966 static int drun_mode_init ( Mode *sw )
967 {
968  if ( mode_get_private_data ( sw ) != NULL ) {
969  return TRUE;
970  }
971  DRunModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) );
972  pd->disabled_entries = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
973  mode_set_private_data ( sw, (void *) pd );
974  // current destkop
975  const char *current_desktop = g_getenv ( "XDG_CURRENT_DESKTOP" );
976  pd->current_desktop_list = current_desktop ? g_strsplit ( current_desktop, ":", 0 ) : NULL;
977 
979  pd->show_categories = g_strsplit ( config.drun_categories, ",", 0 );
980  }
981 
982  drun_mode_parse_entry_fields ();
983  get_apps ( pd );
984  return TRUE;
985 }
986 static void drun_entry_clear ( DRunModeEntry *e )
987 {
988  g_free ( e->root );
989  g_free ( e->path );
990  g_free ( e->app_id );
991  g_free ( e->desktop_id );
992  if ( e->icon != NULL ) {
993  cairo_surface_destroy ( e->icon );
994  }
995  g_free ( e->icon_name );
996  g_free ( e->exec );
997  g_free ( e->name );
998  g_free ( e->generic_name );
999  g_free ( e->comment );
1000  if ( e->action != DRUN_GROUP_NAME ) {
1001  g_free ( e->action );
1002  }
1003  g_strfreev ( e->categories );
1004  g_strfreev ( e->keywords );
1005  if ( e->key_file ) {
1006  g_key_file_free ( e->key_file );
1007  }
1008 }
1009 
1010 static ModeMode drun_mode_result ( Mode *sw, int mretv, char **input, unsigned int selected_line )
1011 {
1012  DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( sw );
1013  ModeMode retv = MODE_EXIT;
1014 
1015  if ( mretv & MENU_NEXT ) {
1016  retv = NEXT_DIALOG;
1017  }
1018  else if ( mretv & MENU_PREVIOUS ) {
1019  retv = PREVIOUS_DIALOG;
1020  }
1021  else if ( mretv & MENU_QUICK_SWITCH ) {
1022  retv = ( mretv & MENU_LOWER_MASK );
1023  }
1024  else if ( ( mretv & MENU_OK ) ) {
1025  switch ( rmpd->entry_list[selected_line].type )
1026  {
1027  case DRUN_DESKTOP_ENTRY_TYPE_APPLICATION:
1028  exec_cmd_entry ( &( rmpd->entry_list[selected_line] ) );
1029  break;
1030  case DRUN_DESKTOP_ENTRY_TYPE_LINK:
1031  launch_link_entry ( &( rmpd->entry_list[selected_line] ) );
1032  break;
1033  default:
1034  g_assert_not_reached ();
1035  }
1036  }
1037  else if ( ( mretv & MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] != '\0' ) {
1038  retv = RELOAD_DIALOG;
1039  }
1040  else if ( ( mretv & MENU_ENTRY_DELETE ) && selected_line < rmpd->cmd_list_length ) {
1041  // Possitive sort index means it is in history.
1042  if ( rmpd->entry_list[selected_line].sort_index >= 0 ) {
1043  delete_entry_history ( &( rmpd->entry_list[selected_line] ) );
1044  drun_entry_clear ( &( rmpd->entry_list[selected_line] ) );
1045  memmove ( &( rmpd->entry_list[selected_line] ), &rmpd->entry_list[selected_line + 1],
1046  sizeof ( DRunModeEntry ) * ( rmpd->cmd_list_length - selected_line - 1 ) );
1047  rmpd->cmd_list_length--;
1048  }
1049  retv = RELOAD_DIALOG;
1050  }
1051  return retv;
1052 }
1053 static void drun_mode_destroy ( Mode *sw )
1054 {
1055  DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( sw );
1056  if ( rmpd != NULL ) {
1057  for ( size_t i = 0; i < rmpd->cmd_list_length; i++ ) {
1058  drun_entry_clear ( &( rmpd->entry_list[i] ) );
1059  }
1060  g_hash_table_destroy ( rmpd->disabled_entries );
1061  g_free ( rmpd->entry_list );
1062 
1063  g_strfreev ( rmpd->current_desktop_list );
1064  g_strfreev ( rmpd->show_categories );
1065  g_free ( rmpd );
1066  mode_set_private_data ( sw, NULL );
1067  }
1068 }
1069 
1070 static char *_get_display_value ( const Mode *sw, unsigned int selected_line, int *state, G_GNUC_UNUSED GList **list, int get_entry )
1071 {
1072  DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw );
1073  *state |= MARKUP;
1074  if ( !get_entry ) {
1075  return NULL;
1076  }
1077  if ( pd->entry_list == NULL ) {
1078  // Should never get here.
1079  return g_strdup ( "Failed" );
1080  }
1081  /* Free temp storage. */
1082  DRunModeEntry *dr = &( pd->entry_list[selected_line] );
1083  gchar *cats = NULL;
1084  if ( dr->categories ) {
1085  char *tcats = g_strjoinv ( ",", dr->categories );
1086  if ( tcats ) {
1087  cats = g_markup_escape_text ( tcats, -1 );
1088  g_free ( tcats );
1089  }
1090  }
1091  gchar *keywords = NULL;
1092  if ( dr->keywords ) {
1093  char *tkeyw = g_strjoinv ( ",", dr->keywords );
1094  if ( tkeyw ) {
1095  keywords = g_markup_escape_text ( tkeyw, -1 );
1096  g_free ( tkeyw );
1097  }
1098  }
1099  // Needed for display.
1100  char *egn = NULL;
1101  char *en = NULL;
1102  char *ec = NULL;
1103  if ( dr->generic_name ) {
1104  egn = g_markup_escape_text ( dr->generic_name, -1 );
1105  }
1106  if ( dr->name ) {
1107  en = g_markup_escape_text ( dr->name, -1 );
1108  }
1109  if ( dr->comment ) {
1110  ec = g_markup_escape_text ( dr->comment, -1 );
1111  }
1112 
1114  "{generic}", egn,
1115  "{name}", en,
1116  "{comment}", ec,
1117  "{exec}", dr->exec,
1118  "{categories}", cats,
1119  "{keywords}", keywords,
1120  NULL );
1121  g_free ( egn );
1122  g_free ( en );
1123  g_free ( ec );
1124  g_free ( cats );
1125  return retv;
1126 }
1127 
1128 static cairo_surface_t *_get_icon ( const Mode *sw, unsigned int selected_line, int height )
1129 {
1130  DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw );
1131  g_return_val_if_fail ( pd->entry_list != NULL, NULL );
1132  DRunModeEntry *dr = &( pd->entry_list[selected_line] );
1133  if ( dr->icon_name == NULL ) {
1134  return NULL;
1135  }
1136  if ( dr->icon_fetch_uid > 0 ) {
1137  return rofi_icon_fetcher_get ( dr->icon_fetch_uid );
1138  }
1139  dr->icon_fetch_uid = rofi_icon_fetcher_query ( dr->icon_name, height );
1140  return rofi_icon_fetcher_get ( dr->icon_fetch_uid );
1141 }
1142 
1143 static char *drun_get_completion ( const Mode *sw, unsigned int index )
1144 {
1145  DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw );
1146  /* Free temp storage. */
1147  DRunModeEntry *dr = &( pd->entry_list[index] );
1148  if ( dr->generic_name == NULL ) {
1149  return g_strdup ( dr->name );
1150  }
1151  else {
1152  return g_strdup_printf ( "%s", dr->name );
1153  }
1154 }
1155 
1156 static int drun_token_match ( const Mode *data, rofi_int_matcher **tokens, unsigned int index )
1157 {
1158  DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( data );
1159  int match = 1;
1160  if ( tokens ) {
1161  for ( int j = 0; match && tokens != NULL && tokens[j] != NULL; j++ ) {
1162  int test = 0;
1163  rofi_int_matcher *ftokens[2] = { tokens[j], NULL };
1164  // Match name
1165  if ( matching_entry_fields[DRUN_MATCH_FIELD_NAME].enabled ) {
1166  if ( rmpd->entry_list[index].name ) {
1167  test = helper_token_match ( ftokens, rmpd->entry_list[index].name );
1168  }
1169  }
1170  if ( matching_entry_fields[DRUN_MATCH_FIELD_GENERIC].enabled ) {
1171  // Match generic name
1172  if ( test == tokens[j]->invert && rmpd->entry_list[index].generic_name ) {
1173  test = helper_token_match ( ftokens, rmpd->entry_list[index].generic_name );
1174  }
1175  }
1176  if ( matching_entry_fields[DRUN_MATCH_FIELD_EXEC].enabled ) {
1177  // Match executable name.
1178  if ( test == tokens[j]->invert && rmpd->entry_list[index].exec ) {
1179  test = helper_token_match ( ftokens, rmpd->entry_list[index].exec );
1180  }
1181  }
1182  if ( matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled ) {
1183  // Match against category.
1184  if ( test == tokens[j]->invert ) {
1185  gchar **list = rmpd->entry_list[index].categories;
1186  for ( int iter = 0; test == tokens[j]->invert && list && list[iter]; iter++ ) {
1187  test = helper_token_match ( ftokens, list[iter] );
1188  }
1189  }
1190  }
1191  if ( matching_entry_fields[DRUN_MATCH_FIELD_KEYWORDS].enabled ) {
1192  // Match against category.
1193  if ( test == tokens[j]->invert ) {
1194  gchar **list = rmpd->entry_list[index].keywords;
1195  for ( int iter = 0; test == tokens[j]->invert && list && list[iter]; iter++ ) {
1196  test = helper_token_match ( ftokens, list[iter] );
1197  }
1198  }
1199  }
1200  if ( matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled ) {
1201  // Match executable name.
1202  if ( test == tokens[j]->invert && rmpd->entry_list[index].comment ) {
1203  test = helper_token_match ( ftokens, rmpd->entry_list[index].comment );
1204  }
1205  }
1206  if ( test == 0 ) {
1207  match = 0;
1208  }
1209  }
1210  }
1211 
1212  return match;
1213 }
1214 
1215 static unsigned int drun_mode_get_num_entries ( const Mode *sw )
1216 {
1217  const DRunModePrivateData *pd = (const DRunModePrivateData *) mode_get_private_data ( sw );
1218  return pd->cmd_list_length;
1219 }
1220 #include "mode-private.h"
1221 Mode drun_mode =
1222 {
1223  .name = "drun",
1224  .cfg_name_key = "display-drun",
1225  ._init = drun_mode_init,
1226  ._get_num_entries = drun_mode_get_num_entries,
1227  ._result = drun_mode_result,
1228  ._destroy = drun_mode_destroy,
1229  ._token_match = drun_token_match,
1230  ._get_completion = drun_get_completion,
1231  ._get_display_value = _get_display_value,
1232  ._get_icon = _get_icon,
1233  ._preprocess_input = NULL,
1234  .private_data = NULL,
1235  .free = NULL
1236 };
1237 
1238 #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
rofi_mode::name
char * name
Definition: mode-private.h:156
settings.h
Settings::drun_reload_desktop_cache
gboolean drun_reload_desktop_cache
Definition: settings.h:202
MARKUP
@ MARKUP
Definition: textbox.h:102
MENU_OK
@ MENU_OK
Definition: mode.h:69
rofi_int_matcher_t
Definition: rofi-types.h:267
Settings::drun_match_fields
char * drun_match_fields
Definition: settings.h:124
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:130
MENU_ENTRY_DELETE
@ MENU_ENTRY_DELETE
Definition: mode.h:77
Settings::drun_url_launcher
char * drun_url_launcher
Definition: settings.h:132
TICK_N
#define TICK_N(a)
Definition: timings.h:69
rofi_icon_fetcher_get
cairo_surface_t * rofi_icon_fetcher_get(const uint32_t uid)
Definition: rofi-icon-fetcher.c:307
rofi_int_matcher_t::invert
gboolean invert
Definition: rofi-types.h:269
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:1295
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
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:995
mode_get_private_data
void * mode_get_private_data(const Mode *mode)
Definition: mode.c:128
MENU_NEXT
@ MENU_NEXT
Definition: mode.h:73
Settings::drun_categories
char * drun_categories
Definition: settings.h:126
Settings::drun_use_desktop_cache
gboolean drun_use_desktop_cache
Definition: settings.h:201
rofi_icon_fetcher_query
uint32_t rofi_icon_fetcher_query(const char *name, const int size)
Definition: rofi-icon-fetcher.c:273
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:451
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:81
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:128