Composite systray with alpha channel and support stale systray windows without it. Add a beautiful.systray_skip_bg to skip background drawing, so it can blend nicely with other widgets.

This commit is contained in:
Xinhao Yuan 2022-07-21 22:28:48 -04:00
parent 9334dc71a9
commit 7464c8e5c4
13 changed files with 236 additions and 21 deletions

View File

@ -809,6 +809,8 @@ main(int argc, char **argv)
xcb_discard_reply(globalconf.connection,
xcb_damage_query_version(globalconf.connection, 1, 0).sequence);
globalconf.is_compositing = globalconf.have_composite && globalconf.have_damage;
event_init();
/* Allocate the key symbols */

View File

@ -57,6 +57,7 @@ ESETROOT_PMAP_ID
WM_STATE
_NET_WM_WINDOW_OPACITY
_NET_SYSTEM_TRAY_ORIENTATION
_NET_SYSTEM_TRAY_VISUAL
WM_CHANGE_STATE
WM_WINDOW_ROLE
WM_CLIENT_LEADER

View File

@ -41,6 +41,7 @@ typedef struct xembed_window xembed_window_t;
struct xembed_window
{
xcb_window_t win;
uint8_t depth;
xembed_info_t info;
};

View File

@ -1026,6 +1026,10 @@ event_handle_selectionclear(xcb_selection_clear_event_t *ev)
static void
event_handle_damage_notify(xcb_damage_notify_event_t *ev) {
if (ev->drawable == globalconf.systray.window) {
luaA_systray_invalidate();
xcb_damage_subtract(globalconf.connection, ev->damage, None, None);
}
}
/** \brief awesome xerror function.

View File

@ -130,6 +130,8 @@ typedef struct
bool have_composite;
/** Check for Damage extenion */
bool have_damage;
/** Enable compositing features? */
bool is_compositing;
/** Custom searchpaths are present, the runtime is tinted */
bool have_searchpaths;
/** When --no-argb is used in the modeline or command line */

View File

@ -7,7 +7,9 @@
local wbase = require("wibox.widget.base")
local drawable = require("wibox.drawable")
local cairo = require("lgi").cairo
local beautiful = require("beautiful")
local gcolor = require("gears.color")
local gtable = require("gears.table")
local capi = {
awesome = awesome,
@ -42,6 +44,11 @@ local display_on_screen = "primary"
-- @beautiful beautiful.systray_icon_spacing
-- @tparam[opt=0] integer The icon spacing
--- Whether to skip drawing the systray background when compositing the systray icons.
--
-- @beautiful beautiful.systray_skip_bg
-- @tparam[opt=0] boolean Whether to skip drawing the systray background
local function should_display_on(s)
if display_on_screen == "primary" then
return s == capi.screen.primary
@ -90,6 +97,23 @@ function systray:draw(context, cr, width, height)
end
capi.awesome.systray(context.wibox.drawin, math.ceil(x), math.ceil(y),
base, is_rotated, bg, reverse, spacing, rows)
local surf_width, surf_height =
base * rows + spacing * (rows - 1),
base * cols + spacing * (cols - 1)
if is_rotated then
surf_width, surf_height = surf_height, surf_width
end
local surf_raw = capi.awesome.systray_surface(surf_width, surf_height)
if surf_raw then
local surf = cairo.Surface(surf_raw, true)
if not beautiful.systray_skip_bg then
cr:set_source(gcolor(bg))
cr:paint()
end
cr:set_source_surface(surf, 0, 0)
cr:paint()
end
end
-- Private API. Does not appear in LDoc on purpose. This function is called

1
luaa.c
View File

@ -1090,6 +1090,7 @@ luaA_init(xdgHandle* xdg, string_array_t *searchpath)
{ "disconnect_signal", luaA_awesome_disconnect_signal },
{ "emit_signal", luaA_awesome_emit_signal },
{ "systray", luaA_systray },
{ "systray_surface", luaA_systray_surface },
{ "load_image", luaA_load_image },
{ "pixbuf_to_surface", luaA_pixbuf_to_surface },
{ "set_preferred_icon_size", luaA_set_preferred_icon_size },

View File

@ -45,6 +45,7 @@
#include <cairo-xcb.h>
#include <xcb/shape.h>
#include <xcb/composite.h>
lua_class_t drawin_class;
@ -451,6 +452,8 @@ drawin_allocator(lua_State *L)
globalconf.default_cmap,
xcursor_new(globalconf.cursor_ctx, xcursor_font_fromstr(w->cursor))
});
if (globalconf.is_compositing)
xcb_composite_redirect_subwindows(globalconf.connection, w->window, XCB_COMPOSITE_REDIRECT_MANUAL);
xwindow_set_class_instance(w->window);
xwindow_set_name_static(w->window, "Awesome drawin");

