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