29#define G_LOG_DOMAIN "Modes.DRun"
64#include <gio-unix-2.0/gio/gdesktopappinfo.h>
67#define DRUN_CACHE_FILE "rofi3.druncache"
70#define DRUN_DESKTOP_CACHE_FILE "rofi-drun-desktop.cache"
73#define DRUN_MAX_STRING_LENGTH (10 * 1024 * 1024)
75#define DRUN_MAX_NUM_ENTRIES (256 * 1024)
78char *DRUN_GROUP_NAME =
"Desktop Entry";
83typedef struct _DRunModePrivateData DRunModePrivateData;
90 DRUN_DESKTOP_ENTRY_TYPE_UNDETERMINED = 0,
92 DRUN_DESKTOP_ENTRY_TYPE_APPLICATION,
94 DRUN_DESKTOP_ENTRY_TYPE_LINK,
96 DRUN_DESKTOP_ENTRY_TYPE_SERVICE,
98 DRUN_DESKTOP_ENTRY_TYPE_DIRECTORY,
99} DRunDesktopEntryType;
106 DRunModePrivateData *pd;
125 cairo_surface_t *
icon;
145 uint32_t icon_fetch_uid;
146 uint32_t icon_fetch_size;
147 guint icon_fetch_scale;
151 DRunDesktopEntryType type;
155 const char *entry_field_name;
156 gboolean enabled_match;
157 gboolean enabled_display;
163 DRUN_MATCH_FIELD_NAME,
165 DRUN_MATCH_FIELD_GENERIC,
167 DRUN_MATCH_FIELD_EXEC,
169 DRUN_MATCH_FIELD_CATEGORIES,
171 DRUN_MATCH_FIELD_KEYWORDS,
173 DRUN_MATCH_FIELD_COMMENT,
175 DRUN_MATCH_FIELD_URL,
177 DRUN_MATCH_NUM_FIELDS,
182static DRunEntryField matching_entry_fields[DRUN_MATCH_NUM_FIELDS] = {
184 .entry_field_name =
"name",
185 .enabled_match = TRUE,
186 .enabled_display = TRUE,
189 .entry_field_name =
"generic",
190 .enabled_match = TRUE,
191 .enabled_display = TRUE,
194 .entry_field_name =
"exec",
195 .enabled_match = TRUE,
196 .enabled_display = TRUE,
199 .entry_field_name =
"categories",
200 .enabled_match = TRUE,
201 .enabled_display = TRUE,
204 .entry_field_name =
"keywords",
205 .enabled_match = TRUE,
206 .enabled_display = TRUE,
209 .entry_field_name =
"comment",
210 .enabled_match = FALSE,
211 .enabled_display = FALSE,
214 .entry_field_name =
"url",
215 .enabled_match = FALSE,
216 .enabled_display = FALSE,
219struct _DRunModePrivateData {
220 DRunModeEntry *entry_list;
221 unsigned int cmd_list_length;
222 unsigned int cmd_list_length_actual;
224 GHashTable *disabled_entries;
225 unsigned int disabled_entries_length;
226 unsigned int expected_line_height;
228 char **show_categories;
229 char **exclude_categories;
232 const gchar *icon_theme;
234 gchar **current_desktop_list;
236 gboolean file_complete;
238 char *old_completer_input;
239 uint32_t selected_line;
243 gboolean disable_giolaunch;
251static void drun_entry_clear(DRunModeEntry *e);
253static gboolean drun_helper_eval_cb(
const GMatchInfo *info, GString *res,
256 struct RegexEvalArg *e = (
struct RegexEvalArg *)data;
260 match = g_match_info_fetch(info, 0);
268 g_string_append(res, e->path);
274 if (e->e && e->e->icon) {
275 g_string_append_printf(res,
"--icon %s", e->e->icon_name);
287 g_string_append(res,
"%");
291 char *esc = g_shell_quote(e->e->path);
292 g_string_append(res, esc);
298 char *esc = g_shell_quote(e->e->name);
299 g_string_append(res, esc);
314static void launch_link_entry(DRunModeEntry *e) {
315 if (e->key_file == NULL) {
316 GKeyFile *kf = g_key_file_new();
317 GError *error = NULL;
318 gboolean res = g_key_file_load_from_file(kf, e->path, 0, &error);
322 g_warning(
"[%s] [%s] Failed to parse desktop file because: %s.",
323 e->app_id, e->path, error->message);
330 gchar *url = g_key_file_get_string(e->key_file, e->action,
"URL", NULL);
331 if (url == NULL || strlen(url) == 0) {
332 g_warning(
"[%s] [%s] No URL found.", e->app_id, e->path);
339 gchar *command = g_newa(gchar, command_len);
343 g_debug(
"Link launch command: |%s|", command);
345 char *path = g_build_filename(
cache_dir, DRUN_CACHE_FILE, NULL);
352static void exec_cmd_entry(DRunModePrivateData *pd, DRunModeEntry *e,
354 GError *error = NULL;
355 GRegex *reg = g_regex_new(
"%[a-zA-Z%]", 0, 0, &error);
357 g_warning(
"Internal error, failed to create regex: %s.", error->message);
361 struct RegexEvalArg earg = {.e = e, .path = path, .success = TRUE};
362 char *str = g_regex_replace_eval(reg, e->exec, -1, 0, 0, drun_helper_eval_cb,
365 g_warning(
"Internal error, failed replace field codes: %s.",
371 if (earg.success == FALSE) {
372 g_warning(
"Invalid field code in Exec line: %s.", e->exec);
377 g_warning(
"Nothing to execute after processing: %s.", e->exec);
381 g_debug(
"Parsed command: |%s| into |%s|.", e->exec, str);
383 if (e->key_file == NULL) {
384 GKeyFile *kf = g_key_file_new();
385 GError *key_error = NULL;
386 gboolean res = g_key_file_load_from_file(kf, e->path, 0, &key_error);
390 g_warning(
"[%s] [%s] Failed to parse desktop file because: %s.",
391 e->app_id, e->path, key_error->message);
392 g_error_free(key_error);
399 const gchar *fp = g_strstrip(str);
401 g_key_file_get_string(e->key_file, e->action,
"Path", NULL);
402 if (exec_path != NULL && strlen(exec_path) == 0) {
410 .icon = e->icon_name,
411 .app_id = e->desktop_id,
414 g_key_file_get_boolean(e->key_file, e->action,
"StartupNotify", NULL);
415 gchar *wmclass = NULL;
417 g_key_file_has_key(e->key_file, e->action,
"StartupWMClass", NULL)) {
419 g_key_file_get_string(e->key_file, e->action,
"StartupWMClass", NULL);
422 gboolean launched = FALSE;
424 if (pd->disable_giolaunch == FALSE) {
425 GDesktopAppInfo *gdai = g_desktop_app_info_new_from_keyfile(e->key_file);
428 GError *ai_error = NULL;
431 GFile *file = g_file_new_for_path(path);
432 files = g_list_append(files, (
void *)file);
435 g_desktop_app_info_launch_action(gdai, e->action_name, NULL);
438 launched = g_app_info_launch(G_APP_INFO(gdai), files, NULL, &ai_error);
441 g_warning(
"Failed to launch application using GAppInfo::Launch: %s",
443 g_error_free(ai_error);
445 g_object_unref(gdai);
447 g_list_free_full(files, g_object_unref);
450 g_warning(
"Failed to create GDesktopAppInfo for: %s", e->path);
454 if (launched == FALSE) {
455 gchar **envp = g_get_environ();
456 guint envp_l = g_strv_length(envp);
457 envp = g_realloc(envp,
sizeof(
char *) * (envp_l + 6));
458 envp[envp_l++] = g_strdup_printf(
"DESKTOP_ENTRY_ID=%s", e->desktop_id);
459 envp[envp_l++] = g_strdup_printf(
"DESKTOP_ENTRY_PATH=%s", e->path);
460 envp[envp_l++] = g_strdup_printf(
"DESKTOP_ENTRY_NAME=%s", e->name);
461 envp[envp_l++] = g_strdup_printf(
"DESKTOP_ENTRY_NAME_L=%s", e->name);
463 if (e->generic_name) {
465 g_strdup_printf(
"DESKTOP_ENTRY_GENERICNAME_L=%s", e->generic_name);
467 envp[envp_l++] = NULL;
472 g_key_file_get_boolean(e->key_file, e->action,
"Terminal", NULL);
474 sn ? &context : NULL, envp);
478 if (launched == TRUE) {
479 char *drun_cach_path = g_build_filename(
cache_dir, DRUN_CACHE_FILE, NULL);
482 g_free(drun_cach_path);
489static gboolean rofi_strv_contains(
const char *
const *categories,
490 const char *
const *field) {
491 for (
int i = 0; categories && categories[i]; i++) {
492 for (
int j = 0; field[j]; j++) {
493 if (g_str_equal(categories[i], field[j])) {
503static void read_desktop_file(DRunModePrivateData *pd,
const char *root,
504 const char *path,
const gchar *basename,
505 const char *action,
const char *action_name) {
506 DRunDesktopEntryType desktop_entry_type =
507 DRUN_DESKTOP_ENTRY_TYPE_UNDETERMINED;
511 const ssize_t id_len = strlen(path) - strlen(root);
513 g_strlcpy(
id, &(path[strlen(root) + 1]), id_len);
514 for (
int index = 0; index < id_len; index++) {
515 if (
id[index] ==
'/') {
521 if (g_hash_table_contains(pd->disabled_entries,
id) && !parse_action) {
522 g_debug(
"[%s] [%s] Skipping, was previously seen.",
id, path);
525 GKeyFile *kf = g_key_file_new();
526 GError *error = NULL;
527 gboolean res = g_key_file_load_from_file(kf, path, 0, &error);
530 g_debug(
"[%s] [%s] Failed to parse desktop file because: %s.",
id, path,
537 if (g_key_file_has_group(kf, action) == FALSE) {
539 g_debug(
"[%s] [%s] Invalid desktop file: No %s group",
id, path, action);
544 gchar *key = g_key_file_get_string(kf, DRUN_GROUP_NAME,
"Type", NULL);
547 g_debug(
"[%s] [%s] Invalid desktop file: No type indicated",
id, path);
551 if (!g_strcmp0(key,
"Application")) {
552 desktop_entry_type = DRUN_DESKTOP_ENTRY_TYPE_APPLICATION;
553 }
else if (!g_strcmp0(key,
"Link")) {
554 desktop_entry_type = DRUN_DESKTOP_ENTRY_TYPE_LINK;
555 }
else if (!g_strcmp0(key,
"Service")) {
556 desktop_entry_type = DRUN_DESKTOP_ENTRY_TYPE_SERVICE;
557 g_debug(
"Service file detected.");
560 "[%s] [%s] Skipping desktop file: Not of type Application or Link (%s)",
569 if (!g_key_file_has_key(kf, DRUN_GROUP_NAME,
"Name", NULL)) {
570 g_debug(
"[%s] [%s] Invalid desktop file: no 'Name' key present.",
id, path);
576 if (g_key_file_get_boolean(kf, DRUN_GROUP_NAME,
"Hidden", NULL)) {
578 "[%s] [%s] Adding desktop file to disabled list: 'Hidden' key is true",
581 g_hash_table_add(pd->disabled_entries, g_strdup(
id));
584 if (pd->current_desktop_list) {
585 gboolean show = TRUE;
587 if (g_key_file_has_key(kf, DRUN_GROUP_NAME,
"OnlyShowIn", NULL)) {
590 gchar **list = g_key_file_get_string_list(kf, DRUN_GROUP_NAME,
591 "OnlyShowIn", &llength, NULL);
593 for (gsize lcd = 0; !show && pd->current_desktop_list[lcd]; lcd++) {
594 for (gsize lle = 0; !show && lle < llength; lle++) {
595 show = (g_strcmp0(pd->current_desktop_list[lcd], list[lle]) == 0);
601 if (show && g_key_file_has_key(kf, DRUN_GROUP_NAME,
"NotShowIn", NULL)) {
603 gchar **list = g_key_file_get_string_list(kf, DRUN_GROUP_NAME,
604 "NotShowIn", &llength, NULL);
606 for (gsize lcd = 0; show && pd->current_desktop_list[lcd]; lcd++) {
607 for (gsize lle = 0; show && lle < llength; lle++) {
608 show = !(g_strcmp0(pd->current_desktop_list[lcd], list[lle]) == 0);
616 g_debug(
"[%s] [%s] Adding desktop file to disabled list: "
617 "'OnlyShowIn'/'NotShowIn' keys don't match current desktop",
620 g_hash_table_add(pd->disabled_entries, g_strdup(
id));
625 if (g_key_file_get_boolean(kf, DRUN_GROUP_NAME,
"NoDisplay", NULL)) {
626 g_debug(
"[%s] [%s] Adding desktop file to disabled list: 'NoDisplay' key "
630 g_hash_table_add(pd->disabled_entries, g_strdup(
id));
635 if (desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_APPLICATION &&
636 !g_key_file_has_key(kf, DRUN_GROUP_NAME,
"Exec", NULL)) {
637 g_debug(
"[%s] [%s] Unsupported desktop file: no 'Exec' key present for "
643 if (desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_SERVICE &&
644 !g_key_file_has_key(kf, DRUN_GROUP_NAME,
"Exec", NULL)) {
645 g_debug(
"[%s] [%s] Unsupported desktop file: no 'Exec' key present for "
651 if (desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_LINK &&
652 !g_key_file_has_key(kf, DRUN_GROUP_NAME,
"URL", NULL)) {
653 g_debug(
"[%s] [%s] Unsupported desktop file: no 'URL' key present for type "
660 if (g_key_file_has_key(kf, DRUN_GROUP_NAME,
"TryExec", NULL)) {
661 char *te = g_key_file_get_string(kf, DRUN_GROUP_NAME,
"TryExec", NULL);
662 if (!g_path_is_absolute(te)) {
663 char *fp = g_find_program_in_path(te);
671 if (g_file_test(te, G_FILE_TEST_IS_EXECUTABLE) == FALSE) {
680 char **categories = NULL;
681 if (pd->show_categories) {
682 categories = g_key_file_get_locale_string_list(
683 kf, DRUN_GROUP_NAME,
"Categories", NULL, NULL, NULL);
684 if (!rofi_strv_contains((
const char *
const *)categories,
685 (
const char *
const *)pd->show_categories)) {
686 g_strfreev(categories);
692 if (pd->exclude_categories) {
693 if (categories == NULL) {
694 categories = g_key_file_get_locale_string_list(
695 kf, DRUN_GROUP_NAME,
"Categories", NULL, NULL, NULL);
697 if (rofi_strv_contains((
const char *
const *)categories,
698 (
const char *
const *)pd->exclude_categories)) {
699 g_strfreev(categories);
705 size_t nl = ((pd->cmd_list_length) + 1);
706 if (nl >= pd->cmd_list_length_actual) {
707 pd->cmd_list_length_actual += 256;
708 pd->entry_list = g_realloc(pd->entry_list, pd->cmd_list_length_actual *
709 sizeof(*(pd->entry_list)));
713 if (G_UNLIKELY(pd->cmd_list_length > INT_MAX)) {
715 pd->entry_list[pd->cmd_list_length].sort_index = INT_MIN;
717 pd->entry_list[pd->cmd_list_length].sort_index = -nl;
719 pd->entry_list[pd->cmd_list_length].icon_size = 0;
720 pd->entry_list[pd->cmd_list_length].icon_fetch_uid = 0;
721 pd->entry_list[pd->cmd_list_length].icon_fetch_size = 0;
722 pd->entry_list[pd->cmd_list_length].icon_fetch_scale = 0;
723 pd->entry_list[pd->cmd_list_length].root = g_strdup(root);
724 pd->entry_list[pd->cmd_list_length].path = g_strdup(path);
725 pd->entry_list[pd->cmd_list_length].desktop_id = g_strdup(
id);
726 pd->entry_list[pd->cmd_list_length].is_action = parse_action;
727 pd->entry_list[pd->cmd_list_length].action_name = g_strdup(action_name);
728 pd->entry_list[pd->cmd_list_length].app_id =
729 g_strndup(basename, strlen(basename) - strlen(
".desktop"));
731 g_key_file_get_locale_string(kf, DRUN_GROUP_NAME,
"Name", NULL, NULL);
733 if (action != DRUN_GROUP_NAME) {
734 gchar *na = g_key_file_get_locale_string(kf, action,
"Name", NULL, NULL);
735 gchar *l = g_strdup_printf(
"%s - %s", n, na);
739 pd->entry_list[pd->cmd_list_length].name = n;
740 pd->entry_list[pd->cmd_list_length].action = DRUN_GROUP_NAME;
741 gchar *gn = g_key_file_get_locale_string(kf, DRUN_GROUP_NAME,
"GenericName",
743 pd->entry_list[pd->cmd_list_length].generic_name = gn;
744 if (matching_entry_fields[DRUN_MATCH_FIELD_KEYWORDS].enabled_match ||
745 matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled_display) {
746 pd->entry_list[pd->cmd_list_length].keywords =
747 g_key_file_get_locale_string_list(kf, DRUN_GROUP_NAME,
"Keywords", NULL,
750 pd->entry_list[pd->cmd_list_length].keywords = NULL;
753 if (matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled_match ||
754 matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled_display) {
756 pd->entry_list[pd->cmd_list_length].categories = categories;
759 pd->entry_list[pd->cmd_list_length].categories =
760 g_key_file_get_locale_string_list(kf, DRUN_GROUP_NAME,
"Categories",
764 pd->entry_list[pd->cmd_list_length].categories = NULL;
766 g_strfreev(categories);
768 pd->entry_list[pd->cmd_list_length].type = desktop_entry_type;
769 if (desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_APPLICATION ||
770 desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_SERVICE) {
771 pd->entry_list[pd->cmd_list_length].exec =
772 g_key_file_get_string(kf, action,
"Exec", NULL);
774 pd->entry_list[pd->cmd_list_length].exec = NULL;
777 if (matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled_match ||
778 matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled_display) {
779 pd->entry_list[pd->cmd_list_length].comment = g_key_file_get_locale_string(
780 kf, DRUN_GROUP_NAME,
"Comment", NULL, NULL);
782 pd->entry_list[pd->cmd_list_length].comment = NULL;
784 if (matching_entry_fields[DRUN_MATCH_FIELD_URL].enabled_match ||
785 matching_entry_fields[DRUN_MATCH_FIELD_URL].enabled_display) {
786 pd->entry_list[pd->cmd_list_length].url =
787 g_key_file_get_locale_string(kf, DRUN_GROUP_NAME,
"URL", NULL, NULL);
789 pd->entry_list[pd->cmd_list_length].url = NULL;
791 pd->entry_list[pd->cmd_list_length].icon_name =
792 g_key_file_get_locale_string(kf, DRUN_GROUP_NAME,
"Icon", NULL, NULL);
793 pd->entry_list[pd->cmd_list_length].icon = NULL;
796 pd->entry_list[pd->cmd_list_length].key_file = kf;
798 g_hash_table_add(pd->disabled_entries, g_strdup(
id));
799 g_debug(
"[%s] Using file %s.",
id, path);
800 (pd->cmd_list_length)++;
803 gsize actions_length = 0;
804 char **actions = g_key_file_get_string_list(kf, DRUN_GROUP_NAME,
"Actions",
805 &actions_length, NULL);
806 for (gsize iter = 0; iter < actions_length; iter++) {
807 char *new_action = g_strdup_printf(
"Desktop Action %s", actions[iter]);
808 read_desktop_file(pd, root, path, basename, new_action, actions[iter]);
819static void walk_dir(DRunModePrivateData *pd,
const char *root,
820 const char *dirname,
const gboolean recursive) {
823 g_debug(
"Checking directory %s for desktop files.", dirname);
824 dir = opendir(dirname);
830 gchar *filename = NULL;
832 while ((file = readdir(dir)) != NULL) {
833 if (file->d_name[0] ==
'.') {
836 switch (file->d_type) {
841 filename = g_build_filename(dirname, file->d_name, NULL);
849 if (file->d_type == DT_LNK || file->d_type == DT_UNKNOWN) {
850 file->d_type = DT_UNKNOWN;
851 if (stat(filename, &st) == 0) {
852 if (S_ISDIR(st.st_mode)) {
853 file->d_type = DT_DIR;
854 }
else if (S_ISREG(st.st_mode)) {
855 file->d_type = DT_REG;
860 switch (file->d_type) {
863 if (g_str_has_suffix(file->d_name,
".desktop")) {
864 read_desktop_file(pd, root, filename, file->d_name, DRUN_GROUP_NAME,
870 walk_dir(pd, root, filename, recursive);
885static void delete_entry_history(
const DRunModeEntry *entry) {
886 char *path = g_build_filename(
cache_dir, DRUN_CACHE_FILE, NULL);
891static void get_apps_history(DRunModePrivateData *pd) {
892 TICK_N(
"Start drun history");
893 unsigned int length = 0;
894 gchar *path = g_build_filename(
cache_dir, DRUN_CACHE_FILE, NULL);
896 for (
unsigned int index = 0; index < length; index++) {
897 for (
size_t i = 0; i < pd->cmd_list_length; i++) {
898 if (g_strcmp0(pd->entry_list[i].desktop_id, retv[index]) == 0) {
899 unsigned int sort_index = length - index;
900 if (G_LIKELY(sort_index < INT_MAX)) {
901 pd->entry_list[i].sort_index = sort_index;
904 pd->entry_list[i].sort_index = INT_MAX;
911 TICK_N(
"Stop drun history");
914static gint drun_int_sort_list(gconstpointer a, gconstpointer b,
915 G_GNUC_UNUSED gpointer user_data) {
916 DRunModeEntry *da = (DRunModeEntry *)a;
917 DRunModeEntry *db = (DRunModeEntry *)b;
919 if (da->sort_index < 0 && db->sort_index < 0) {
920 if (da->name == NULL && db->name == NULL) {
923 if (da->name == NULL) {
926 if (db->name == NULL) {
929 return g_utf8_collate(da->name, db->name);
931 return db->sort_index - da->sort_index;
939#define CACHE_VERSION 3
940static void drun_write_str(FILE *fd,
const char *str) {
941 size_t l = (str == NULL ? 0 : strlen(str));
942 fwrite(&l,
sizeof(l), 1, fd);
946 fwrite(str, 1, l + 1, fd);
949static void drun_write_integer(FILE *fd, int32_t val) {
950 fwrite(&val,
sizeof(val), 1, fd);
952static gboolean drun_read_integer(FILE *fd, int32_t *type) {
953 if (fread(type,
sizeof(int32_t), 1, fd) != 1) {
954 g_warning(
"Failed to read entry, cache corrupt?");
959static gboolean drun_read_string(FILE *fd,
char **str) {
962 if (fread(&l,
sizeof(l), 1, fd) != 1) {
963 g_warning(
"Failed to read entry, cache corrupt?");
968 if (l > DRUN_MAX_STRING_LENGTH) {
973 (*str) = g_malloc(l);
974 if (fread((*str), 1, l, fd) != l) {
975 g_warning(
"Failed to read entry, cache corrupt?");
981static void drun_write_strv(FILE *fd,
char **str) {
982 guint vl = (str == NULL ? 0 : g_strv_length(str));
983 fwrite(&vl,
sizeof(vl), 1, fd);
984 for (guint index = 0; index < vl; index++) {
985 drun_write_str(fd, str[index]);
988static gboolean drun_read_stringv(FILE *fd,
char ***str) {
991 if (fread(&vl,
sizeof(vl), 1, fd) != 1) {
992 g_warning(
"Failed to read entry, cache corrupt?");
998 if (vl >= UINT16_MAX) {
999 g_warning(
"Array of strings vector is to long.");
1002 size_t ss =
sizeof(
char **) * (vl + 1);
1004 (*str) = g_malloc0(ss);
1005 for (guint index = 0; index < vl; index++) {
1006 if (drun_read_string(fd, &((*str)[index]))) {
1013static void write_cache(DRunModePrivateData *pd,
const char *cache_file) {
1017 TICK_N(
"DRUN Write CACHE: start");
1019 FILE *fd = fopen(cache_file,
"w");
1021 g_warning(
"Failed to write to cache file");
1024 uint8_t version = CACHE_VERSION;
1025 fwrite(&version,
sizeof(version), 1, fd);
1027 fwrite(&(pd->cmd_list_length),
sizeof(pd->cmd_list_length), 1, fd);
1028 for (
unsigned int index = 0; index < pd->cmd_list_length; index++) {
1029 DRunModeEntry *entry = &(pd->entry_list[index]);
1031 drun_write_str(fd, entry->action);
1032 drun_write_str(fd, entry->root);
1033 drun_write_str(fd, entry->path);
1034 drun_write_str(fd, entry->app_id);
1035 drun_write_str(fd, entry->desktop_id);
1036 drun_write_str(fd, entry->icon_name);
1037 drun_write_str(fd, entry->exec);
1038 drun_write_str(fd, entry->name);
1039 drun_write_str(fd, entry->generic_name);
1041 drun_write_strv(fd, entry->categories);
1042 drun_write_strv(fd, entry->keywords);
1044 drun_write_str(fd, entry->comment);
1045 drun_write_str(fd, entry->url);
1046 drun_write_integer(fd, (int32_t)entry->type);
1050 TICK_N(
"DRUN Write CACHE: end");
1056static gboolean drun_read_cache(DRunModePrivateData *pd,
1057 const char *cache_file) {
1065 TICK_N(
"DRUN Read CACHE: start");
1066 FILE *fd = fopen(cache_file,
"r");
1068 TICK_N(
"DRUN Read CACHE: stop");
1073 uint8_t version = 0;
1075 if (fread(&version,
sizeof(version), 1, fd) != 1) {
1077 g_warning(
"Cache corrupt, ignoring.");
1078 TICK_N(
"DRUN Read CACHE: stop");
1082 if (version != CACHE_VERSION) {
1084 g_warning(
"Cache file wrong version, ignoring.");
1085 TICK_N(
"DRUN Read CACHE: stop");
1089 if (fread(&(pd->cmd_list_length),
sizeof(pd->cmd_list_length), 1, fd) != 1) {
1091 g_warning(
"Cache corrupt, ignoring.");
1092 TICK_N(
"DRUN Read CACHE: stop");
1096 pd->cmd_list_length_actual = pd->cmd_list_length;
1099 gsize newsize =
sizeof(DRunModeEntry) * pd->cmd_list_length;
1100 if ((DRUN_MAX_NUM_ENTRIES *
sizeof(DRunModeEntry) < newsize)) {
1102 g_warning(
"Cache has to many entries.");
1103 TICK_N(
"DRUN Read CACHE: stop");
1106 pd->entry_list = g_malloc0(newsize);
1109 for (
unsigned int index = 0; !error && index < pd->cmd_list_length; index++) {
1110 DRunModeEntry *entry = &(pd->entry_list[index]);
1112 if (drun_read_string(fd, &(entry->action))) {
1116 if (drun_read_string(fd, &(entry->root))) {
1120 if (drun_read_string(fd, &(entry->path))) {
1124 if (drun_read_string(fd, &(entry->app_id))) {
1128 if (drun_read_string(fd, &(entry->desktop_id))) {
1132 if (drun_read_string(fd, &(entry->icon_name))) {
1136 if (drun_read_string(fd, &(entry->exec))) {
1140 if (drun_read_string(fd, &(entry->name))) {
1144 if (drun_read_string(fd, &(entry->generic_name))) {
1149 if (drun_read_stringv(fd, &(entry->categories))) {
1153 if (drun_read_stringv(fd, &(entry->keywords))) {
1158 if (drun_read_string(fd, &(entry->comment))) {
1162 if (drun_read_string(fd, &(entry->url))) {
1167 if (drun_read_integer(fd, &(type))) {
1176 for (
size_t i = 0; i < pd->cmd_list_length; i++) {
1177 drun_entry_clear(&(pd->entry_list[i]));
1179 g_free(pd->entry_list);
1180 pd->cmd_list_length = 0;
1181 pd->cmd_list_length_actual = 0;
1184 TICK_N(
"DRUN Read CACHE: stop");
1188static void get_apps(DRunModePrivateData *pd) {
1189 char *cache_file = g_build_filename(
cache_dir, DRUN_DESKTOP_CACHE_FILE, NULL);
1190 TICK_N(
"Get Desktop apps (start)");
1191 if (drun_read_cache(pd, cache_file)) {
1200 dir = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP);
1201 walk_dir(pd, dir, dir, FALSE);
1202 TICK_N(
"Get Desktop dir apps");
1209 dir = g_build_filename(g_get_user_data_dir(),
"applications", NULL);
1210 walk_dir(pd, dir, dir, TRUE);
1212 TICK_N(
"Get Desktop apps (user dir)");
1219 const gchar *
const *sys = g_get_system_data_dirs();
1220 for (
const gchar *
const *iter = sys; *iter != NULL; ++iter) {
1221 gboolean unique = TRUE;
1223 for (
const gchar *
const *iterd = sys; iterd != iter; ++iterd) {
1224 if (g_strcmp0(*iter, *iterd) == 0) {
1229 if (unique && (**iter) !=
'\0') {
1230 char *dir = g_build_filename(*iter,
"applications", NULL);
1231 walk_dir(pd, dir, dir, TRUE);
1235 TICK_N(
"Get Desktop apps (system dirs)");
1237 pd->disable_giolaunch = FALSE;
1240 pd->disable_giolaunch = TRUE;
1243 get_apps_history(pd);
1245 g_qsort_with_data(pd->entry_list, pd->cmd_list_length,
1246 sizeof(DRunModeEntry), drun_int_sort_list, NULL);
1250 write_cache(pd, cache_file);
1252 g_debug(
"Read drun entries from cache.");
1257static void drun_mode_parse_entry_fields(
void) {
1258 char *savept = NULL;
1261 const char *
const sep =
",#";
1263 for (
unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++) {
1264 matching_entry_fields[i].enabled_match = FALSE;
1265 matching_entry_fields[i].enabled_display = FALSE;
1267 for (
char *token = strtok_r(switcher_str, sep, &savept); token != NULL;
1268 token = strtok_r(NULL, sep, &savept)) {
1269 if (strcmp(token,
"all") == 0) {
1270 for (
unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++) {
1271 matching_entry_fields[i].enabled_match = TRUE;
1272 matching_entry_fields[i].enabled_display = TRUE;
1276 gboolean matched = FALSE;
1277 for (
unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++) {
1278 const char *entry_name = matching_entry_fields[i].entry_field_name;
1279 if (g_ascii_strcasecmp(token, entry_name) == 0) {
1280 matching_entry_fields[i].enabled_match = TRUE;
1281 matching_entry_fields[i].enabled_display = TRUE;
1286 g_warning(
"Invalid entry name :%s", token);
1290 g_free(switcher_str);
1293static void drun_mode_parse_display_format(
void) {
1294 for (
int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++) {
1295 if (matching_entry_fields[i].enabled_display)
1298 gchar *search_term =
1299 g_strdup_printf(
"{%s}", matching_entry_fields[i].entry_field_name);
1301 matching_entry_fields[i].enabled_match = TRUE;
1303 g_free(search_term);
1307static int drun_mode_init(
Mode *sw) {
1311 DRunModePrivateData *pd = g_malloc0(
sizeof(*pd));
1312 pd->disabled_entries =
1313 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1316 const char *current_desktop = g_getenv(
"XDG_CURRENT_DESKTOP");
1317 pd->current_desktop_list =
1318 current_desktop ? g_strsplit(current_desktop,
":", 0) : NULL;
1328 drun_mode_parse_entry_fields();
1329 drun_mode_parse_display_format();
1332 pd->completer = NULL;
1335static void drun_entry_clear(DRunModeEntry *e) {
1342 g_free(e->desktop_id);
1343 if (e->icon != NULL) {
1344 cairo_surface_destroy(e->icon);
1346 g_free(e->icon_name);
1349 g_free(e->generic_name);
1351 if (e->action != DRUN_GROUP_NAME) {
1354 g_free(e->action_name);
1355 g_strfreev(e->categories);
1356 g_strfreev(e->keywords);
1358 g_key_file_free(e->key_file);
1362static ModeMode drun_mode_result(
Mode *sw,
int mretv,
char **input,
1363 unsigned int selected_line) {
1367 if (rmpd->file_complete == TRUE) {
1372 g_free(rmpd->old_completer_input);
1373 rmpd->old_completer_input = *input;
1375 if (rmpd->selected_line < rmpd->cmd_list_length) {
1376 (*input) = g_strdup(rmpd->old_input);
1378 rmpd->file_complete = FALSE;
1386 exec_cmd_entry(rmpd, &(rmpd->entry_list[rmpd->selected_line]), path);
1393 switch (rmpd->entry_list[selected_line].type) {
1394 case DRUN_DESKTOP_ENTRY_TYPE_SERVICE:
1395 case DRUN_DESKTOP_ENTRY_TYPE_APPLICATION:
1396 exec_cmd_entry(rmpd, &(rmpd->entry_list[selected_line]), NULL);
1398 case DRUN_DESKTOP_ENTRY_TYPE_LINK:
1399 launch_link_entry(&(rmpd->entry_list[selected_line]));
1402 g_assert_not_reached();
1405 *input[0] !=
'\0') {
1410 run_in_term ? &context : NULL)) {
1414 selected_line < rmpd->cmd_list_length) {
1416 if (rmpd->entry_list[selected_line].sort_index >= 0) {
1417 delete_entry_history(&(rmpd->entry_list[selected_line]));
1418 drun_entry_clear(&(rmpd->entry_list[selected_line]));
1419 memmove(&(rmpd->entry_list[selected_line]),
1420 &rmpd->entry_list[selected_line + 1],
1421 sizeof(DRunModeEntry) *
1422 (rmpd->cmd_list_length - selected_line - 1));
1423 rmpd->cmd_list_length--;
1430 if (selected_line < rmpd->cmd_list_length) {
1431 switch (rmpd->entry_list[selected_line].type) {
1432 case DRUN_DESKTOP_ENTRY_TYPE_SERVICE:
1433 case DRUN_DESKTOP_ENTRY_TYPE_APPLICATION: {
1434 GRegex *regex = g_regex_new(
"%[fFuU]", 0, 0, NULL);
1436 if (g_regex_match(regex, rmpd->entry_list[selected_line].exec, 0,
1438 rmpd->selected_line = selected_line;
1441 g_free(rmpd->old_input);
1442 rmpd->old_input = g_strdup(*input);
1446 *input = g_strdup(rmpd->old_completer_input);
1452 rmpd->file_complete = TRUE;
1455 g_regex_unref(regex);
1464static void drun_mode_destroy(
Mode *sw) {
1467 for (
size_t i = 0; i < rmpd->cmd_list_length; i++) {
1468 drun_entry_clear(&(rmpd->entry_list[i]));
1470 g_hash_table_destroy(rmpd->disabled_entries);
1471 g_free(rmpd->entry_list);
1473 g_free(rmpd->old_completer_input);
1474 g_free(rmpd->old_input);
1475 if (rmpd->completer != NULL) {
1477 g_free(rmpd->completer);
1480 g_strfreev(rmpd->current_desktop_list);
1481 g_strfreev(rmpd->show_categories);
1482 g_strfreev(rmpd->exclude_categories);
1488static char *_get_display_value(
const Mode *sw,
unsigned int selected_line,
1489 int *state, G_GNUC_UNUSED GList **list,
1493 if (pd->file_complete) {
1494 return pd->completer->_get_display_value(pd->completer, selected_line,
1495 state, list, get_entry);
1501 if (pd->entry_list == NULL) {
1503 return g_strdup(
"Failed");
1506 DRunModeEntry *dr = &(pd->entry_list[selected_line]);
1508 if (dr->categories) {
1509 char *tcats = g_strjoinv(
",", dr->categories);
1511 cats = g_markup_escape_text(tcats, -1);
1515 gchar *keywords = NULL;
1517 char *tkeyw = g_strjoinv(
",", dr->keywords);
1519 keywords = g_markup_escape_text(tkeyw, -1);
1529 if (dr->generic_name) {
1530 egn = g_markup_escape_text(dr->generic_name, -1);
1533 en = g_markup_escape_text(dr->name, -1);
1536 ec = g_markup_escape_text(dr->comment, -1);
1539 eu = g_markup_escape_text(dr->url, -1);
1542 ee = g_markup_escape_text(dr->exec, -1);
1547 ec,
"{exec}", ee,
"{categories}", cats,
"{keywords}", keywords,
"{url}",
1559static cairo_surface_t *_get_icon(
const Mode *sw,
unsigned int selected_line,
1560 unsigned int height) {
1563 if (pd->file_complete) {
1564 return pd->completer->_get_icon(pd->completer, selected_line, height);
1566 g_return_val_if_fail(pd->entry_list != NULL, NULL);
1567 DRunModeEntry *dr = &(pd->entry_list[selected_line]);
1568 if (dr->icon_name != NULL) {
1569 if (dr->icon_fetch_uid > 0 && dr->icon_fetch_size == height &&
1570 dr->icon_fetch_scale == scale) {
1575 dr->icon_fetch_size = height;
1576 dr->icon_fetch_scale = scale;
1583static char *drun_get_completion(
const Mode *sw,
unsigned int index) {
1586 DRunModeEntry *dr = &(pd->entry_list[index]);
1587 if (dr->generic_name == NULL) {
1588 return g_strdup(dr->name);
1590 return g_strdup_printf(
"%s", dr->name);
1594 unsigned int index) {
1595 DRunModePrivateData *rmpd =
1597 if (rmpd->file_complete) {
1598 return rmpd->completer->_token_match(rmpd->completer, tokens, index);
1602 for (
int j = 0; match && tokens[j] != NULL; j++) {
1606 if (matching_entry_fields[DRUN_MATCH_FIELD_NAME].enabled_match) {
1607 if (rmpd->entry_list[index].name) {
1611 if (matching_entry_fields[DRUN_MATCH_FIELD_GENERIC].enabled_match) {
1613 if (test == tokens[j]->invert && rmpd->entry_list[index].generic_name) {
1618 if (matching_entry_fields[DRUN_MATCH_FIELD_EXEC].enabled_match) {
1620 if (test == tokens[j]->invert && rmpd->entry_list[index].exec) {
1624 if (matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled_match) {
1626 if (test == tokens[j]->invert) {
1627 gchar **list = rmpd->entry_list[index].categories;
1628 for (
int iter = 0; test == tokens[j]->
invert && list && list[iter];
1634 if (matching_entry_fields[DRUN_MATCH_FIELD_KEYWORDS].enabled_match) {
1636 if (test == tokens[j]->invert) {
1637 gchar **list = rmpd->entry_list[index].keywords;
1638 for (
int iter = 0; test == tokens[j]->
invert && list && list[iter];
1644 if (matching_entry_fields[DRUN_MATCH_FIELD_URL].enabled_match) {
1647 if (test == tokens[j]->invert && rmpd->entry_list[index].url) {
1651 if (matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled_match) {
1654 if (test == tokens[j]->invert && rmpd->entry_list[index].comment) {
1667static unsigned int drun_mode_get_num_entries(
const Mode *sw) {
1668 const DRunModePrivateData *pd =
1670 if (pd->file_complete) {
1671 return pd->completer->_get_num_entries(pd->completer);
1673 return pd->cmd_list_length;
1675static char *drun_get_message(
const Mode *sw) {
1677 if (pd->file_complete) {
1678 if (pd->selected_line < pd->cmd_list_length) {
1682 g_strdup_printf(
"File complete for: %s\n%s",
1683 pd->entry_list[pd->selected_line].name, msg);
1687 return g_strdup_printf(
"File complete for: %s",
1688 pd->entry_list[pd->selected_line].name);
1695Mode drun_mode = {.name =
"drun",
1696 .cfg_name_key =
"display-drun",
1697 ._init = drun_mode_init,
1698 ._get_num_entries = drun_mode_get_num_entries,
1699 ._result = drun_mode_result,
1700 ._destroy = drun_mode_destroy,
1701 ._token_match = drun_token_match,
1702 ._get_message = drun_get_message,
1703 ._get_completion = drun_get_completion,
1704 ._get_display_value = _get_display_value,
1705 ._get_icon = _get_icon,
1706 ._preprocess_input = NULL,
1707 .private_data = NULL,
guint display_scale(void)
const char * icon_name[NUM_FILE_TYPES]
gboolean helper_execute_command_env(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context, char **envp)
Property * rofi_theme_find_property(ThemeWidget *wid, PropertyType type, const char *property, gboolean exact)
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
char * helper_string_replace_if_exists(char *string,...)
ThemeWidget * rofi_config_find_widget(const char *name, const char *state, gboolean exact)
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
void history_set(const char *filename, const char *entry)
void history_remove(const char *filename, const char *entry)
char ** history_get_list(const char *filename, unsigned int *length)
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)
int mode_init(Mode *mode)
Mode * mode_create(const Mode *mode)
ModeMode mode_completer_result(Mode *mode, int menu_retv, char **input, unsigned int selected_line, char **path)
void * mode_get_private_data(const Mode *mode)
char * mode_get_message(const Mode *mode)
void mode_set_private_data(Mode *mode, void *pd)
const Mode * rofi_get_completer(void)
struct rofi_int_matcher_t rofi_int_matcher
gboolean drun_reload_desktop_cache
unsigned int drun_show_actions
char * drun_display_format
char * drun_exclude_categories
gboolean drun_use_desktop_cache