window: Add icons support (quick and dirty)

This commit is contained in:
Dave Davenport 2017-04-12 18:19:58 +02:00 committed by Quentin Glidic
parent 03eb4a3abb
commit f3b4af1da0
No known key found for this signature in database
GPG Key ID: AC203F96E2C34BB7
4 changed files with 146 additions and 5 deletions

View File

@ -111,7 +111,7 @@ Settings config = {
.dpi = -1,
.threads = 0,
.scroll_method = 0,
.window_format = "{w} {c} {t}",
.window_format = "{w} {i}{c} {t}",
.click_to_exit = TRUE,
.show_match = TRUE,
.theme = NULL,

View File

@ -220,6 +220,15 @@ char * rofi_force_utf8 ( const gchar *data, ssize_t length );
*/
char * rofi_latin_to_utf8_strdup ( const char *input, gssize length );
/**
* @param input the string to escape
*
* Escape XML markup from the string. @param input is freed.
*
* @return the escaped string
*/
gchar *rofi_escape_markup ( gchar *input );
/**
* @param pattern The user input to match against.
* @param plen Pattern length.

View File

@ -33,6 +33,7 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
@ -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, "<span alpha=\"1\">\uFFFC</span>" );
}
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,

View File

@ -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 ) {