diff --git a/draw.h b/draw.h index 224a9c5ef..0a414752c 100644 --- a/draw.h +++ b/draw.h @@ -27,6 +27,7 @@ #include #include /* for GError */ +#include "common/array.h" #include "common/util.h" typedef struct area_t area_t; @@ -69,6 +70,13 @@ a_iso2utf8(const char *str, ssize_t len, char **dest, ssize_t *dlen) return false; } +static inline void +cairo_surface_array_destroy_surface(cairo_surface_t **s) +{ + cairo_surface_destroy(*s); +} +DO_ARRAY(cairo_surface_t *, cairo_surface, cairo_surface_array_destroy_surface) + cairo_surface_t *draw_surface_from_data(int width, int height, uint32_t *data); cairo_surface_t *draw_dup_image_surface(cairo_surface_t *surface); cairo_surface_t *draw_load_image(lua_State *L, const char *path, GError **error); diff --git a/ewmh.c b/ewmh.c index 608276478..c2811bb1e 100644 --- a/ewmh.c +++ b/ewmh.c @@ -676,63 +676,62 @@ ewmh_window_icon_get_unchecked(xcb_window_t w) } static cairo_surface_t * -ewmh_window_icon_from_reply(xcb_get_property_reply_t *r, uint32_t preferred_size) +ewmh_window_icon_from_reply_next(uint32_t **data, uint32_t *data_end) { - uint32_t *data, *end, *found_data = 0; - uint32_t found_size = 0; + uint32_t width, height; + uint64_t data_len; + uint32_t *icon_data; - if(!r || r->type != XCB_ATOM_CARDINAL || r->format != 32 || r->length < 2) - return 0; + if(data_end - *data <= 2) + return NULL; - data = (uint32_t *) xcb_get_property_value(r); - if (!data) return 0; + width = (*data)[0]; + height = (*data)[1]; - end = data + r->length; + /* Check that we have enough data, handling overflow */ + data_len = width * (uint64_t) height; + if (width < 1 || height < 1 || data_len > (uint64_t) (data_end - *data) - 2) + return NULL; - /* 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; + icon_data = *data + 2; + *data += 2 + data_len; + return draw_surface_from_data(width, height, icon_data); +} - /* use the greater of the two dimensions to match against the preferred size */ - uint32_t size = MAX(data[0], data[1]); +static cairo_surface_array_t +ewmh_window_icon_from_reply(xcb_get_property_reply_t *r) +{ + uint32_t *data, *data_end; + cairo_surface_array_t result; + cairo_surface_t *s; - /* pick the icon if it's a better match than the one we already have */ - bool found_icon_too_small = found_size < preferred_size; - bool found_icon_too_large = found_size > preferred_size; - bool icon_empty = data[0] == 0 || data[1] == 0; - bool better_because_bigger = found_icon_too_small && size > found_size; - bool 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; - } + cairo_surface_array_init(&result); + if(!r || r->type != XCB_ATOM_CARDINAL || r->format != 32) + return result; - data += data_size + 2; + data = (uint32_t*) xcb_get_property_value(r); + data_end = &data[r->length]; + if(!data) + return result; + + while ((s = ewmh_window_icon_from_reply_next(&data, data_end)) != NULL) { + cairo_surface_array_push(&result, s); } - if (!found_data) return 0; - - return draw_surface_from_data(found_data[0], found_data[1], found_data + 2); + return result; } /** Get NET_WM_ICON. * \param cookie The cookie. - * \return The number of elements on stack. + * \return An array of icons. */ -cairo_surface_t * -ewmh_window_icon_get_reply(xcb_get_property_cookie_t cookie, uint32_t preferred_size) +cairo_surface_array_t +ewmh_window_icon_get_reply(xcb_get_property_cookie_t cookie) { xcb_get_property_reply_t *r = xcb_get_property_reply(globalconf.connection, cookie, NULL); - cairo_surface_t *surface = ewmh_window_icon_from_reply(r, preferred_size); + cairo_surface_array_t result = ewmh_window_icon_from_reply(r); p_delete(&r); - return surface; + return result; } // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/ewmh.h b/ewmh.h index 67e3f3aeb..f7d72b918 100644 --- a/ewmh.h +++ b/ewmh.h @@ -28,6 +28,7 @@ #include "strut.h" typedef struct client_t client_t; +typedef struct cairo_surface_array_t cairo_surface_array_t; void ewmh_init(void); void ewmh_init_lua(void); @@ -42,7 +43,7 @@ void ewmh_process_client_strut(client_t *); void ewmh_update_strut(xcb_window_t, strut_t *); void ewmh_update_window_type(xcb_window_t window, uint32_t type); xcb_get_property_cookie_t ewmh_window_icon_get_unchecked(xcb_window_t); -cairo_surface_t *ewmh_window_icon_get_reply(xcb_get_property_cookie_t, uint32_t preferred_size); +cairo_surface_array_t ewmh_window_icon_get_reply(xcb_get_property_cookie_t); #endif // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/objects/client.c b/objects/client.c index 7683aa4c4..cb201a6c8 100644 --- a/objects/client.c +++ b/objects/client.c @@ -826,6 +826,7 @@ client_wipe(client_t *c) { key_array_wipe(&c->keys); xcb_icccm_get_wm_protocols_reply_wipe(&c->protocols); + cairo_surface_array_wipe(&c->icons); p_delete(&c->machine); p_delete(&c->class); p_delete(&c->instance); @@ -834,8 +835,6 @@ client_wipe(client_t *c) p_delete(&c->name); p_delete(&c->alt_name); p_delete(&c->startup_id); - if(c->icon) - cairo_surface_destroy(c->icon); } /** Change the clients urgency flag. @@ -2319,27 +2318,38 @@ luaA_client_isvisible(lua_State *L) return 1; } +/** Set client icons. + * \param L The Lua VM state. + * \param array Array of icons to set. + */ +void +client_set_icons(client_t *c, cairo_surface_array_t array) +{ + cairo_surface_array_wipe(&c->icons); + c->icons = array; + + lua_State *L = globalconf_get_lua_State(); + luaA_object_push(L, c); + luaA_object_emit_signal(L, -1, "property::icon", 0); + lua_pop(L, 1); +} + /** Set a client icon. * \param L The Lua VM state. * \param cidx The client index on the stack. * \param iidx The image index on the stack. */ -void +static void client_set_icon(client_t *c, cairo_surface_t *s) { - lua_State *L = globalconf_get_lua_State(); - - if (s) - s = draw_dup_image_surface(s); - if(c->icon) - cairo_surface_destroy(c->icon); - c->icon = s; - - luaA_object_push(L, c); - luaA_object_emit_signal(L, -1, "property::icon", 0); - lua_pop(L, 1); + cairo_surface_array_t array; + cairo_surface_array_init(&array); + if (s && cairo_surface_status(s) == CAIRO_STATUS_SUCCESS) + cairo_surface_array_push(&array, draw_dup_image_surface(s)); + client_set_icons(c, array); } + /** Set a client icon. * \param c The client to change. * \param icon A bitmap containing the icon. @@ -3079,10 +3089,38 @@ luaA_client_get_content(lua_State *L, client_t *c) static int luaA_client_get_icon(lua_State *L, client_t *c) { - if(!c->icon) + if(c->icons.len == 0) return 0; + + /* Pick the closest available size, only picking a smaller icon if no bigger + * one is available. + */ + cairo_surface_t *found = NULL; + int found_size = 0; + int preferred_size = globalconf.preferred_icon_size; + + foreach(surf, c->icons) + { + int width = cairo_image_surface_get_width(*surf); + int height = cairo_image_surface_get_height(*surf); + int size = MAX(width, height); + + /* pick the icon if it's a better match than the one we already have */ + bool found_icon_too_small = found_size < preferred_size; + bool found_icon_too_large = found_size > preferred_size; + bool icon_empty = width == 0 || height == 0; + bool better_because_bigger = found_icon_too_small && size > found_size; + bool 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 = *surf; + found_size = size; + } + } + /* lua gets its own reference which it will have to destroy */ - lua_pushlightuserdata(L, cairo_surface_reference(c->icon)); + lua_pushlightuserdata(L, cairo_surface_reference(found)); return 1; } diff --git a/objects/client.h b/objects/client.h index ad94e1333..ee7384988 100644 --- a/objects/client.h +++ b/objects/client.h @@ -116,8 +116,8 @@ struct client_t xcb_icccm_get_wm_protocols_reply_t protocols; /** Key bindings */ key_array_t keys; - /** Icon */ - cairo_surface_t *icon; + /** Icons */ + cairo_surface_array_t icons; /** True if we ever got an icon from _NET_WM_ICON */ bool have_ewmh_icon; /** Size hints */ @@ -186,7 +186,7 @@ void client_set_transient_for(lua_State *L, int, client_t *); void client_set_name(lua_State *L, int, char *); void client_set_alt_name(lua_State *L, int, char *); void client_set_group_window(lua_State *, int, xcb_window_t); -void client_set_icon(client_t *, cairo_surface_t *); +void client_set_icons(client_t *, cairo_surface_array_t); void client_set_icon_from_pixmaps(client_t *, xcb_pixmap_t, xcb_pixmap_t); void client_set_skip_taskbar(lua_State *, int, bool); void client_focus(client_t *); diff --git a/property.c b/property.c index de000d77a..e57effb40 100644 --- a/property.c +++ b/property.c @@ -206,8 +206,6 @@ property_update_wm_hints(client_t *c, xcb_get_property_cookie_t cookie) else client_set_icon_from_pixmaps(c, wmh.icon_pixmap, XCB_NONE); } - else - client_set_icon(c, NULL); } lua_pop(L, 1); @@ -262,14 +260,14 @@ property_get_net_wm_icon(client_t *c) void property_update_net_wm_icon(client_t *c, xcb_get_property_cookie_t cookie) { - cairo_surface_t *surface = ewmh_window_icon_get_reply(cookie, globalconf.preferred_icon_size); - - if(!surface) + cairo_surface_array_t array = ewmh_window_icon_get_reply(cookie); + if (array.len == 0) + { + cairo_surface_array_wipe(&array); return; - + } c->have_ewmh_icon = true; - client_set_icon(c, surface); - cairo_surface_destroy(surface); + client_set_icons(c, array); } xcb_get_property_cookie_t