rofi  1.6.1
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 2
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_write_integer ( FILE *fd, int32_t val )
738 {
739  fwrite ( &val, sizeof ( val ), 1, fd );
740 }
741 static void drun_read_integer ( FILE *fd, int32_t *type )
742 {
743  if ( fread ( type, sizeof ( int32_t ), 1, fd ) != 1 ) {
744  g_warning ( "Failed to read entry, cache corrupt?" );
745  return;
746  }
747 }
748 static void drun_read_string ( FILE *fd, char **str )
749 {
750  size_t l = 0;
751 
752  if ( fread ( &l, sizeof ( l ), 1, fd ) != 1 ) {
753  g_warning ( "Failed to read entry, cache corrupt?" );
754  return;
755  }
756  ( *str ) = NULL;
757  if ( l > 0 ) {
758  // Include \0
759  l++;
760  ( *str ) = g_malloc ( l );
761  if ( fread ( ( *str ), 1, l, fd ) != l ) {
762  g_warning ( "Failed to read entry, cache corrupt?" );
763  }
764  }
765 }
766 static void drun_write_strv ( FILE *fd, char **str )
767 {
768  guint vl = ( str == NULL ? 0 : g_strv_length ( str ) );
769  fwrite ( &vl, sizeof ( vl ), 1, fd );
770  for ( guint index = 0; index < vl; index++ ) {
771  drun_write_str ( fd, str[index] );
772  }
773 }
774 static void drun_read_stringv ( FILE *fd, char ***str )
775 {
776  guint vl = 0;
777  ( *str ) = NULL;
778  if ( fread ( &vl, sizeof ( vl ), 1, fd ) != 1 ) {
779  g_warning ( "Failed to read entry, cache corrupt?" );
780  return;
781  }
782  if ( vl > 0 ) {
783  // Include terminating NULL entry.
784  ( *str ) = g_malloc0 ( ( vl + 1 ) * sizeof ( **str ) );
785  for ( guint index = 0; index < vl; index++ ) {
786  drun_read_string ( fd, &( ( *str )[index] ) );
787  }
788  }
789 }
790 
791 static void write_cache ( DRunModePrivateData *pd, const char *cache_file )
792 {
793  if ( cache_file == NULL || config.drun_use_desktop_cache == FALSE ) {
794  return;
795  }
796  TICK_N ( "DRUN Write CACHE: start" );
797 
798  FILE *fd = fopen ( cache_file, "w" );
799  if ( fd == NULL ) {
800  g_warning ( "Failed to write to cache file" );
801  return;
802  }
803  uint8_t version = CACHE_VERSION;
804  fwrite ( &version, sizeof ( version ), 1, fd );
805 
806  fwrite ( &( pd->cmd_list_length ), sizeof ( pd->cmd_list_length ), 1, fd );
807  for ( unsigned int index = 0; index < pd->cmd_list_length; index++ ) {
808  DRunModeEntry *entry = &( pd->entry_list[index] );
809 
810  drun_write_str ( fd, entry->action );
811  drun_write_str ( fd, entry->root );
812  drun_write_str ( fd, entry->path );
813  drun_write_str ( fd, entry->app_id );
814  drun_write_str ( fd, entry->desktop_id );
815  drun_write_str ( fd, entry->icon_name );
816  drun_write_str ( fd, entry->exec );
817  drun_write_str ( fd, entry->name );
818  drun_write_str ( fd, entry->generic_name );
819 
820  drun_write_strv ( fd, entry->categories );
821  drun_write_strv ( fd, entry->keywords );
822 
823  drun_write_str ( fd, entry->comment );
824  drun_write_integer ( fd, (int32_t) entry->type );
825  }
826 
827  fclose ( fd );
828  TICK_N ( "DRUN Write CACHE: end" );
829 }
830 
834 static gboolean drun_read_cache ( DRunModePrivateData *pd, const char *cache_file )
835 {
836  if ( cache_file == NULL || config.drun_use_desktop_cache == FALSE ) {
837  return TRUE;
838  }
839 
841  return TRUE;
842  }
843  TICK_N ( "DRUN Read CACHE: start" );
844  FILE *fd = fopen ( cache_file, "r" );
845  if ( fd == NULL ) {
846  TICK_N ( "DRUN Read CACHE: stop" );
847  return TRUE;
848  }
849 
850  // Read version.
851  uint8_t version = 0;
852 
853  if ( fread ( &version, sizeof ( version ), 1, fd ) != 1 ) {
854  fclose ( fd );
855  g_warning ( "Cache corrupt, ignoring." );
856  TICK_N ( "DRUN Read CACHE: stop" );
857  return TRUE;
858  }
859 
860  if ( version != CACHE_VERSION ) {
861  fclose ( fd );
862  g_warning ( "Cache file wrong version, ignoring." );
863  TICK_N ( "DRUN Read CACHE: stop" );
864  return TRUE;
865  }
866 
867  if ( fread ( &( pd->cmd_list_length ), sizeof ( pd->cmd_list_length ), 1, fd ) != 1 ) {
868  fclose ( fd );
869  g_warning ( "Cache corrupt, ignoring." );
870  TICK_N ( "DRUN Read CACHE: stop" );
871  return TRUE;
872  }
873  // set actual length to length;
874  pd->cmd_list_length_actual = pd->cmd_list_length;
875 
876  pd->entry_list = g_malloc0 ( pd->cmd_list_length_actual * sizeof ( *( pd->entry_list ) ) );
877 
878  for ( unsigned int index = 0; index < pd->cmd_list_length; index++ ) {
879  DRunModeEntry *entry = &( pd->entry_list[index] );
880 
881  drun_read_string ( fd, &( entry->action ) );
882  drun_read_string ( fd, &( entry->root ) );
883  drun_read_string ( fd, &( entry->path ) );
884  drun_read_string ( fd, &( entry->app_id ) );
885  drun_read_string ( fd, &( entry->desktop_id ) );
886  drun_read_string ( fd, &( entry->icon_name ) );
887  drun_read_string ( fd, &( entry->exec ) );
888  drun_read_string ( fd, &( entry->name ) );
889  drun_read_string ( fd, &( entry->generic_name ) );
890 
891  drun_read_stringv ( fd, &( entry->categories ) );
892  drun_read_stringv ( fd, &( entry->keywords ) );
893 
894  drun_read_string ( fd, &( entry->comment ) );
895  int32_t type = 0;
896  drun_read_integer ( fd, &( type ) );
897  entry->type = type;
898  }
899 
900  fclose ( fd );
901  TICK_N ( "DRUN Read CACHE: stop" );
902  return FALSE;
903 }
904 
905 static void get_apps ( DRunModePrivateData *pd )
906 {
907  char *cache_file = g_build_filename ( cache_dir, DRUN_DESKTOP_CACHE_FILE, NULL );
908  TICK_N ( "Get Desktop apps (start)" );
909  if ( drun_read_cache ( pd, cache_file ) ) {
910  gchar *dir;
911  // First read the user directory.
912  dir = g_build_filename ( g_get_user_data_dir (), "applications", NULL );
913  walk_dir ( pd, dir, dir );
914  g_free ( dir );
915  TICK_N ( "Get Desktop apps (user dir)" );
916  // Then read thee system data dirs.
917  const gchar * const * sys = g_get_system_data_dirs ();
918  for ( const gchar * const *iter = sys; *iter != NULL; ++iter ) {
919  gboolean unique = TRUE;
920  // Stupid duplicate detection, better then walking dir.
921  for ( const gchar *const *iterd = sys; iterd != iter; ++iterd ) {
922  if ( g_strcmp0 ( *iter, *iterd ) == 0 ) {
923  unique = FALSE;
924  }
925  }
926  // Check, we seem to be getting empty string...
927  if ( unique && ( **iter ) != '\0' ) {
928  dir = g_build_filename ( *iter, "applications", NULL );
929  walk_dir ( pd, dir, dir );
930  g_free ( dir );
931  }
932  }
933  TICK_N ( "Get Desktop apps (system dirs)" );
934  get_apps_history ( pd );
935 
936  g_qsort_with_data ( pd->entry_list, pd->cmd_list_length, sizeof ( DRunModeEntry ), drun_int_sort_list, NULL );
937 
938  TICK_N ( "Sorting done." );
939 
940  write_cache ( pd, cache_file );
941  }
942  g_free ( cache_file );
943 }
944 
945 static void drun_mode_parse_entry_fields ()
946 {
947  char *savept = NULL;
948  // Make a copy, as strtok will modify it.
949  char *switcher_str = g_strdup ( config.drun_match_fields );
950  const char * const sep = ",#";
951  // Split token on ','. This modifies switcher_str.
952  for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
953  matching_entry_fields[i].enabled = FALSE;
954  }
955  for ( char *token = strtok_r ( switcher_str, sep, &savept ); token != NULL;
956  token = strtok_r ( NULL, sep, &savept ) ) {
957  if ( strcmp ( token, "all" ) == 0 ) {
958  for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
959  matching_entry_fields[i].enabled = TRUE;
960  }
961  break;
962  }
963  else {
964  gboolean matched = FALSE;
965  for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) {
966  const char * entry_name = matching_entry_fields[i].entry_field_name;
967  if ( g_ascii_strcasecmp ( token, entry_name ) == 0 ) {
968  matching_entry_fields[i].enabled = TRUE;
969  matched = TRUE;
970  }
971  }
972  if ( !matched ) {
973  g_warning ( "Invalid entry name :%s", token );
974  }
975  }
976  }
977  // Free string that was modified by strtok_r
978  g_free ( switcher_str );
979 }
980 
981 static int drun_mode_init ( Mode *sw )
982 {
983  if ( mode_get_private_data ( sw ) != NULL ) {
984  return TRUE;
985  }
986  DRunModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) );
987  pd->disabled_entries = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
988  mode_set_private_data ( sw, (void *) pd );
989  // current destkop
990  const char *current_desktop = g_getenv ( "XDG_CURRENT_DESKTOP" );
991  pd->current_desktop_list = current_desktop ? g_strsplit ( current_desktop, ":", 0 ) : NULL;
992 
994  pd->show_categories = g_strsplit ( config.drun_categories, ",", 0 );
995  }
996 
997  drun_mode_parse_entry_fields ();
998  get_apps ( pd );
999  return TRUE;
1000 }
1001 static void drun_entry_clear ( DRunModeEntry *e )
1002 {
1003  g_free ( e->root );
1004  g_free ( e->path );
1005  g_free ( e->app_id );
1006  g_free ( e->desktop_id );
1007  if ( e->icon != NULL ) {
1008  cairo_surface_destroy ( e->icon );
1009  }
1010  g_free ( e->icon_name );
1011  g_free ( e->exec );
1012  g_free ( e->name );
1013  g_free ( e->generic_name );
1014  g_free ( e->comment );
1015  if ( e->action != DRUN_GROUP_NAME ) {
1016  g_free ( e->action );
1017  }
1018  g_strfreev ( e->categories );
1019  g_strfreev ( e->keywords );
1020  if ( e->key_file ) {
1021  g_key_file_free ( e->key_file );
1022  }
1023 }
1024 
1025 static ModeMode drun_mode_result ( Mode *sw, int mretv, char **input, unsigned int selected_line )
1026 {
1027  DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( sw );
1028  ModeMode retv = MODE_EXIT;
1029 
1030  if ( ( mretv & MENU_OK ) ) {
1031  switch ( rmpd->entry_list[selected_line].type )
1032  {
1033  case DRUN_DESKTOP_ENTRY_TYPE_APPLICATION:
1034  exec_cmd_entry ( &( rmpd->entry_list[selected_line] ) );
1035  break;
1036  case DRUN_DESKTOP_ENTRY_TYPE_LINK:
1037  launch_link_entry ( &( rmpd->entry_list[selected_line] ) );
1038  break;
1039  default:
1040  g_assert_not_reached ();
1041  }
1042  }
1043  else if ( ( mretv & MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] != '\0' ) {
1044  RofiHelperExecuteContext context = { .name = NULL };
1045  gboolean run_in_term = ( ( mretv & MENU_CUSTOM_ACTION ) == MENU_CUSTOM_ACTION );
1046  // FIXME: We assume startup notification in terminals, not in others
1047  if ( !helper_execute_command ( NULL, *input, run_in_term, run_in_term ? &context : NULL ) ) {
1048  retv = RELOAD_DIALOG;
1049  }
1050  }
1051  else if ( ( mretv & MENU_ENTRY_DELETE ) && selected_line < rmpd->cmd_list_length ) {
1052  // Possitive sort index means it is in history.
1053  if ( rmpd->entry_list[selected_line].sort_index >= 0 ) {
1054  delete_entry_history ( &( rmpd->entry_list[selected_line] ) );
1055  drun_entry_clear ( &( rmpd->entry_list[selected_line] ) );
1056  memmove ( &( rmpd->entry_list[selected_line] ), &rmpd->entry_list[selected_line + 1],
1057  sizeof ( DRunModeEntry ) * ( rmpd->cmd_list_length - selected_line - 1 ) );
1058  rmpd->cmd_list_length--;
1059  }
1060  retv = RELOAD_DIALOG;
1061  }
1062  return retv;
1063 }
1064 static void drun_mode_destroy ( Mode *sw )
1065 {
1066  DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( sw );
1067  if ( rmpd != NULL ) {
1068  for ( size_t i = 0; i < rmpd->cmd_list_length; i++ ) {
1069  drun_entry_clear ( &( rmpd->entry_list[i] ) );
1070  }
1071  g_hash_table_destroy ( rmpd->disabled_entries );
1072  g_free ( rmpd->entry_list );
1073 
1074  g_strfreev ( rmpd->current_desktop_list );
1075  g_strfreev ( rmpd->show_categories );
1076  g_free ( rmpd );
1077  mode_set_private_data ( sw, NULL );
1078  }
1079 }
1080 
1081 static char *_get_display_value ( const Mode *sw, unsigned int selected_line, int *state, G_GNUC_UNUSED GList **list, int get_entry )
1082 {
1083  DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw );
1084  *state |= MARKUP;
1085  if ( !get_entry ) {
1086  return NULL;
1087  }
1088  if ( pd->entry_list == NULL ) {
1089  // Should never get here.
1090  return g_strdup ( "Failed" );
1091  }
1092  /* Free temp storage. */
1093  DRunModeEntry *dr = &( pd->entry_list[selected_line] );
1094  gchar *cats = NULL;
1095  if ( dr->categories ) {
1096  char *tcats = g_strjoinv ( ",", dr->categories );
1097  if ( tcats ) {
1098  cats = g_markup_escape_text ( tcats, -1 );
1099  g_free ( tcats );
1100  }
1101  }
1102  gchar *keywords = NULL;
1103  if ( dr->keywords ) {
1104  char *tkeyw = g_strjoinv ( ",", dr->keywords );
1105  if ( tkeyw ) {
1106  keywords = g_markup_escape_text ( tkeyw, -1 );
1107  g_free ( tkeyw );
1108  }
1109  }
1110  // Needed for display.
1111  char *egn = NULL;
1112  char *en = NULL;
1113  char *ec = NULL;
1114  if ( dr->generic_name ) {
1115  egn = g_markup_escape_text ( dr->generic_name, -1 );
1116  }
1117  if ( dr->name ) {
1118  en = g_markup_escape_text ( dr->name, -1 );
1119  }
1120  if ( dr->comment ) {
1121  ec = g_markup_escape_text ( dr->comment, -1 );
1122  }
1123 
1125  "{generic}", egn,
1126  "{name}", en,
1127  "{comment}", ec,
1128  "{exec}", dr->exec,
1129  "{categories}", cats,
1130  "{keywords}", keywords,
1131  NULL );
1132  g_free ( egn );
1133  g_free ( en );
1134  g_free ( ec );
1135  g_free ( cats );
1136  return retv;
1137 }
1138 
1139 static cairo_surface_t *_get_icon ( const Mode *sw, unsigned int selected_line, int height )
1140 {
1141  DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw );
1142  g_return_val_if_fail ( pd->entry_list != NULL, NULL );
1143  DRunModeEntry *dr = &( pd->entry_list[selected_line] );
1144  if ( dr->icon_name == NULL ) {
1145  return NULL;
1146  }
1147  if ( dr->icon_fetch_uid > 0 ) {
1148  return rofi_icon_fetcher_get ( dr->icon_fetch_uid );
1149  }
1150  dr->icon_fetch_uid = rofi_icon_fetcher_query ( dr->icon_name, height );
1151  return rofi_icon_fetcher_get ( dr->icon_fetch_uid );
1152 }
1153 
1154 static char *drun_get_completion ( const Mode *sw, unsigned int index )
1155 {
1156  DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw );
1157  /* Free temp storage. */
1158  DRunModeEntry *dr = &( pd->entry_list[index] );
1159  if ( dr->generic_name == NULL ) {
1160  return g_strdup ( dr->name );
1161  }
1162  else {
1163  return g_strdup_printf ( "%s", dr->name );
1164  }
1165 }
1166 
1167 static int drun_token_match ( const Mode *data, rofi_int_matcher **tokens, unsigned int index )
1168 {
1169  DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( data );
1170  int match = 1;
1171  if ( tokens ) {
1172  for ( int j = 0; match && tokens != NULL && tokens[j] != NULL; j++ ) {
1173  int test = 0;
1174  rofi_int_matcher *ftokens[2] = { tokens[j], NULL };
1175  // Match name
1176  if ( matching_entry_fields[DRUN_MATCH_FIELD_NAME].enabled ) {
1177  if ( rmpd->entry_list[index].name ) {
1178  test = helper_token_match ( ftokens, rmpd->entry_list[index].name );
1179  }
1180  }
1181  if ( matching_entry_fields[DRUN_MATCH_FIELD_GENERIC].enabled ) {
1182  // Match generic name
1183  if ( test == tokens[j]->invert && rmpd->entry_list[index].generic_name ) {
1184  test = helper_token_match ( ftokens, rmpd->entry_list[index].generic_name );
1185  }
1186  }
1187  if ( matching_entry_fields[DRUN_MATCH_FIELD_EXEC].enabled ) {
1188  // Match executable name.
1189  if ( test == tokens[j]->invert && rmpd->entry_list[index].exec ) {
1190  test = helper_token_match ( ftokens, rmpd->entry_list[index].exec );
1191  }
1192  }
1193  if ( matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled ) {
1194  // Match against category.
1195  if ( test == tokens[j]->invert ) {
1196  gchar **list = rmpd->entry_list[index].categories;
1197  for ( int iter = 0; test == tokens[j]->invert && list && list[iter]; iter++ ) {
1198  test = helper_token_match ( ftokens, list[iter] );
1199  }
1200  }
1201  }
1202  if ( matching_entry_fields[DRUN_MATCH_FIELD_KEYWORDS].enabled ) {
1203  // Match against category.
1204  if ( test == tokens[j]->invert ) {
1205  gchar **list = rmpd->entry_list[index].keywords;
1206  for ( int iter = 0; test == tokens[j]->invert && list && list[iter]; iter++ ) {
1207  test = helper_token_match ( ftokens, list[iter] );
1208  }
1209  }
1210  }
1211  if ( matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled ) {
1212  // Match executable name.
1213  if ( test == tokens[j]->invert && rmpd->entry_list[index].comment ) {
1214  test = helper_token_match ( ftokens, rmpd->entry_list[index].comment );
1215  }
1216  }
1217  if ( test == 0 ) {
1218  match = 0;
1219  }
1220  }
1221  }
1222 
1223  return match;
1224 }
1225 
1226 static unsigned int drun_mode_get_num_entries ( const Mode *sw )
1227 {
1228  const DRunModePrivateData *pd = (const DRunModePrivateData *) mode_get_private_data ( sw );
1229  return pd->cmd_list_length;
1230 }
1231 #include "mode-private.h"
1232 Mode drun_mode =
1233 {
1234  .name = "drun",
1235  .cfg_name_key = "display-drun",
1236  ._init = drun_mode_init,
1237  ._get_num_entries = drun_mode_get_num_entries,
1238  ._result = drun_mode_result,
1239  ._destroy = drun_mode_destroy,
1240  ._token_match = drun_token_match,
1241  ._get_completion = drun_get_completion,
1242  ._get_display_value = _get_display_value,
1243  ._get_icon = _get_icon,
1244  ._preprocess_input = NULL,
1245  .private_data = NULL,
1246  .free = NULL
1247 };
1248 
1249 #endif // ENABLE_DRUN
static cairo_surface_t * _get_icon(const Mode *sw, unsigned int selected_line, int height)
Definition: filebrowser.c:351
static char * _get_display_value(const Mode *sw, unsigned int selected_line, G_GNUC_UNUSED int *state, G_GNUC_UNUSED GList **attr_list, int get_entry)
Definition: filebrowser.c:312
const char * icon_name[NUM_FILE_TYPES]
Definition: filebrowser.c:61
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
Definition: helper.c:1042
char * helper_string_replace_if_exists(char *string,...)
Definition: helper.c:1298
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition: helper.c:488
void history_set(const char *filename, const char *entry)
Definition: history.c:178
void history_remove(const char *filename, const char *entry)
Definition: history.c:259
char ** history_get_list(const char *filename, unsigned int *length)
Definition: history.c:325
cairo_surface_t * rofi_icon_fetcher_get(const uint32_t uid)
uint32_t rofi_icon_fetcher_query(const char *name, const int size)
void mode_set_private_data(Mode *mode, void *pd)
Definition: mode.c:145
void * mode_get_private_data(const Mode *mode)
Definition: mode.c:139
ModeMode
Definition: mode.h:50
@ MENU_ENTRY_DELETE
Definition: mode.h:77
@ MENU_CUSTOM_ACTION
Definition: mode.h:85
@ MENU_OK
Definition: mode.h:69
@ MENU_CUSTOM_INPUT
Definition: mode.h:75
@ MODE_EXIT
Definition: mode.h:52
@ RELOAD_DIALOG
Definition: mode.h:56
const char * cache_dir
Definition: rofi.c:84
#define TICK_N(a)
Definition: timings.h:69
@ MARKUP
Definition: textbox.h:114
struct _icon icon
Definition: icon.h:44
static void get_apps(KeysHelpModePrivateData *pd)
Definition: help-keys.c:54
Settings config
const gchar * wmclass
Definition: helper.h:279
const gchar * name
Definition: helper.h:269
gboolean drun_reload_desktop_cache
Definition: settings.h:202
char * drun_match_fields
Definition: settings.h:124
char * drun_url_launcher
Definition: settings.h:132
unsigned int drun_show_actions
Definition: settings.h:128
char * drun_display_format
Definition: settings.h:130
char * drun_categories
Definition: settings.h:126
gboolean show_icons
Definition: settings.h:81
gboolean drun_use_desktop_cache
Definition: settings.h:201
char * name
Definition: mode-private.h:156