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
This commit is contained in:
Mike Dalessio 2020-08-26 15:10:04 -04:00 committed by GitHub
parent 120ce36055
commit 5bec191d2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 102 additions and 14 deletions

View File

@ -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} [<span weight='light' size='small'><i>({generic})</i></span>]",
/** Desktop entry show actions */
.drun_show_actions = FALSE,
/** Desktop entry show actions */
/** Desktop format display */
.drun_display_format = "{name} [<span weight='light' size='small'><i>({generic})</i></span>]",
/** 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
};

View File

@ -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 */

View File

@ -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 );
}
}

View File

@ -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,