From 5bec191d2ef3f7083573a99b4f9dd4fcc4a68208 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Wed, 26 Aug 2020 15:10:04 -0400 Subject: [PATCH] Follow Type=Link standard desktop entries with drun (#1168) * [DRun] Introduce data structure changes for Link desktop entries From the [freedesktop spec][1]: > This specification defines 3 types of desktop entries: > Application (type 1), Link (type 2) and Directory (type 3). To allow > the addition of new types in the future, implementations should > ignore desktop entries with an unknown type. This commit adds an enum to capture these types, and adds `type` to DRunModeEntry. [1]: https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html part of #1166 * [DRun] Sanity check Link entries and capture the URL Note that we're introducing some logic that will differ depending on the Desktop entry type (Application or Link). The logic is: - if entry is Application type, - then Exec is required - and the value is saved in .exec - and drun_mode_result calls exec_cmd_entry - if entry is Link type, - then URL is required (but is not saved in the DRunModeEntry) - and drun_mode_result calls new function launch_link_entry part of #1166 * [DRun] Launch desktop links via xdg-open Note that this introduces a new dependency on xdg-open, which may not be installed. In that case, rofi will display an error dialog with something like: "Failed to execute child process xdg-open (No such file or directory)" which hopefully is explanatory enough for folks. part of #1166 * Make drun options comments consistent and add a bit of whitespace * [DRun] new config option drun-url-launcher for opening links In previous commit, this was a hard-coded string. part of #1166 --- config/config.c | 15 +++++-- include/settings.h | 6 ++- source/dialogs/drun.c | 91 +++++++++++++++++++++++++++++++++++++++---- source/xrmoptions.c | 4 +- 4 files changed, 102 insertions(+), 14 deletions(-) diff --git a/config/config.c b/config/config.c index fe8fcc42..6e256e53 100644 --- a/config/config.c +++ b/config/config.c @@ -116,14 +116,18 @@ Settings config = { .tokenize = TRUE, .matching = "normal", .matching_method = MM_NORMAL, - /** Desktop entry fields to match*/ + + /** Desktop entries to match in drun */ .drun_match_fields = "name,generic,exec,categories,keywords", + /** Only show entries in this category */ .drun_categories = NULL, - /** Desktop format display */ - .drun_display_format = "{name} [({generic})]", /** Desktop entry show actions */ .drun_show_actions = FALSE, - /** Desktop entry show actions */ + /** Desktop format display */ + .drun_display_format = "{name} [({generic})]", + /** Desktop Link launch command */ + .drun_url_launcher = "xdg-open", + /** Window fields to match in window mode*/ .window_match_fields = "all", /** Monitor */ @@ -160,8 +164,11 @@ Settings config = { .cache_dir = NULL, .window_thumbnail = FALSE, + + /** drun cache */ .drun_use_desktop_cache = FALSE, .drun_reload_desktop_cache = FALSE, + /** Benchmarks */ .benchmark_ui = FALSE }; diff --git a/include/settings.h b/include/settings.h index 7c57080d..cecb9c34 100644 --- a/include/settings.h +++ b/include/settings.h @@ -119,14 +119,18 @@ typedef struct SortingMethod sorting_method_enum; /** Sorting method. */ char * sorting_method; + /** Desktop entries to match in drun */ char * drun_match_fields; /** Only show entries in this category */ char * drun_categories; /** Desktop entry show actions */ unsigned int drun_show_actions; - /** Desktop entry show */ + /** Desktop format display */ char * drun_display_format; + /** Desktop Link launch command */ + char * drun_url_launcher; + /** Search case sensitivity */ unsigned int case_sensitive; /** Cycle through in the element list */ diff --git a/source/dialogs/drun.c b/source/dialogs/drun.c index afc19ba6..26fe73a1 100644 --- a/source/dialogs/drun.c +++ b/source/dialogs/drun.c @@ -61,6 +61,15 @@ char *DRUN_GROUP_NAME = "Desktop Entry"; typedef struct _DRunModePrivateData DRunModePrivateData; + +typedef enum +{ + DRUN_DESKTOP_ENTRY_TYPE_UNDETERMINED = 0, + DRUN_DESKTOP_ENTRY_TYPE_APPLICATION, + DRUN_DESKTOP_ENTRY_TYPE_LINK, + DRUN_DESKTOP_ENTRY_TYPE_DIRECTORY, +} DRunDesktopEntryType; + /** * Store extra information about the entry. * Currently the executable and if it should run in terminal. @@ -86,7 +95,7 @@ typedef struct int icon_size; /* Surface holding the icon. */ cairo_surface_t *icon; - /* Executable */ + /* Executable - for Application entries only */ char *exec; /* Name of the Entry */ char *name; @@ -104,6 +113,8 @@ typedef struct gint sort_index; uint32_t icon_fetch_uid; + + DRunDesktopEntryType type; } DRunModeEntry; typedef struct @@ -209,6 +220,43 @@ static gboolean drun_helper_eval_cb ( const GMatchInfo *info, GString *res, gpoi // Continue replacement. return FALSE; } +static void launch_link_entry ( DRunModeEntry *e ) +{ + if ( e->key_file == NULL ) { + GKeyFile *kf = g_key_file_new (); + GError *error = NULL; + gboolean res = g_key_file_load_from_file ( kf, e->path, 0, &error ); + if ( res ) { + e->key_file = kf; + } + else { + g_warning ( "[%s] [%s] Failed to parse desktop file because: %s.", e->app_id, e->path, error->message ); + g_error_free ( error ); + g_key_file_free ( kf ); + return; + } + } + + gchar *url = g_key_file_get_string ( e->key_file, e->action, "URL", NULL ); + if ( url == NULL || strlen ( url ) == 0 ) { + g_warning ( "[%s] [%s] No URL found.", e->app_id, e->path ); + g_free ( url ); + return ; + } + + gsize command_len = strlen( config.drun_url_launcher ) + strlen( url ) + 2; // space + terminator = 2 + gchar *command = g_newa ( gchar, command_len ); + g_snprintf( command, command_len, "%s %s", config.drun_url_launcher, url ); + g_free ( url ); + + g_debug ( "Link launch command: |%s|", command ); + if ( helper_execute_command ( NULL, command, FALSE, NULL ) ) { + char *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL ); + // Store it based on the unique identifiers (desktop_id). + history_set ( path, e->desktop_id ); + g_free ( path ); + } +} static void exec_cmd_entry ( DRunModeEntry *e ) { GError *error = NULL; @@ -300,6 +348,7 @@ static gboolean rofi_strv_contains ( const char * const *categories, const char */ static void read_desktop_file ( DRunModePrivateData *pd, const char *root, const char *path, const gchar *basename, const char *action ) { + DRunDesktopEntryType desktop_entry_type = DRUN_DESKTOP_ENTRY_TYPE_UNDETERMINED; int parse_action = ( config.drun_show_actions && action != DRUN_GROUP_NAME ); // Create ID on stack. // We know strlen (path ) > strlen(root)+1 @@ -342,8 +391,12 @@ static void read_desktop_file ( DRunModePrivateData *pd, const char *root, const g_key_file_free ( kf ); return; } - if ( g_strcmp0 ( key, "Application" ) ) { - g_debug ( "[%s] [%s] Skipping desktop file: Not of type application (%s)", id, path, key ); + if ( !g_strcmp0 ( key, "Application" ) ) { + desktop_entry_type = DRUN_DESKTOP_ENTRY_TYPE_APPLICATION; + } else if ( !g_strcmp0 ( key, "Link" ) ) { + desktop_entry_type = DRUN_DESKTOP_ENTRY_TYPE_LINK; + } else { + g_debug ( "[%s] [%s] Skipping desktop file: Not of type Application or Link (%s)", id, path, key ); g_free ( key ); g_key_file_free ( kf ); return; @@ -407,9 +460,17 @@ static void read_desktop_file ( DRunModePrivateData *pd, const char *root, const g_hash_table_add ( pd->disabled_entries, g_strdup ( id ) ); return; } + // We need Exec, don't support DBusActivatable - if ( !g_key_file_has_key ( kf, DRUN_GROUP_NAME, "Exec", NULL ) ) { - g_debug ( "[%s] [%s] Unsupported desktop file: no 'Exec' key present.", id, path ); + if ( desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_APPLICATION + && !g_key_file_has_key ( kf, DRUN_GROUP_NAME, "Exec", NULL ) ) { + g_debug ( "[%s] [%s] Unsupported desktop file: no 'Exec' key present for type Application.", id, path ); + g_key_file_free ( kf ); + return; + } + if ( desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_LINK + && !g_key_file_has_key ( kf, DRUN_GROUP_NAME, "URL", NULL ) ) { + g_debug ( "[%s] [%s] Unsupported desktop file: no 'URL' key present for type Link.", id, path ); g_key_file_free ( kf ); return; } @@ -499,7 +560,12 @@ static void read_desktop_file ( DRunModePrivateData *pd, const char *root, const } g_strfreev ( categories ); - pd->entry_list[pd->cmd_list_length].exec = g_key_file_get_string ( kf, action, "Exec", NULL ); + pd->entry_list[pd->cmd_list_length].type = desktop_entry_type; + if ( desktop_entry_type == DRUN_DESKTOP_ENTRY_TYPE_APPLICATION ) { + pd->entry_list[pd->cmd_list_length].exec = g_key_file_get_string ( kf, action, "Exec", NULL ); + } else { + pd->entry_list[pd->cmd_list_length].exec = NULL; + } if ( matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled ) { pd->entry_list[pd->cmd_list_length].comment = g_key_file_get_locale_string ( kf, @@ -953,7 +1019,16 @@ static ModeMode drun_mode_result ( Mode *sw, int mretv, char **input, unsigned i retv = ( mretv & MENU_LOWER_MASK ); } else if ( ( mretv & MENU_OK ) ) { - exec_cmd_entry ( &( rmpd->entry_list[selected_line] ) ); + switch ( rmpd->entry_list[selected_line].type ) { + case DRUN_DESKTOP_ENTRY_TYPE_APPLICATION: + exec_cmd_entry ( &( rmpd->entry_list[selected_line] ) ); + break; + case DRUN_DESKTOP_ENTRY_TYPE_LINK: + launch_link_entry ( &( rmpd->entry_list[selected_line] ) ); + break; + default: + g_assert_not_reached (); + } } else if ( ( mretv & MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] != '\0' ) { retv = RELOAD_DIALOG; @@ -1096,7 +1171,7 @@ static int drun_token_match ( const Mode *data, rofi_int_matcher **tokens, unsig } if ( matching_entry_fields[DRUN_MATCH_FIELD_EXEC].enabled ) { // Match executable name. - if ( test == tokens[j]->invert ) { + if ( test == tokens[j]->invert && rmpd->entry_list[index].exec ) { test = helper_token_match ( ftokens, rmpd->entry_list[index].exec ); } } diff --git a/source/xrmoptions.c b/source/xrmoptions.c index 18b925f4..94322edc 100644 --- a/source/xrmoptions.c +++ b/source/xrmoptions.c @@ -135,13 +135,15 @@ static XrmOption xrmOptions[] = { { xrm_String, "drun-match-fields", { .str = &config.drun_match_fields }, NULL, "Desktop entry fields to match in drun", CONFIG_DEFAULT }, - { xrm_String, "drun-categories", { .str = &config.drun_categories }, NULL, "Only show Desktop entry from these categories", CONFIG_DEFAULT }, { xrm_Boolean, "drun-show-actions", { .num = &config.drun_show_actions }, NULL, "Desktop entry show actions.", CONFIG_DEFAULT }, { xrm_String, "drun-display-format", { .str = &config.drun_display_format }, NULL, "DRUN format string. (Supports: generic,name,comment,exec,categories)", CONFIG_DEFAULT }, + { xrm_String, "drun-url-launcher", { .str = &config.drun_url_launcher }, NULL, + "Command to open an Desktop Entry that is a Link.", CONFIG_DEFAULT }, + { xrm_Boolean, "disable-history", { .num = &config.disable_history }, NULL, "Disable history in run/ssh", CONFIG_DEFAULT }, { xrm_String, "ignored-prefixes", { .str = &config.ignored_prefixes }, NULL,