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