View File

@ -27,7 +27,10 @@ _G.awesome = {
systray_arguments = { first_arg, ... }
end
return num_systray_icons
end
end,
systray_surface = function()
return nil
end,
}
_G.screen = {
connect_signal = function() end

View File

@ -29,6 +29,8 @@
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xcb_atom.h>
#include <xcb/damage.h>
#include <cairo-xcb.h>
#define SYSTEM_TRAY_REQUEST_DOCK 0 /* Begin icon docking */
@ -44,13 +46,29 @@ systray_init(void)
globalconf.systray.window = xcb_generate_id(globalconf.connection);
globalconf.systray.background_pixel = xscreen->black_pixel;
xcb_create_window(globalconf.connection, xscreen->root_depth,
globalconf.systray.window,
xscreen->root,
-1, -1, 1, 1, 0,
XCB_COPY_FROM_PARENT, xscreen->root_visual,
XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, (const uint32_t [])
{ xscreen->black_pixel, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT });
if (globalconf.is_compositing) {
xcb_create_window(globalconf.connection, globalconf.default_depth,
globalconf.systray.window,
xscreen->root,
-1, -1, 1, 1, 0,
XCB_COPY_FROM_PARENT, globalconf.visual->visual_id,
XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP,
(const uint32_t [])
{ xscreen->black_pixel, xscreen->black_pixel, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, globalconf.default_cmap });
xcb_damage_create(globalconf.connection, xcb_generate_id(globalconf.connection), globalconf.systray.window, XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY);
xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE,
globalconf.systray.window, _NET_SYSTEM_TRAY_VISUAL,
XCB_ATOM_VISUALID, 32, 1, (const uint32_t [])
{ globalconf.visual->visual_id });
} else {
xcb_create_window(globalconf.connection, xscreen->root_depth,
globalconf.systray.window,
xscreen->root,
-1, -1, 1, 1, 0,
XCB_COPY_FROM_PARENT, xscreen->root_visual,
XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, (const uint32_t [])
{ xscreen->black_pixel, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT });
}
xwindow_set_class_instance(globalconf.systray.window);
xwindow_set_name_static(globalconf.systray.window, "Awesome systray window");
@ -130,6 +148,8 @@ int
systray_request_handle(xcb_window_t embed_win)
{
xembed_window_t em;
xcb_get_geometry_cookie_t geom_c;
xcb_get_geometry_reply_t *geom_r;
xcb_get_property_cookie_t em_cookie;
const uint32_t select_input_val[] =
{
@ -142,6 +162,12 @@ systray_request_handle(xcb_window_t embed_win)
if(xembed_getbywin(&globalconf.embedded, embed_win))
return -1;
geom_c = xcb_get_geometry(globalconf.connection, embed_win);
if(!(geom_r = xcb_get_geometry_reply(globalconf.connection, geom_c, NULL)))
return -1;
em.depth = geom_r->depth;
p_delete(&geom_r);
p_clear(&em_cookie, 1);
em_cookie = xembed_info_get_unchecked(globalconf.connection, embed_win);
@ -149,6 +175,15 @@ systray_request_handle(xcb_window_t embed_win)
xcb_change_window_attributes(globalconf.connection, embed_win, XCB_CW_EVENT_MASK,
select_input_val);
if (globalconf.is_compositing && em.depth != globalconf.default_depth) {
/* Disable the message because the test runner is not happy with warnings. This should rarely happen anyway. */
/* warn("Fixing the background of the systray window 0x%x possibly because the client does not support composition.", embed_win); */
xcb_change_window_attributes(globalconf.connection, embed_win, XCB_CW_BACK_PIXEL,
(const uint32_t []){ globalconf.systray.background_pixel });
xcb_clear_area(globalconf.connection, 1, embed_win, 0, 0, 0, 0);
}
/* we grab the window, but also make sure it's automatically reparented back
* to the root window if we should die.
*/
@ -376,12 +411,21 @@ luaA_systray(lua_State *L)
&& globalconf.systray.background_pixel != bg_color.pixel)
{
uint32_t config_back[] = { bg_color.pixel };
globalconf.systray.background_pixel = bg_color.pixel;
xcb_change_window_attributes(globalconf.connection,
globalconf.systray.window,
XCB_CW_BACK_PIXEL, config_back);
xcb_clear_area(globalconf.connection, 1, globalconf.systray.window, 0, 0, 0, 0);
force_redraw = true;
if (globalconf.is_compositing) {
foreach(em, globalconf.embedded)
if (em->depth != globalconf.default_depth) {
xcb_change_window_attributes(
globalconf.connection, em->win, XCB_CW_BACK_PIXEL, config_back);
xcb_clear_area(globalconf.connection, 1, em->win, 0, 0, 0, 0);
}
} else {
globalconf.systray.background_pixel = bg_color.pixel;
xcb_change_window_attributes(globalconf.connection,
globalconf.systray.window,
XCB_CW_BACK_PIXEL, config_back);
xcb_clear_area(globalconf.connection, 1, globalconf.systray.window, 0, 0, 0, 0);
force_redraw = true;
}
}
if(globalconf.systray.parent != w)
@ -413,4 +457,29 @@ luaA_systray(lua_State *L)
return 2;
}
/** Return the native surface of the systray if composite is enabled.
* \param L The Lua VM state.
* \return the number of element returned. (1)
* \luastack
* \lparam width The width of the systray surface.
* \lparam height The height of the systray surface.
*/
int
luaA_systray_surface(lua_State *L)
{
if (!globalconf.is_compositing) {
lua_pushnil(L);
return 1;
}
int width = luaL_checkinteger(L, 1);
int height = luaL_checkinteger(L, 2);
/* Lua has to make sure to free the ref or we have a leak */
lua_pushlightuserdata(
L, cairo_xcb_surface_create(
globalconf.connection, globalconf.systray.window, globalconf.visual,
width, height));
return 1;
}
// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -33,6 +33,7 @@ bool systray_iskdedockapp(xcb_window_t);
int systray_process_client_message(xcb_client_message_event_t *);
int xembed_process_client_message(xcb_client_message_event_t *);
int luaA_systray(lua_State *);
int luaA_systray_surface(lua_State *);
#endif
// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -65,8 +65,8 @@ static xcb_window_t find_systray(xcb_connection_t *conn, xcb_atom_t net_system_t
return owner;
}
static uint32_t get_color(xcb_connection_t *conn, xcb_screen_t *screen, uint16_t red, uint16_t green, uint16_t blue) {
xcb_alloc_color_reply_t *reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, screen->default_colormap, red, green, blue), NULL);
static uint32_t get_color(xcb_connection_t *conn, xcb_colormap_t cm, uint16_t red, uint16_t green, uint16_t blue) {
xcb_alloc_color_reply_t *reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, cm, red, green, blue), NULL);
if (!reply)
fatal("Error allocating color");
uint32_t pixel = reply->pixel;
@ -74,6 +74,19 @@ static uint32_t get_color(xcb_connection_t *conn, xcb_screen_t *screen, uint16_t
return pixel;
}
static uint8_t find_visual_depth(const xcb_screen_t *s, xcb_visualid_t visual)
{
xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(s);
if(depth_iter.data)
for(; depth_iter.rem; xcb_depth_next (&depth_iter))
for(xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
visual_iter.rem; xcb_visualtype_next (&visual_iter))
if(visual == visual_iter.data->visual_id)
return depth_iter.data->depth;
return 0;
}
int main() {
int default_screen;
xcb_connection_t* conn = xcb_connect(NULL, &default_screen);
@ -82,15 +95,54 @@ int main() {
}
atoms_init(conn);
xcb_screen_t* screen = xcb_aux_get_screen(conn, default_screen);
xcb_window_t systray_owner = find_systray(conn, systray_atom(conn, default_screen));
// Try to use tray visual hint, if requested.
char *use_hint_var = getenv("USE_TRAY_VISUAL_HINT");
bool use_tray_visual_hint = use_hint_var && *use_hint_var;
xcb_visualid_t tray_visual_id;
uint8_t tray_depth;
if (use_tray_visual_hint) {
xcb_get_property_reply_t *tray_visual_r =
xcb_get_property_reply(
conn, xcb_get_property(conn, false, systray_owner,
_NET_SYSTEM_TRAY_VISUAL,
XCB_ATOM_VISUALID, 0, 1),
NULL);
tray_visual_id = screen->root_visual;
if(tray_visual_r != NULL && xcb_get_property_value_length(tray_visual_r)) {
tray_visual_id = *(uint32_t *)xcb_get_property_value(tray_visual_r);
p_delete(&tray_visual_r);
}
tray_depth = find_visual_depth(screen, tray_visual_id);
if (tray_depth == 0) {
fatal("Error getting visual hint\n");
}
} else {
tray_visual_id= screen->root_visual;
tray_depth = screen->root_depth;
}
xcb_colormap_t cm = xcb_generate_id(conn);
xcb_create_colormap(conn, XCB_COLORMAP_ALLOC_NONE,
cm, screen->root,
tray_visual_id);
// Create a window for the systray icon
xcb_window_t window = xcb_generate_id(conn);
xcb_create_window(conn, screen->root_depth, window, screen->root, 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
screen->root_visual, XCB_CW_BACK_PIXEL, (uint32_t[]) { get_color(conn, screen, 0xffff, 0x9999, 0x0000) });
xcb_create_window(conn, tray_depth, window, screen->root, 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
tray_visual_id,
(use_tray_visual_hint ? XCB_CW_BACK_PIXEL : XCB_CW_BACK_PIXMAP)
| XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP,
(uint32_t[]) {
use_tray_visual_hint
? get_color(conn, cm, 0xffff, 0x9999, 0x0000)
: XCB_BACK_PIXMAP_PARENT_RELATIVE,
screen->black_pixel, cm
});
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, _XEMBED_INFO, _XEMBED_INFO, 32, 2, (uint32_t[]) { 0, 1 });
// Make our window a systray icon
xcb_window_t systray_owner = find_systray(conn, systray_atom(conn, default_screen));
xcb_client_message_event_t ev;
p_clear(&ev, 1);

