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