diff --git a/config/config.c b/config/config.c index 0e72e894..1792583c 100644 --- a/config/config.c +++ b/config/config.c @@ -58,6 +58,8 @@ Settings config = { .run_shell_command = "{terminal} -e {cmd}", /** Command executed on accep-entry-custom for window modus */ .window_command = "xkill -id {window}", + /** Sane default for an icon theme */ + .drun_icon_theme = "gnome", /** * Location of the window. * Enumeration indicating location or gravity of window. diff --git a/configure.ac b/configure.ac index 6a9d123b..6feb5de5 100644 --- a/configure.ac +++ b/configure.ac @@ -116,7 +116,7 @@ PKG_PROG_PKG_CONFIG dnl --------------------------------------------------------------------- dnl PKG_CONFIG based dependencies dnl --------------------------------------------------------------------- -NK_INIT([bindings]) +NK_INIT([bindings xdg-theme]) PKG_CHECK_MODULES([glib], [glib-2.0 >= 2.40 gio-unix-2.0 gmodule-2.0]) GW_CHECK_XCB([xcb-aux xcb-xkb xkbcommon xkbcommon-x11 xcb-ewmh xcb-icccm xcb-xrm xcb-randr xcb-xinerama]) PKG_CHECK_MODULES([pango], [pango pangocairo]) diff --git a/include/settings.h b/include/settings.h index f18be7a9..938d6a08 100644 --- a/include/settings.h +++ b/include/settings.h @@ -111,6 +111,8 @@ typedef struct char * run_list_command; /** Command for window */ char * window_command; + /** Theme for icons */ + char * drun_icon_theme; /** Windows location/gravity */ WindowLocation location; diff --git a/meson.build b/meson.build index cb4c76bc..8fa9d2d9 100644 --- a/meson.build +++ b/meson.build @@ -104,6 +104,7 @@ config_h = configure_file(output: 'config.h', configuration: header_conf) nk_modules = [ 'bindings=true', + 'xdg-theme=true', ] nk = subproject('libnkutils', default_options: nk_modules) nk_options = nk.get_variable('nk_options') diff --git a/source/dialogs/drun.c b/source/dialogs/drun.c index f17fa1ae..941a0b33 100644 --- a/source/dialogs/drun.c +++ b/source/dialogs/drun.c @@ -49,6 +49,7 @@ #include "widgets/textbox.h" #include "history.h" #include "dialogs/drun.h" +#include "nkutils-xdg-theme.h" #define DRUN_CACHE_FILE "rofi2.druncache" @@ -61,31 +62,35 @@ typedef struct { /* Root */ - char *root; + char *root; /* Path to desktop file */ - char *path; + char *path; + /* Icon stuff */ + char *icon_name; + cairo_surface_t *icon; /* Executable */ - char *exec; + char *exec; /* Name of the Entry */ - char *name; + char *name; /* Generic Name */ - char *generic_name; + char *generic_name; #ifdef GET_CAT_PARSE_TIME - char **categories; + char **categories; #endif - GKeyFile *key_file; + GKeyFile *key_file; } DRunModeEntry; typedef struct { - DRunModeEntry *entry_list; - unsigned int cmd_list_length; - unsigned int cmd_list_length_actual; - unsigned int history_length; + NkXdgThemeContext *xdg_context; + DRunModeEntry *entry_list; + unsigned int cmd_list_length; + unsigned int cmd_list_length_actual; + unsigned int history_length; // List of disabled entries. - GHashTable *disabled_entries; - unsigned int disabled_entries_length; + GHashTable *disabled_entries; + unsigned int disabled_entries_length; } DRunModePrivateData; struct RegexEvalArg @@ -279,6 +284,9 @@ static gboolean read_desktop_file ( DRunModePrivateData *pd, const char *root, c #endif pd->entry_list[pd->cmd_list_length].exec = g_key_file_get_string ( kf, "Desktop Entry", "Exec", NULL ); + pd->entry_list[pd->cmd_list_length].icon_name = g_key_file_get_locale_string ( kf, "Desktop Entry", "Icon", NULL, NULL ); + pd->entry_list[pd->cmd_list_length].icon = NULL; + // Keep keyfile around. pd->entry_list[pd->cmd_list_length].key_file = kf; // We don't want to parse items with this id anymore. @@ -411,6 +419,7 @@ static int drun_mode_init ( Mode *sw ) DRunModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) ); pd->disabled_entries = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL ); mode_set_private_data ( sw, (void *) pd ); + pd->xdg_context = nk_xdg_theme_context_new (); get_apps ( pd ); } return TRUE; @@ -419,6 +428,10 @@ static void drun_entry_clear ( DRunModeEntry *e ) { g_free ( e->root ); g_free ( e->path ); + if ( e->icon != NULL ) { + cairo_surface_destroy ( e->icon ); + } + g_free ( e->icon_name ); g_free ( e->exec ); g_free ( e->name ); g_free ( e->generic_name ); @@ -471,6 +484,7 @@ static void drun_mode_destroy ( Mode *sw ) } g_hash_table_destroy ( rmpd->disabled_entries ); g_free ( rmpd->entry_list ); + nk_xdg_theme_context_free ( rmpd->xdg_context ); g_free ( rmpd ); mode_set_private_data ( sw, NULL ); } @@ -489,14 +503,75 @@ static char *_get_display_value ( const Mode *sw, unsigned int selected_line, in } /* Free temp storage. */ DRunModeEntry *dr = &( pd->entry_list[selected_line] ); + /* We use '\t' as the icon placeholder for now */ if ( dr->generic_name == NULL ) { - return g_markup_escape_text ( dr->name, -1 ); + return g_markup_printf_escaped ( "\uFFFC%s", dr->name ); } else { - return g_markup_printf_escaped ( "%s (%s)", dr->name, - dr->generic_name ); + return g_markup_printf_escaped ( "\uFFFC%s (%s)", + dr->name, dr->generic_name ); } } + +static cairo_surface_t *_get_icon ( const Mode *sw, unsigned int selected_line, int height ) +{ + DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw ); + g_return_val_if_fail ( pd->entry_list != NULL, NULL ); + DRunModeEntry *dr = &( pd->entry_list[selected_line] ); + if ( dr->icon != NULL ) { + return dr->icon; + } + if ( dr->icon_name == NULL ) { + return NULL; + } + + gchar *icon_path; + + if ( g_path_is_absolute ( dr->icon_name ) ) { + icon_path = dr->icon_name; + } + else { + const gchar *name = dr->icon_name; + if ( g_str_has_suffix ( name, ".png" ) || g_str_has_suffix ( name, ".svg" ) || g_str_has_suffix ( name, ".xpm" ) ) { + /* We truncate the extension if the .desktop file is not compliant + * We cannot just strip at '.' because D-Bus-styled names are now common. + */ + gchar *c = g_utf8_strrchr ( name, -1, '.' ); + g_assert_nonnull ( c ); + *c = '\0'; + c = g_utf8_strchr ( name, -1, G_DIR_SEPARATOR ); + if ( c != NULL ) { + /* And just in case, we strip any path component too */ + *c = '\0'; + name = ++c; + } + } + icon_path = nk_xdg_theme_get_icon ( pd->xdg_context, config.drun_icon_theme, "Applications", name, height, 1, TRUE ); + if ( icon_path != NULL ) { + g_debug ( "Found Icon %s(%d): %s", name, height, icon_path ); + } + g_free ( dr->icon_name ); + } + dr->icon_name = NULL; + + if ( icon_path == NULL ) { + return NULL; + } + + if ( g_str_has_suffix ( icon_path, ".png" ) ) { + dr->icon = cairo_image_surface_create_from_png ( icon_path ); + } + else if ( g_str_has_suffix ( icon_path, ".svg" ) ) { + dr->icon = cairo_image_surface_create_from_svg ( icon_path, height ); + } + else { + g_debug ( "Icon type not yet supported: %s", icon_path ); + } + + g_free ( icon_path ); + return dr->icon; +} + static char *drun_get_completion ( const Mode *sw, unsigned int index ) { DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw ); @@ -572,6 +647,7 @@ Mode drun_mode = ._token_match = drun_token_match, ._get_completion = drun_get_completion, ._get_display_value = _get_display_value, + ._get_icon = _get_icon, ._preprocess_input = NULL, .private_data = NULL, .free = NULL diff --git a/source/xrmoptions.c b/source/xrmoptions.c index ef0c7dfe..aeb04921 100644 --- a/source/xrmoptions.c +++ b/source/xrmoptions.c @@ -119,6 +119,8 @@ static XrmOption xrmOptions[] = { "Run command to execute that runs in shell", CONFIG_DEFAULT }, { xrm_String, "window-command", { .str = &config.window_command }, NULL, "Command executed on accep-entry-custom for window modus", CONFIG_DEFAULT }, + { xrm_String, "drun-icon-theme", { .str = &config.drun_icon_theme }, NULL, + "Theme to use to look for icons", CONFIG_DEFAULT }, { xrm_Boolean, "disable-history", { .num = &config.disable_history }, NULL, "Disable history in run/ssh", CONFIG_DEFAULT },