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