Merge pull request #1636 from psychon/client-icons2

Add support for multiple client icons
This commit is contained in:
Emmanuel Lepage Vallée 2017-03-11 18:59:09 -05:00 committed by GitHub
commit 4a42ed0d44
9 changed files with 294 additions and 75 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

@ -12,6 +12,7 @@ local gmath = require("gears.math")
local abutton = require("awful.button")
local aclient = require("awful.client")
local atooltip = require("awful.tooltip")
local clienticon = require("awful.widget.clienticon")
local beautiful = require("beautiful")
local drawable = require("wibox.drawable")
local imagebox = require("wibox.widget.imagebox")
@ -572,14 +573,7 @@ end
-- @param c The client for which an icon widget should be created.
-- @return The icon widget.
function titlebar.widget.iconwidget(c)
local ret = imagebox()
local function update()
ret:set_image(c.icon)
end
c:connect_signal("property::icon", update)
update()
return ret
return clienticon(c)
end
--- Create a new button widget. A button widget displays an image and reacts to

View File

@ -0,0 +1,118 @@
--- Container showing the icon of a client.
-- @author Uli Schlachter
-- @copyright 2017 Uli Schlachter
-- @classmod awful.widget.clienticon
local base = require("wibox.widget.base")
local surface = require("gears.surface")
local util = require("awful.util")
local clienticon = {}
local instances = setmetatable({}, { __mode = "k" })
local function find_best_icon(sizes, width, height)
local best, best_size
for k, size in ipairs(sizes) do
if not best then
best, best_size = k, size
else
local best_too_small = best_size[1] < width or best_size[2] < height
local best_too_large = best_size[1] > width or best_size[2] > height
local better_because_bigger = best_too_small and size[1] > best_size[1] and size[2] > best_size[2]
local better_because_smaller = best_too_large and size[1] < best_size[1] and size[2] < best_size[2]
and size[1] >= width and size[2] >= height
if better_because_bigger or better_because_smaller then
best, best_size = k, size
end
end
end
return best, best_size
end
function clienticon:draw(_, cr, width, height)
local c = self._private.client
if not c.valid then
return
end
local index, size = find_best_icon(c.icon_sizes, width, height)
if not index then
return
end
local aspect_w = width / size[1]
local aspect_h = height / size[2]
local aspect = math.min(aspect_w, aspect_h)
cr:scale(aspect, aspect)
local s = surface(c:get_icon(index))
cr:set_source_surface(s, 0, 0)
cr:paint()
end
function clienticon:fit(_, width, height)
local c = self._private.client
if not c.valid then
return 0, 0
end
local index, size = find_best_icon(c.icon_sizes, width, height)
if not index then
return 0, 0
end
local w, h = size[1], size[2]
if w > width then
h = h * width / w
w = width
end
if h > height then
w = w * height / h
h = height
end
if h == 0 or w == 0 then
return 0, 0
end
local aspect = math.min(width / w, height / h)
return w * aspect, h * aspect
end
--- Returns a new clienticon.
-- @tparam client c The client whose icon should be displayed.
-- @treturn widget A new `widget`
-- @function awful.widget.clienticon
local function new(c)
local ret = base.make_widget(nil, nil, {enable_properties = true})
util.table.crush(ret, clienticon, true)
ret._private.client = c
instances[ret] = true
return ret
end
client.connect_signal("property::icon", function(c)
for obj in pairs(instances) do
if obj._private.client.valid and obj._private.client == c then
obj:emit_signal("widget::layout_changed")
obj:emit_signal("widget::redraw_needed")
end
end
end)
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(clienticon, {
__call = function(_, ...)
return new(...)
end
})
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -20,6 +20,7 @@ return
keyboardlayout = require("awful.widget.keyboardlayout");
watch = require("awful.widget.watch");
only_on_screen = require("awful.widget.only_on_screen");
clienticon = require("awful.widget.clienticon");
}
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -381,6 +381,19 @@
* @param surface
*/
/**
* The available sizes of client icons. This is a table where each entry
* contains the width and height of an icon.
*
* **Signal:**
*
* * *property::icon*
*
* @property icon_sizes
* @tparam table sizes
* @see `get_icon`
*/
/**
* Client screen.
*
@ -826,6 +839,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 +848,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 +2331,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 +3102,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;
}
@ -3387,6 +3438,50 @@ luaA_client_keys(lua_State *L)
return luaA_key_array_get(L, 1, keys);
}
static int
luaA_client_get_icon_sizes(lua_State *L)
{
int index = 1;
client_t *c = luaA_checkudata(L, 1, &client_class);
lua_newtable(L);
foreach (s, c->icons) {
/* Create a table { width, height } and append it to the table */
lua_createtable(L, 2, 0);
lua_pushinteger(L, cairo_image_surface_get_width(*s));
lua_rawseti(L, -2, 1);
lua_pushinteger(L, cairo_image_surface_get_height(*s));
lua_rawseti(L, -2, 2);
lua_rawseti(L, -2, index++);
}
return 1;
}
/** Get the client's n-th icon.
*
* **Signal:**
*
* * *property::icon*
*
* @tparam interger index The index in the list of icons to get.
* @treturn surface A lightuserdata for a cairo surface. This reference must be
* destroyed!
* @function get_icon
*/
static int
luaA_client_get_some_icon(lua_State *L)
{
client_t *c = luaA_checkudata(L, 1, &client_class);
int index = luaL_checkinteger(L, 2);
luaL_argcheck(L, (index >= 1 && index <= c->icons.len), 2,
"invalid icon index");
lua_pushlightuserdata(L, cairo_surface_reference(c->icons.tab[index-1]));
return 1;
}
static int
client_tostring(lua_State *L, client_t *c)
{
@ -3472,6 +3567,7 @@ client_class_setup(lua_State *L)
{ "titlebar_right", luaA_client_titlebar_right },
{ "titlebar_bottom", luaA_client_titlebar_bottom },
{ "titlebar_left", luaA_client_titlebar_left },
{ "get_icon", luaA_client_get_some_icon },
{ NULL, NULL }
};
@ -3570,6 +3666,10 @@ client_class_setup(lua_State *L)
(lua_class_propfunc_t) luaA_client_set_icon,
(lua_class_propfunc_t) luaA_client_get_icon,
(lua_class_propfunc_t) luaA_client_set_icon);
luaA_class_add_property(&client_class, "icon_sizes",
NULL,
(lua_class_propfunc_t) luaA_client_get_icon_sizes,
NULL);
luaA_class_add_property(&client_class, "ontop",
(lua_class_propfunc_t) luaA_client_set_ontop,
(lua_class_propfunc_t) luaA_client_get_ontop,

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