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