C code: save all instead of just one client icons

Clients can provide various icons in their _NET_WM_ICON property. Up to
now we only saved a single one, now we save all of them.

Signed-off-by: Uli Schlachter <psychon@znc.in>
This commit is contained in:
Uli Schlachter 2017-03-06 15:50:51 +01:00 committed by Daniel Hahler
parent 862bfbf795
commit f2cb8d8eb9
6 changed files with 111 additions and 67 deletions

8
draw.h
View file

@ -27,6 +27,7 @@
#include <lua.h>
#include <glib.h> /* 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);

77
ewmh.c
View file

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

3
ewmh.h
View file

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

View file

@ -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;
}

View file

@ -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 *);

View file

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