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