View File

@ -2,7 +2,7 @@ local spawn = require("awful.spawn")
local wibox = require("wibox")
local beautiful = require("beautiful")
local steps, pid1, pid2, draw_w, st = {}
local steps, pid1, pid2, draw_w, wb, st = {}
table.insert(steps, function()
screen[1].mywibox:remove()
@ -17,7 +17,7 @@ table.insert(steps, function()
draw_w = width
end
local wb = wibox {
wb = wibox {
x = 0,
y = 0,
width = 100,
@ -81,6 +81,58 @@ table.insert(steps, function()
return true
end)
table.insert(steps, function()
if draw_w ~= 100 then return end
return true
end)
table.insert(steps, function()
st.base_size = 20
pid1 = spawn("env USE_TRAY_VISUAL_HINT=1 ./test-systray")
return true
end)
local lgi_core = require("lgi.core")
local lgi_ffi = require("lgi.ffi")
local lgi_ti = lgi_ffi.types
local lgi_record = require("lgi.record")
local lgi_component = require("lgi.component")
local cairo = require("lgi").cairo
local wrapped_uchar = lgi_component.create(nil, lgi_record.struct_mt, "wrapped_uchar")
lgi_ffi.load_fields(wrapped_uchar, { { 'v', lgi_ti.uchar } })
table.insert(steps, function()
if draw_w ~= 80 then return end
local systray_surface_raw = awesome.systray_surface(20, 20)
if systray_surface_raw then
local src = cairo.Surface(systray_surface_raw, true)
local s = cairo.ImageSurface("ARGB32", 20, 20)
local cr = cairo.Context(s)
cr:set_source_surface(src, 0, 0)
cr:paint()
-- Read the first pixel (a,r,g,b) as 4 8-bit integers.
local data = s:get_data()
local array = lgi_core.record.new(wrapped_uchar, data, false)
local argb = {}
for i = 0, 3 do
argb[i + 1] = lgi_core.record.fromarray(array, i).v
end
-- Check with the pixel from test-systray.c
return
argb[1] == 0 and
argb[2] == 153 and
argb[3] == 255 and
argb[4] == 255
else
print("Systray composition test is disabled.")
return true
end
end)
table.insert(steps, function()
awesome.kill(pid1, 9)
return true
end)
table.insert(steps, function()
if draw_w ~= 100 then return end