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