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