From f3b4af1da0c6cea3dbab3d5eca2ac13b6019e4b2 Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Wed, 12 Apr 2017 18:19:58 +0200 Subject: window: Add icons support (quick and dirty) --- source/dialogs/window.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++-- source/helper.c | 10 ++++ 2 files changed, 136 insertions(+), 4 deletions(-) (limited to 'source') diff --git a/source/dialogs/window.c b/source/dialogs/window.c index 535c2e72..1e3611e6 100644 --- a/source/dialogs/window.c +++ b/source/dialogs/window.c @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -77,6 +78,8 @@ typedef struct long hint_flags; uint32_t wmdesktop; char *wmdesktopstr; + cairo_surface_t *icon; + gboolean icon_checked; } client; // window lists @@ -149,6 +152,9 @@ static void winlist_empty ( winlist *l ) while ( l->len > 0 ) { client *c = l->data[--l->len]; if ( c != NULL ) { + if ( c->icon ) { + cairo_surface_destroy ( c->icon ); + } g_free ( c->title ); g_free ( c->class ); g_free ( c->name ); @@ -295,18 +301,18 @@ static client* window_client ( ModeModePrivateData *pd, xcb_window_t win ) c->title = window_get_text_prop ( c->window, xcb->ewmh._NET_WM_NAME ); if ( c->title == NULL ) { - c->title = window_get_text_prop ( c->window, XCB_ATOM_WM_NAME ); + c->title = rofi_escape_markup ( window_get_text_prop ( c->window, XCB_ATOM_WM_NAME ) ); } pd->title_len = MAX ( c->title ? g_utf8_strlen ( c->title, -1 ) : 0, pd->title_len ); - c->role = window_get_text_prop ( c->window, netatoms[WM_WINDOW_ROLE] ); + c->role = rofi_escape_markup ( window_get_text_prop ( c->window, netatoms[WM_WINDOW_ROLE] ) ); pd->role_len = MAX ( c->role ? g_utf8_strlen ( c->role, -1 ) : 0, pd->role_len ); cky = xcb_icccm_get_wm_class ( xcb->connection, c->window ); xcb_icccm_get_wm_class_reply_t wcr; if ( xcb_icccm_get_wm_class_reply ( xcb->connection, cky, &wcr, NULL ) ) { - c->class = rofi_latin_to_utf8_strdup ( wcr.class_name, -1 ); - c->name = rofi_latin_to_utf8_strdup ( wcr.instance_name, -1 ); + c->class = rofi_escape_markup ( rofi_latin_to_utf8_strdup ( wcr.class_name, -1 ) ); + c->name = rofi_escape_markup ( rofi_latin_to_utf8_strdup ( wcr.instance_name, -1 ) ); pd->name_len = MAX ( c->name ? g_utf8_strlen ( c->name, -1 ) : 0, pd->name_len ); xcb_icccm_get_wm_class_reply_wipe ( &wcr ); } @@ -686,6 +692,9 @@ static gboolean helper_eval_cb ( const GMatchInfo *info, GString *str, gpointer if ( match[1] == 'w' ) { helper_eval_add_str ( str, d->c->wmdesktopstr, l, d->pd->wmdn_len ); } + else if ( match[1] == 'i' ) { + g_string_append ( str, "\uFFFC" ); + } else if ( match[1] == 'c' ) { helper_eval_add_str ( str, d->c->class, l, d->pd->clf_len ); } @@ -717,6 +726,7 @@ static char *_get_display_value ( const Mode *sw, unsigned int selected_line, in if ( c == NULL ) { return get_entry ? g_strdup ( "Window has fanished" ) : NULL; } + *state |= MARKUP; if ( c->demands ) { *state |= URGENT; } @@ -726,6 +736,116 @@ static char *_get_display_value ( const Mode *sw, unsigned int selected_line, in return get_entry ? _generate_display_string ( rmpd, c ) : NULL; } +/** + * Icon code borrowed from https://github.com/olejorgenb/extract-window-icon + */ +static cairo_user_data_key_t data_key; + +/** Create a surface object from this image data. + * \param width The width of the image. + * \param height The height of the image + * \param data The image's data in ARGB format, will be copied by this function. + */ +static cairo_surface_t * draw_surface_from_data ( int width, int height, uint32_t *data ) +{ + unsigned long int len = width * height; + unsigned long int i; + uint32_t *buffer = g_new0 ( uint32_t, len ); + cairo_surface_t *surface; + + /* Cairo wants premultiplied alpha, meh :( */ + for ( i = 0; i < len; i++ ) { + uint8_t a = ( data[i] >> 24 ) & 0xff; + double alpha = a / 255.0; + uint8_t r = ( ( data[i] >> 16 ) & 0xff ) * alpha; + uint8_t g = ( ( data[i] >> 8 ) & 0xff ) * alpha; + uint8_t b = ( ( data[i] >> 0 ) & 0xff ) * alpha; + buffer[i] = ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b; + } + + surface = cairo_image_surface_create_for_data ( (unsigned char *) buffer, + CAIRO_FORMAT_ARGB32, + width, + height, + width * 4 ); + /* This makes sure that buffer will be freed */ + cairo_surface_set_user_data ( surface, &data_key, buffer, g_free ); + + return surface; +} +static cairo_surface_t * ewmh_window_icon_from_reply ( xcb_get_property_reply_t *r, uint32_t preferred_size ) +{ + uint32_t *data, *end, *found_data = 0; + uint32_t found_size = 0; + + if ( !r || r->type != XCB_ATOM_CARDINAL || r->format != 32 || r->length < 2 ) { + return 0; + } + + data = (uint32_t *) xcb_get_property_value ( r ); + if ( !data ) { + return 0; + } + + end = data + r->length; + + /* Goes over the icon data and picks the icon that best matches the size preference. + * In case the size match is not exact, picks the closest bigger size if present, + * closest smaller size otherwise. + */ + while ( data + 1 < end ) { + /* check whether the data size specified by width and height fits into the array we got */ + uint64_t data_size = (uint64_t) data[0] * data[1]; + if ( data_size > (uint64_t) ( end - data - 2 ) ) { + break; + } + + /* use the greater of the two dimensions to match against the preferred size */ + uint32_t size = MAX ( data[0], data[1] ); + + /* pick the icon if it's a better match than the one we already have */ + gboolean found_icon_too_small = found_size < preferred_size; + gboolean found_icon_too_large = found_size > preferred_size; + gboolean icon_empty = data[0] == 0 || data[1] == 0; + gboolean better_because_bigger = found_icon_too_small && size > found_size; + gboolean better_because_smaller = found_icon_too_large && + size >= preferred_size && size < found_size; + if ( !icon_empty && ( better_because_bigger || better_because_smaller || found_size == 0 ) ) { + found_data = data; + found_size = size; + } + + data += data_size + 2; + } + + if ( !found_data ) { + return 0; + } + + return draw_surface_from_data ( found_data[0], found_data[1], found_data + 2 ); +} +/** Get NET_WM_ICON. */ +static cairo_surface_t * get_net_wm_icon ( xcb_window_t xid, uint32_t preferred_size ) +{ + xcb_get_property_cookie_t cookie = xcb_get_property_unchecked ( + xcb->connection, FALSE, xid, + xcb->ewmh._NET_WM_ICON, XCB_ATOM_CARDINAL, 0, UINT32_MAX ); + xcb_get_property_reply_t *r = xcb_get_property_reply ( xcb->connection, cookie, NULL ); + cairo_surface_t *surface = ewmh_window_icon_from_reply ( r, preferred_size ); + free ( r ); + return surface; +} +static cairo_surface_t *_get_icon ( const Mode *sw, unsigned int selected_line, int size ) +{ + ModeModePrivateData *rmpd = mode_get_private_data ( sw ); + client *c = window_client ( rmpd, rmpd->ids->array[selected_line] ); + if ( c->icon_checked == FALSE ) { + c->icon = get_net_wm_icon ( rmpd->ids->array[selected_line], size ); + c->icon_checked = TRUE; + } + return c->icon; +} + #include "mode-private.h" Mode window_mode = { @@ -737,6 +857,7 @@ Mode window_mode = ._destroy = window_mode_destroy, ._token_match = window_match, ._get_display_value = _get_display_value, + ._get_icon = _get_icon, ._get_completion = NULL, ._preprocess_input = NULL, .private_data = NULL, @@ -752,6 +873,7 @@ Mode window_mode_cd = ._destroy = window_mode_destroy, ._token_match = window_match, ._get_display_value = _get_display_value, + ._get_icon = _get_icon, ._get_completion = NULL, ._preprocess_input = NULL, .private_data = NULL, diff --git a/source/helper.c b/source/helper.c index f6b3b74d..5299ced3 100644 --- a/source/helper.c +++ b/source/helper.c @@ -730,6 +730,16 @@ char * rofi_latin_to_utf8_strdup ( const char *input, gssize length ) return g_convert_with_fallback ( input, length, "UTF-8", "latin1", "\uFFFD", NULL, &slength, NULL ); } +gchar *rofi_escape_markup ( gchar *text ) +{ + if ( text == NULL ) { + return NULL; + } + gchar *ret = g_markup_escape_text ( text, -1 ); + g_free ( text ); + return ret; +} + char * rofi_force_utf8 ( const gchar *data, ssize_t length ) { if ( data == NULL ) { -- cgit v1.2.3