Merge pull request #1636 from psychon/client-icons2
Add support for multiple client icons
This commit is contained in:
commit
4a42ed0d44
8
draw.h
8
draw.h
|
@ -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
77
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
|
||||
|
|
3
ewmh.h
3
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
132
objects/client.c
132
objects/client.c
|
@ -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,
|
||||
|
|
|
@ -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 *);
|
||||
|
|
14
property.c
14
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
|
||||
|
|
Loading…
Reference in New Issue