28#define G_LOG_DOMAIN "Modes.DRun"
64#define DRUN_CACHE_FILE "rofi3.druncache"
67#define DRUN_DESKTOP_CACHE_FILE "rofi-drun-desktop.cache"
70char *DRUN_GROUP_NAME =
"Desktop Entry";
75typedef struct _DRunModePrivateData DRunModePrivateData;
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;
98 DRunModePrivateData *pd;
117 cairo_surface_t *
icon;
137 uint32_t icon_fetch_uid;
138 uint32_t icon_fetch_size;
139 guint icon_fetch_scale;
141 DRunDesktopEntryType type;
145 const char *entry_field_name;
146 gboolean enabled_match;
147 gboolean enabled_display;
153 DRUN_MATCH_FIELD_NAME,
155 DRUN_MATCH_FIELD_GENERIC,
157 DRUN_MATCH_FIELD_EXEC,
159 DRUN_MATCH_FIELD_CATEGORIES,
161 DRUN_MATCH_FIELD_KEYWORDS,
163 DRUN_MATCH_FIELD_COMMENT,
165 DRUN_MATCH_FIELD_URL,
167 DRUN_MATCH_NUM_FIELDS,
172static DRunEntryField matching_entry_fields[DRUN_MATCH_NUM_FIELDS] = {
174 .entry_field_name =
"name",
175 .enabled_match = TRUE,
176 .enabled_display = TRUE,
179 .entry_field_name =
"generic",
180 .enabled_match = TRUE,
181 .enabled_display = TRUE,
184 .entry_field_name =
"exec",
185 .enabled_match = TRUE,
186 .enabled_display = TRUE,
189 .entry_field_name =
"categories",
190 .enabled_match = TRUE,
191 .enabled_display = TRUE,
194 .entry_field_name =
"keywords",
195 .enabled_match = TRUE,
196 .enabled_display = TRUE,
199 .entry_field_name =
"comment",
200 .enabled_match = FALSE,
201 .enabled_display = FALSE,
204 .entry_field_name =
"url",
205 .enabled_match = FALSE,
206 .enabled_display = FALSE,
209struct _DRunModePrivateData {
210 DRunModeEntry *entry_list;
211 unsigned int cmd_list_length;
212 unsigned int cmd_list_length_actual;
214 GHashTable *disabled_entries;
215 unsigned int disabled_entries_length;
216 unsigned int expected_line_height;
218 char **show_categories;
219 char **exclude_categories;
222 const gchar *icon_theme;
224 gchar **current_desktop_list;
226 gboolean file_complete;
228 char *old_completer_input;
229 uint32_t selected_line;
232 gboolean disable_dbusactivate;
240static void drun_entry_clear(DRunModeEntry *e);
242static gboolean drun_helper_eval_cb(
const GMatchInfo *info, GString *res,
245 struct RegexEvalArg *e = (
struct RegexEvalArg *)data;
249 match = g_match_info_fetch(info, 0);
257 g_string_append(res, e->path);
263 if (e->e && e->e->icon) {
264 g_string_append_printf(res,
"--icon %s", e->e->icon_name);
276 g_string_append(res,
"%");
280 char *esc = g_shell_quote(e->e->path);
281 g_string_append(res, esc);
287 char *esc = g_shell_quote(e->e->name);
288 g_string_append(res, esc);
303static void launch_link_entry(DRunModeEntry *e) {
304 if (e->key_file == NULL) {
305 GKeyFile *kf = g_key_file_new();
306 GError *error = NULL;
307 gboolean res = g_key_file_load_from_file(kf, e->path, 0, &error);
311 g_warning(
"[%s] [%s] Failed to parse desktop file because: %s.",
312 e->app_id, e->path, error->message);
319 gchar *url = g_key_file_get_string(e->key_file, e->action,
"URL", NULL);
320 if (url == NULL || strlen(url) == 0) {
321 g_warning(
"[%s] [%s] No URL found.", e->app_id, e->path);
326 gsize command_len = strlen(
config.drun_url_launcher) + strlen(url) +
328 gchar *command = g_newa(gchar, command_len);
329 g_snprintf(command, command_len,
"%s %s",
config.drun_url_launcher, url);
332 g_debug(
"Link launch command: |%s|", command);
334 char *path = g_build_filename(
cache_dir, DRUN_CACHE_FILE, NULL);
340static gchar *app_path_for_id(
const gchar *app_id) {
344 path = g_strconcat(
"/", app_id, NULL);
345 for (i = 0; path[i]; i++) {
354static GVariant *app_get_platform_data(
void) {
355 GVariantBuilder builder;
356 const gchar *startup_id;
358 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
360 if ((startup_id = g_getenv(
"DESKTOP_STARTUP_ID")))
361 g_variant_builder_add(&builder,
"{sv}",
"desktop-startup-id",
362 g_variant_new_string(startup_id));
364 if ((startup_id = g_getenv(
"XDG_ACTIVATION_TOKEN")))
365 g_variant_builder_add(&builder,
"{sv}",
"activation-token",
366 g_variant_new_string(startup_id));
368 return g_variant_builder_end(&builder);
371static gboolean exec_dbus_entry(DRunModeEntry *e,
const char *path) {
372 GVariantBuilder files;
373 GDBusConnection *session;
374 GError *error = NULL;
377 GVariant *params = NULL;
378 const char *method =
"Activate";
379 g_debug(
"Trying to launch desktop file using dbus activation.");
381 session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
383 g_warning(
"unable to connect to D-Bus: %s\n", error->message);
388 object_path = app_path_for_id(e->app_id);
390 g_variant_builder_init(&files, G_VARIANT_TYPE_STRING_ARRAY);
394 params = g_variant_new(
"(as@a{sv})", &files, app_get_platform_data());
396 params = g_variant_new(
"(@a{sv})", app_get_platform_data());
399 GFile *file = g_file_new_for_commandline_arg(path);
400 g_variant_builder_add_value(
401 &files, g_variant_new_take_string(g_file_get_uri(file)));
402 g_object_unref(file);
405 result = g_dbus_connection_call_sync(
406 session, e->app_id, object_path,
"org.freedesktop.Application", method,
407 params, G_VARIANT_TYPE_UNIT, G_DBUS_CALL_FLAGS_NONE, 1500, NULL, &error);
412 g_variant_unref(result);
414 g_warning(
"error sending %s message to application: %s\n",
"Open",
417 g_object_unref(session);
420 g_object_unref(session);
424static void exec_cmd_entry(DRunModePrivateData *pd, DRunModeEntry *e,
426 GError *error = NULL;
427 GRegex *reg = g_regex_new(
"%[a-zA-Z%]", 0, 0, &error);
429 g_warning(
"Internal error, failed to create regex: %s.", error->message);
433 struct RegexEvalArg earg = {.e = e, .path = path, .success = TRUE};
434 char *str = g_regex_replace_eval(reg, e->exec, -1, 0, 0, drun_helper_eval_cb,
437 g_warning(
"Internal error, failed replace field codes: %s.",
443 if (earg.success == FALSE) {
444 g_warning(
"Invalid field code in Exec line: %s.", e->exec);
449 g_warning(
"Nothing to execute after processing: %s.", e->exec);
453 g_debug(
"Parsed command: |%s| into |%s|.", e->exec, str);
455 if (e->key_file == NULL) {
456 GKeyFile *kf = g_key_file_new();
457 GError *key_error = NULL;
458 gboolean res = g_key_file_load_from_file(kf, e->path, 0, &key_error);
462 g_warning(
"[%s] [%s] Failed to parse desktop file because: %s.",
463 e->app_id, e->path, key_error->message);
464 g_error_free(key_error);
471 const gchar *fp = g_strstrip(str);
473 g_key_file_get_string(e->key_file, e->action,
"Path", NULL);
474 if (exec_path != NULL && strlen(exec_path) == 0) {
482 .icon = e->icon_name,
486 g_key_file_get_boolean(e->key_file, e->action,
"StartupNotify", NULL);
487 gchar *wmclass = NULL;
489 g_key_file_has_key(e->key_file, e->action,
"StartupWMClass", NULL)) {
491 g_key_file_get_string(e->key_file, e->action,
"StartupWMClass", NULL);
497 gboolean launched = FALSE;
498 if (!(pd->disable_dbusactivate)) {
499 if (g_key_file_get_boolean(e->key_file, e->action,
"DBusActivatable",
501 printf(
"DBus launch\n");
502 launched = exec_dbus_entry(e, path);
505 if (launched == FALSE) {
511 g_key_file_get_boolean(e->key_file, e->action,
"Terminal", NULL);
513 char *drun_cach_path = g_build_filename(
cache_dir, DRUN_CACHE_FILE, NULL);
516 g_free(drun_cach_path);
524static gboolean rofi_strv_contains(
const char *
const *categories,
525 const char *
const *field) {
526 for (
int i = 0; categories && categories[i]; i++) {
527 for (
int j = 0; field[j]; j++) {
528 if (g_str_equal(categories[i], field[j])) {
538static void read_desktop_file(DRunModePrivateData *pd,
const char *root,
539 const char *path,
const gchar *basename,
540 const char *action) {
541 DRunDesktopEntryType desktop_entry_type =
542 DRUN_DESKTOP_ENTRY_TYPE_UNDETERMINED;
543 int parse_action = (
config.drun_show_actions && action != DRUN_GROUP_NAME);
546 const ssize_t id_len = strlen(path) - strlen(root);
548 g_strlcpy(
id, &(path[strlen(root) + 1]), id_len);
549 for (
int index = 0; index < id_len; index++) {
550 if (
id[index] ==
'/') {
556 if (g_hash_table_contains(pd->disabled_entries,
id) && !parse_action) {
557 g_debug(
"[%s] [%s] Skipping, was previously seen.",
id, path);
560 GKeyFile *kf = g_key_file_new();
561 GError *error = NULL;
562 gboolean res = g_key_file_load_from_file(kf, path, 0, &error);
565 g_debug(
"[%s] [%s] Failed to parse desktop file because: %s.",
id, path,
572 if (g_key_file_has_group(kf, action) == FALSE) {
574 g_debug(
"[%s] [%s] Invalid desktop file: No %s group",
id, path, action);
579 gchar *key = g_key_file_get_string(kf, DRUN_GROUP_NAME,
"Type", NULL);
582 g_debug(
"[%s] [%s] Invalid desktop file: No type indicated",
id, path);
586 if (!g_strcmp0(key,
"Application")) {
587 desktop_entry_type = DRUN_DESKTOP_ENTRY_TYPE_APPLICATION;
588 }
else if (!g_strcmp0(key,
"Link")) {
589 desktop_entry_type = DRUN_DESKTOP_ENTRY_TYPE_LINK;
590 }
else if (!g_strcmp0(key,
"Service")) {
591 desktop_entry_type = DRUN_DESKTOP_ENTRY_TYPE_SERVICE;
592 g_debug(
"Service file detected.");
595 "[%s] [%s] Skipping desktop file: Not of type Application or Link (%s)",
604 if (!g_key_file_has_key(kf, DRUN_GROUP_NAME,
"Name", NULL)) {
605 g_debug(
"[%s] [%s] Invalid desktop file: no 'Name' key present.",
id, path);
611 if (g_key_file_get_boolean(kf, DRUN_GROUP_NAME,
"Hidden", NULL)) {
613 "[%s] [%s] Adding desktop file to disabled list: 'Hidden' key is true",
616 g_hash_table_add(pd->disabled_entries, g_strdup(
id));
619 if (pd->current_desktop_list) {
620 gboolean show = TRUE;
622 if (g_key_file_has_key(kf, DRUN_GROUP_NAME,
"OnlyShowIn", NULL)) {
625 gchar **list = g_key_file_get_string_list(kf, DRUN_GROUP_NAME,
626 "OnlyShowIn", &llength, NULL);
628 for (gsize lcd = 0; !show && pd->current_desktop_list[lcd]; lcd++) {
629 for (gsize lle = 0; !show && lle < llength; lle++) {
630 show = (g_strcmp0(pd->current_desktop_list[lcd], list[lle]) == 0);
636 if (show && g_key_file_has_key(kf, DRUN_GROUP_NAME,
"NotShowIn", NULL)) {
638 gchar **list = g_key_file_get_string_list(kf, DRUN_GROUP_NAME,
639 "NotShowIn", &llength, NULL);
641 for (gsize lcd = 0; show && pd->current_desktop_list[lcd]; lcd++) {
642 for (gsize lle = 0; show && lle < llength; lle++) {
643 show = !(g_strcmp0(pd->current_desktop_list[lcd], list[lle]) == 0);
651 g_debug(
"[%s] [%s] Adding desktop file to disabled list: "
652 "'OnlyShowIn'/'NotShowIn' keys don't match current desktop",
655 g_hash_table_add(pd->disabled_entries, g_strdup(
id));
660 if (g_key_file_get_boolean(kf, DRUN_GROUP_NAME,
"NoDisplay", NULL)) {
661 g_debug(
"[%s] [%s] Adding desktop file to disabled list: 'NoDisplay' key "
665 g_hash_table_add(pd->disabled_entries, g_strdup(
id));
670 if (desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_APPLICATION &&
671 !g_key_file_has_key(kf, DRUN_GROUP_NAME,
"Exec", NULL)) {
672 g_debug(
"[%s] [%s] Unsupported desktop file: no 'Exec' key present for "
678 if (desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_SERVICE &&
679 !g_key_file_has_key(kf, DRUN_GROUP_NAME,
"Exec", NULL)) {
680 g_debug(
"[%s] [%s] Unsupported desktop file: no 'Exec' key present for "
686 if (desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_LINK &&
687 !g_key_file_has_key(kf, DRUN_GROUP_NAME,
"URL", NULL)) {
688 g_debug(
"[%s] [%s] Unsupported desktop file: no 'URL' key present for type "
695 if (g_key_file_has_key(kf, DRUN_GROUP_NAME,
"TryExec", NULL)) {
696 char *te = g_key_file_get_string(kf, DRUN_GROUP_NAME,
"TryExec", NULL);
697 if (!g_path_is_absolute(te)) {
698 char *fp = g_find_program_in_path(te);
706 if (g_file_test(te, G_FILE_TEST_IS_EXECUTABLE) == FALSE) {
715 char **categories = NULL;
716 if (pd->show_categories) {
717 categories = g_key_file_get_locale_string_list(
718 kf, DRUN_GROUP_NAME,
"Categories", NULL, NULL, NULL);
719 if (!rofi_strv_contains((
const char *
const *)categories,
720 (
const char *
const *)pd->show_categories)) {
721 g_strfreev(categories);
727 if (pd->exclude_categories) {
728 if (categories == NULL) {
729 categories = g_key_file_get_locale_string_list(
730 kf, DRUN_GROUP_NAME,
"Categories", NULL, NULL, NULL);
732 if (rofi_strv_contains((
const char *
const *)categories,
733 (
const char *
const *)pd->exclude_categories)) {
734 g_strfreev(categories);
740 size_t nl = ((pd->cmd_list_length) + 1);
741 if (nl >= pd->cmd_list_length_actual) {
742 pd->cmd_list_length_actual += 256;
743 pd->entry_list = g_realloc(pd->entry_list, pd->cmd_list_length_actual *
744 sizeof(*(pd->entry_list)));
748 if (G_UNLIKELY(pd->cmd_list_length > INT_MAX)) {
750 pd->entry_list[pd->cmd_list_length].sort_index = INT_MIN;
752 pd->entry_list[pd->cmd_list_length].sort_index = -nl;
754 pd->entry_list[pd->cmd_list_length].icon_size = 0;
755 pd->entry_list[pd->cmd_list_length].icon_fetch_uid = 0;
756 pd->entry_list[pd->cmd_list_length].icon_fetch_size = 0;
757 pd->entry_list[pd->cmd_list_length].icon_fetch_scale = 0;
758 pd->entry_list[pd->cmd_list_length].root = g_strdup(root);
759 pd->entry_list[pd->cmd_list_length].path = g_strdup(path);
760 pd->entry_list[pd->cmd_list_length].desktop_id = g_strdup(
id);
761 pd->entry_list[pd->cmd_list_length].app_id =
762 g_strndup(basename, strlen(basename) - strlen(
".desktop"));
764 g_key_file_get_locale_string(kf, DRUN_GROUP_NAME,
"Name", NULL, NULL);
766 if (action != DRUN_GROUP_NAME) {
767 gchar *na = g_key_file_get_locale_string(kf, action,
"Name", NULL, NULL);
768 gchar *l = g_strdup_printf(
"%s - %s", n, na);
772 pd->entry_list[pd->cmd_list_length].name = n;
773 pd->entry_list[pd->cmd_list_length].action = DRUN_GROUP_NAME;
774 gchar *gn = g_key_file_get_locale_string(kf, DRUN_GROUP_NAME,
"GenericName",
776 pd->entry_list[pd->cmd_list_length].generic_name = gn;
777 if (matching_entry_fields[DRUN_MATCH_FIELD_KEYWORDS].enabled_match ||
778 matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled_display) {
779 pd->entry_list[pd->cmd_list_length].keywords =
780 g_key_file_get_locale_string_list(kf, DRUN_GROUP_NAME,
"Keywords", NULL,
783 pd->entry_list[pd->cmd_list_length].keywords = NULL;
786 if (matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled_match ||
787 matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled_display) {
789 pd->entry_list[pd->cmd_list_length].categories = categories;
792 pd->entry_list[pd->cmd_list_length].categories =
793 g_key_file_get_locale_string_list(kf, DRUN_GROUP_NAME,
"Categories",
797 pd->entry_list[pd->cmd_list_length].categories = NULL;
799 g_strfreev(categories);
801 pd->entry_list[pd->cmd_list_length].type = desktop_entry_type;
802 if (desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_APPLICATION ||
803 desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_SERVICE) {
804 pd->entry_list[pd->cmd_list_length].exec =
805 g_key_file_get_string(kf, action,
"Exec", NULL);
807 pd->entry_list[pd->cmd_list_length].exec = NULL;
810 if (matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled_match ||
811 matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled_display) {
812 pd->entry_list[pd->cmd_list_length].comment = g_key_file_get_locale_string(
813 kf, DRUN_GROUP_NAME,
"Comment", NULL, NULL);
815 pd->entry_list[pd->cmd_list_length].comment = NULL;
817 if (matching_entry_fields[DRUN_MATCH_FIELD_URL].enabled_match ||
818 matching_entry_fields[DRUN_MATCH_FIELD_URL].enabled_display) {
819 pd->entry_list[pd->cmd_list_length].url =
820 g_key_file_get_locale_string(kf, DRUN_GROUP_NAME,
"URL", NULL, NULL);
822 pd->entry_list[pd->cmd_list_length].url = NULL;
824 pd->entry_list[pd->cmd_list_length].icon_name =
825 g_key_file_get_locale_string(kf, DRUN_GROUP_NAME,
"Icon", NULL, NULL);
826 pd->entry_list[pd->cmd_list_length].icon = NULL;
829 pd->entry_list[pd->cmd_list_length].key_file = kf;
831 g_hash_table_add(pd->disabled_entries, g_strdup(
id));
832 g_debug(
"[%s] Using file %s.",
id, path);
833 (pd->cmd_list_length)++;
836 gsize actions_length = 0;
837 char **actions = g_key_file_get_string_list(kf, DRUN_GROUP_NAME,
"Actions",
838 &actions_length, NULL);
839 for (gsize iter = 0; iter < actions_length; iter++) {
840 char *new_action = g_strdup_printf(
"Desktop Action %s", actions[iter]);
841 read_desktop_file(pd, root, path, basename, new_action);
852static void walk_dir(DRunModePrivateData *pd,
const char *root,
853 const char *dirname,
const gboolean recursive) {
856 g_debug(
"Checking directory %s for desktop files.", dirname);
857 dir = opendir(dirname);
863 gchar *filename = NULL;
865 while ((file = readdir(dir)) != NULL) {
866 if (file->d_name[0] ==
'.') {
869 switch (file->d_type) {
874 filename = g_build_filename(dirname, file->d_name, NULL);
882 if (file->d_type == DT_LNK || file->d_type == DT_UNKNOWN) {
883 file->d_type = DT_UNKNOWN;
884 if (stat(filename, &st) == 0) {
885 if (S_ISDIR(st.st_mode)) {
886 file->d_type = DT_DIR;
887 }
else if (S_ISREG(st.st_mode)) {
888 file->d_type = DT_REG;
893 switch (file->d_type) {
896 if (g_str_has_suffix(file->d_name,
".desktop")) {
897 read_desktop_file(pd, root, filename, file->d_name, DRUN_GROUP_NAME);
902 walk_dir(pd, root, filename, recursive);
917static void delete_entry_history(
const DRunModeEntry *entry) {
918 char *path = g_build_filename(
cache_dir, DRUN_CACHE_FILE, NULL);
923static void get_apps_history(DRunModePrivateData *pd) {
924 TICK_N(
"Start drun history");
925 unsigned int length = 0;
926 gchar *path = g_build_filename(
cache_dir, DRUN_CACHE_FILE, NULL);
928 for (
unsigned int index = 0; index < length; index++) {
929 for (
size_t i = 0; i < pd->cmd_list_length; i++) {
930 if (g_strcmp0(pd->entry_list[i].desktop_id, retv[index]) == 0) {
931 unsigned int sort_index = length - index;
932 if (G_LIKELY(sort_index < INT_MAX)) {
933 pd->entry_list[i].sort_index = sort_index;
936 pd->entry_list[i].sort_index = INT_MAX;
943 TICK_N(
"Stop drun history");
946static gint drun_int_sort_list(gconstpointer a, gconstpointer b,
947 G_GNUC_UNUSED gpointer user_data) {
948 DRunModeEntry *da = (DRunModeEntry *)a;
949 DRunModeEntry *db = (DRunModeEntry *)b;
951 if (da->sort_index < 0 && db->sort_index < 0) {
952 if (da->name == NULL && db->name == NULL) {
955 if (da->name == NULL) {
958 if (db->name == NULL) {
961 return g_utf8_collate(da->name, db->name);
963 return db->sort_index - da->sort_index;
971#define CACHE_VERSION 3
972static void drun_write_str(FILE *fd,
const char *str) {
973 size_t l = (str == NULL ? 0 : strlen(str));
974 fwrite(&l,
sizeof(l), 1, fd);
978 fwrite(str, 1, l + 1, fd);
981static void drun_write_integer(FILE *fd, int32_t val) {
982 fwrite(&val,
sizeof(val), 1, fd);
984static gboolean drun_read_integer(FILE *fd, int32_t *type) {
985 if (fread(type,
sizeof(int32_t), 1, fd) != 1) {
986 g_warning(
"Failed to read entry, cache corrupt?");
991static gboolean drun_read_string(FILE *fd,
char **str) {
994 if (fread(&l,
sizeof(l), 1, fd) != 1) {
995 g_warning(
"Failed to read entry, cache corrupt?");
1002 (*str) = g_malloc(l);
1003 if (fread((*str), 1, l, fd) != l) {
1004 g_warning(
"Failed to read entry, cache corrupt?");
1010static void drun_write_strv(FILE *fd,
char **str) {
1011 guint vl = (str == NULL ? 0 : g_strv_length(str));
1012 fwrite(&vl,
sizeof(vl), 1, fd);
1013 for (guint index = 0; index < vl; index++) {
1014 drun_write_str(fd, str[index]);
1017static gboolean drun_read_stringv(FILE *fd,
char ***str) {
1020 if (fread(&vl,
sizeof(vl), 1, fd) != 1) {
1021 g_warning(
"Failed to read entry, cache corrupt?");
1026 (*str) = g_malloc0((vl + 1) *
sizeof(**str));
1027 for (guint index = 0; index < vl; index++) {
1028 if (drun_read_string(fd, &((*str)[index]))) {
1036static void write_cache(DRunModePrivateData *pd,
const char *cache_file) {
1037 if (cache_file == NULL ||
config.drun_use_desktop_cache == FALSE) {
1040 TICK_N(
"DRUN Write CACHE: start");
1042 FILE *fd = fopen(cache_file,
"w");
1044 g_warning(
"Failed to write to cache file");
1047 uint8_t version = CACHE_VERSION;
1048 fwrite(&version,
sizeof(version), 1, fd);
1050 fwrite(&(pd->cmd_list_length),
sizeof(pd->cmd_list_length), 1, fd);
1051 for (
unsigned int index = 0; index < pd->cmd_list_length; index++) {
1052 DRunModeEntry *entry = &(pd->entry_list[index]);
1054 drun_write_str(fd, entry->action);
1055 drun_write_str(fd, entry->root);
1056 drun_write_str(fd, entry->path);
1057 drun_write_str(fd, entry->app_id);
1058 drun_write_str(fd, entry->desktop_id);
1059 drun_write_str(fd, entry->icon_name);
1060 drun_write_str(fd, entry->exec);
1061 drun_write_str(fd, entry->name);
1062 drun_write_str(fd, entry->generic_name);
1064 drun_write_strv(fd, entry->categories);
1065 drun_write_strv(fd, entry->keywords);
1067 drun_write_str(fd, entry->comment);
1068 drun_write_str(fd, entry->url);
1069 drun_write_integer(fd, (int32_t)entry->type);
1073 TICK_N(
"DRUN Write CACHE: end");
1079static gboolean drun_read_cache(DRunModePrivateData *pd,
1080 const char *cache_file) {
1081 if (cache_file == NULL ||
config.drun_use_desktop_cache == FALSE) {
1085 if (
config.drun_reload_desktop_cache) {
1088 TICK_N(
"DRUN Read CACHE: start");
1089 FILE *fd = fopen(cache_file,
"r");
1091 TICK_N(
"DRUN Read CACHE: stop");
1096 uint8_t version = 0;
1098 if (fread(&version,
sizeof(version), 1, fd) != 1) {
1100 g_warning(
"Cache corrupt, ignoring.");
1101 TICK_N(
"DRUN Read CACHE: stop");
1105 if (version != CACHE_VERSION) {
1107 g_warning(
"Cache file wrong version, ignoring.");
1108 TICK_N(
"DRUN Read CACHE: stop");
1112 if (fread(&(pd->cmd_list_length),
sizeof(pd->cmd_list_length), 1, fd) != 1) {
1114 g_warning(
"Cache corrupt, ignoring.");
1115 TICK_N(
"DRUN Read CACHE: stop");
1119 pd->cmd_list_length_actual = pd->cmd_list_length;
1122 g_malloc0(pd->cmd_list_length_actual *
sizeof(*(pd->entry_list)));
1125 for (
unsigned int index = 0; !error && index < pd->cmd_list_length; index++) {
1126 DRunModeEntry *entry = &(pd->entry_list[index]);
1128 if (drun_read_string(fd, &(entry->action))) {
1132 if (drun_read_string(fd, &(entry->root))) {
1136 if (drun_read_string(fd, &(entry->path))) {
1140 if (drun_read_string(fd, &(entry->app_id))) {
1144 if (drun_read_string(fd, &(entry->desktop_id))) {
1148 if (drun_read_string(fd, &(entry->icon_name))) {
1152 if (drun_read_string(fd, &(entry->exec))) {
1156 if (drun_read_string(fd, &(entry->name))) {
1160 if (drun_read_string(fd, &(entry->generic_name))) {
1165 if (drun_read_stringv(fd, &(entry->categories))) {
1169 if (drun_read_stringv(fd, &(entry->keywords))) {
1174 if (drun_read_string(fd, &(entry->comment))) {
1178 if (drun_read_string(fd, &(entry->url))) {
1183 if (drun_read_integer(fd, &(type))) {
1192 for (
size_t i = 0; i < pd->cmd_list_length; i++) {
1193 drun_entry_clear(&(pd->entry_list[i]));
1195 g_free(pd->entry_list);
1196 pd->cmd_list_length = 0;
1197 pd->cmd_list_length_actual = 0;
1200 TICK_N(
"DRUN Read CACHE: stop");
1204static void get_apps(DRunModePrivateData *pd) {
1205 char *cache_file = g_build_filename(
cache_dir, DRUN_DESKTOP_CACHE_FILE, NULL);
1206 TICK_N(
"Get Desktop apps (start)");
1207 if (drun_read_cache(pd, cache_file)) {
1216 dir = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP);
1217 walk_dir(pd, dir, dir, FALSE);
1218 TICK_N(
"Get Desktop dir apps");
1225 dir = g_build_filename(g_get_user_data_dir(),
"applications", NULL);
1226 walk_dir(pd, dir, dir, TRUE);
1228 TICK_N(
"Get Desktop apps (user dir)");
1235 const gchar *
const *sys = g_get_system_data_dirs();
1236 for (
const gchar *
const *iter = sys; *iter != NULL; ++iter) {
1237 gboolean unique = TRUE;
1239 for (
const gchar *
const *iterd = sys; iterd != iter; ++iterd) {
1240 if (g_strcmp0(*iter, *iterd) == 0) {
1245 if (unique && (**iter) !=
'\0') {
1246 char *dir = g_build_filename(*iter,
"applications", NULL);
1247 walk_dir(pd, dir, dir, TRUE);
1251 TICK_N(
"Get Desktop apps (system dirs)");
1253 pd->disable_dbusactivate = FALSE;
1256 pd->disable_dbusactivate = TRUE;
1258 get_apps_history(pd);
1260 g_qsort_with_data(pd->entry_list, pd->cmd_list_length,
1261 sizeof(DRunModeEntry), drun_int_sort_list, NULL);
1265 write_cache(pd, cache_file);
1267 g_debug(
"Read drun entries from cache.");
1272static void drun_mode_parse_entry_fields(
void) {
1273 char *savept = NULL;
1275 char *switcher_str = g_strdup(
config.drun_match_fields);
1276 const char *
const sep =
",#";
1278 for (
unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++) {
1279 matching_entry_fields[i].enabled_match = FALSE;
1280 matching_entry_fields[i].enabled_display = FALSE;
1282 for (
char *token = strtok_r(switcher_str, sep, &savept); token != NULL;
1283 token = strtok_r(NULL, sep, &savept)) {
1284 if (strcmp(token,
"all") == 0) {
1285 for (
unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++) {
1286 matching_entry_fields[i].enabled_match = TRUE;
1287 matching_entry_fields[i].enabled_display = TRUE;
1291 gboolean matched = FALSE;
1292 for (
unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++) {
1293 const char *entry_name = matching_entry_fields[i].entry_field_name;
1294 if (g_ascii_strcasecmp(token, entry_name) == 0) {
1295 matching_entry_fields[i].enabled_match = TRUE;
1296 matching_entry_fields[i].enabled_display = TRUE;
1301 g_warning(
"Invalid entry name :%s", token);
1305 g_free(switcher_str);
1308static void drun_mode_parse_display_format(
void) {
1309 for (
int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++) {
1310 if (matching_entry_fields[i].enabled_display)
1313 gchar *search_term =
1314 g_strdup_printf(
"{%s}", matching_entry_fields[i].entry_field_name);
1315 if (strstr(
config.drun_display_format, search_term)) {
1316 matching_entry_fields[i].enabled_match = TRUE;
1318 g_free(search_term);
1322static int drun_mode_init(
Mode *sw) {
1326 DRunModePrivateData *pd = g_malloc0(
sizeof(*pd));
1327 pd->disabled_entries =
1328 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1331 const char *current_desktop = g_getenv(
"XDG_CURRENT_DESKTOP");
1332 pd->current_desktop_list =
1333 current_desktop ? g_strsplit(current_desktop,
":", 0) : NULL;
1335 if (
config.drun_categories && *(
config.drun_categories)) {
1336 pd->show_categories = g_strsplit(
config.drun_categories,
",", 0);
1339 if (
config.drun_exclude_categories && *(
config.drun_exclude_categories)) {
1340 pd->exclude_categories = g_strsplit(
config.drun_exclude_categories,
",", 0);
1343 drun_mode_parse_entry_fields();
1344 drun_mode_parse_display_format();
1347 pd->completer = NULL;
1350static void drun_entry_clear(DRunModeEntry *e) {
1357 g_free(e->desktop_id);
1358 if (e->icon != NULL) {
1359 cairo_surface_destroy(e->icon);
1361 g_free(e->icon_name);
1364 g_free(e->generic_name);
1366 if (e->action != DRUN_GROUP_NAME) {
1369 g_strfreev(e->categories);
1370 g_strfreev(e->keywords);
1372 g_key_file_free(e->key_file);
1376static ModeMode drun_mode_result(
Mode *sw,
int mretv,
char **input,
1377 unsigned int selected_line) {
1381 if (rmpd->file_complete == TRUE) {
1386 g_free(rmpd->old_completer_input);
1387 rmpd->old_completer_input = *input;
1389 if (rmpd->selected_line < rmpd->cmd_list_length) {
1390 (*input) = g_strdup(rmpd->old_input);
1392 rmpd->file_complete = FALSE;
1400 exec_cmd_entry(rmpd, &(rmpd->entry_list[rmpd->selected_line]), path);
1407 switch (rmpd->entry_list[selected_line].type) {
1408 case DRUN_DESKTOP_ENTRY_TYPE_SERVICE:
1409 case DRUN_DESKTOP_ENTRY_TYPE_APPLICATION:
1410 exec_cmd_entry(rmpd, &(rmpd->entry_list[selected_line]), NULL);
1412 case DRUN_DESKTOP_ENTRY_TYPE_LINK:
1413 launch_link_entry(&(rmpd->entry_list[selected_line]));
1416 g_assert_not_reached();
1419 *input[0] !=
'\0') {
1424 run_in_term ? &context : NULL)) {
1428 selected_line < rmpd->cmd_list_length) {
1430 if (rmpd->entry_list[selected_line].sort_index >= 0) {
1431 delete_entry_history(&(rmpd->entry_list[selected_line]));
1432 drun_entry_clear(&(rmpd->entry_list[selected_line]));
1433 memmove(&(rmpd->entry_list[selected_line]),
1434 &rmpd->entry_list[selected_line + 1],
1435 sizeof(DRunModeEntry) *
1436 (rmpd->cmd_list_length - selected_line - 1));
1437 rmpd->cmd_list_length--;
1444 if (selected_line < rmpd->cmd_list_length) {
1445 switch (rmpd->entry_list[selected_line].type) {
1446 case DRUN_DESKTOP_ENTRY_TYPE_SERVICE:
1447 case DRUN_DESKTOP_ENTRY_TYPE_APPLICATION: {
1448 GRegex *regex = g_regex_new(
"%[fFuU]", 0, 0, NULL);
1450 if (g_regex_match(regex, rmpd->entry_list[selected_line].exec, 0,
1452 rmpd->selected_line = selected_line;
1455 g_free(rmpd->old_input);
1456 rmpd->old_input = g_strdup(*input);
1460 *input = g_strdup(rmpd->old_completer_input);
1466 rmpd->file_complete = TRUE;
1469 g_regex_unref(regex);
1478static void drun_mode_destroy(
Mode *sw) {
1481 for (
size_t i = 0; i < rmpd->cmd_list_length; i++) {
1482 drun_entry_clear(&(rmpd->entry_list[i]));
1484 g_hash_table_destroy(rmpd->disabled_entries);
1485 g_free(rmpd->entry_list);
1487 g_free(rmpd->old_completer_input);
1488 g_free(rmpd->old_input);
1489 if (rmpd->completer != NULL) {
1491 g_free(rmpd->completer);
1494 g_strfreev(rmpd->current_desktop_list);
1495 g_strfreev(rmpd->show_categories);
1496 g_strfreev(rmpd->exclude_categories);
1503 int *state, G_GNUC_UNUSED GList **list,
1507 if (pd->file_complete) {
1508 return pd->completer->_get_display_value(pd->completer, selected_line,
1509 state, list, get_entry);
1515 if (pd->entry_list == NULL) {
1517 return g_strdup(
"Failed");
1520 DRunModeEntry *dr = &(pd->entry_list[selected_line]);
1522 if (dr->categories) {
1523 char *tcats = g_strjoinv(
",", dr->categories);
1525 cats = g_markup_escape_text(tcats, -1);
1529 gchar *keywords = NULL;
1531 char *tkeyw = g_strjoinv(
",", dr->keywords);
1533 keywords = g_markup_escape_text(tkeyw, -1);
1543 if (dr->generic_name) {
1544 egn = g_markup_escape_text(dr->generic_name, -1);
1547 en = g_markup_escape_text(dr->name, -1);
1550 ec = g_markup_escape_text(dr->comment, -1);
1553 eu = g_markup_escape_text(dr->url, -1);
1556 ee = g_markup_escape_text(dr->exec, -1);
1560 config.drun_display_format,
"{generic}", egn,
"{name}", en,
"{comment}",
1561 ec,
"{exec}", ee,
"{categories}", cats,
"{keywords}", keywords,
"{url}",
1573static cairo_surface_t *
_get_icon(
const Mode *sw,
unsigned int selected_line,
1574 unsigned int height) {
1577 if (pd->file_complete) {
1578 return pd->completer->_get_icon(pd->completer, selected_line, height);
1580 g_return_val_if_fail(pd->entry_list != NULL, NULL);
1581 DRunModeEntry *dr = &(pd->entry_list[selected_line]);
1582 if (dr->icon_name != NULL) {
1583 if (dr->icon_fetch_uid > 0 && dr->icon_fetch_size == height &&
1584 dr->icon_fetch_scale == scale) {
1589 dr->icon_fetch_size = height;
1590 dr->icon_fetch_scale = scale;
1597static char *drun_get_completion(
const Mode *sw,
unsigned int index) {
1600 DRunModeEntry *dr = &(pd->entry_list[index]);
1601 if (dr->generic_name == NULL) {
1602 return g_strdup(dr->name);
1604 return g_strdup_printf(
"%s", dr->name);
1608 unsigned int index) {
1609 DRunModePrivateData *rmpd =
1611 if (rmpd->file_complete) {
1612 return rmpd->completer->_token_match(rmpd->completer, tokens, index);
1616 for (
int j = 0; match && tokens[j] != NULL; j++) {
1620 if (matching_entry_fields[DRUN_MATCH_FIELD_NAME].enabled_match) {
1621 if (rmpd->entry_list[index].name) {
1625 if (matching_entry_fields[DRUN_MATCH_FIELD_GENERIC].enabled_match) {
1627 if (test == tokens[j]->invert && rmpd->entry_list[index].generic_name) {
1632 if (matching_entry_fields[DRUN_MATCH_FIELD_EXEC].enabled_match) {
1634 if (test == tokens[j]->invert && rmpd->entry_list[index].exec) {
1638 if (matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled_match) {
1640 if (test == tokens[j]->invert) {
1641 gchar **list = rmpd->entry_list[index].categories;
1642 for (
int iter = 0; test == tokens[j]->
invert && list && list[iter];
1648 if (matching_entry_fields[DRUN_MATCH_FIELD_KEYWORDS].enabled_match) {
1650 if (test == tokens[j]->invert) {
1651 gchar **list = rmpd->entry_list[index].keywords;
1652 for (
int iter = 0; test == tokens[j]->
invert && list && list[iter];
1658 if (matching_entry_fields[DRUN_MATCH_FIELD_URL].enabled_match) {
1661 if (test == tokens[j]->invert && rmpd->entry_list[index].url) {
1665 if (matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled_match) {
1668 if (test == tokens[j]->invert && rmpd->entry_list[index].comment) {
1681static unsigned int drun_mode_get_num_entries(
const Mode *sw) {
1682 const DRunModePrivateData *pd =
1684 if (pd->file_complete) {
1685 return pd->completer->_get_num_entries(pd->completer);
1687 return pd->cmd_list_length;
1689static char *drun_get_message(
const Mode *sw) {
1691 if (pd->file_complete) {
1692 if (pd->selected_line < pd->cmd_list_length) {
1696 g_strdup_printf(
"File complete for: %s\n%s",
1697 pd->entry_list[pd->selected_line].name, msg);
1701 return g_strdup_printf(
"File complete for: %s",
1702 pd->entry_list[pd->selected_line].name);
1709Mode drun_mode = {.name =
"drun",
1710 .cfg_name_key =
"display-drun",
1711 ._init = drun_mode_init,
1712 ._get_num_entries = drun_mode_get_num_entries,
1713 ._result = drun_mode_result,
1714 ._destroy = drun_mode_destroy,
1715 ._token_match = drun_token_match,
1716 ._get_message = drun_get_message,
1717 ._get_completion = drun_get_completion,
1720 ._preprocess_input = NULL,
1721 .private_data = NULL,
guint display_scale(void)
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]
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)
static void get_apps(KeysHelpModePrivateData *pd)
struct rofi_int_matcher_t rofi_int_matcher