From 7464c8e5c4b8a8945d30fe2a1cdc7b711dfbb8f3 Mon Sep 17 00:00:00 2001 From: Xinhao Yuan Date: Thu, 21 Jul 2022 22:28:48 -0400 Subject: [PATCH] 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. --- awesome.c | 2 + common/atoms.list | 1 + common/xembed.h | 1 + event.c | 4 ++ globalconf.h | 2 + lib/wibox/widget/systray.lua | 24 ++++++++ luaa.c | 1 + objects/drawin.c | 3 + spec/wibox/widget/systray_spec.lua | 5 +- systray.c | 95 ++++++++++++++++++++++++++---- systray.h | 1 + tests/test-systray.c | 62 +++++++++++++++++-- tests/test-systray.lua | 56 +++++++++++++++++- 13 files changed, 236 insertions(+), 21 deletions(-) diff --git a/awesome.c b/awesome.c index f5da5d5124..e11ed808c2 100644 --- a/awesome.c +++ b/awesome.c @@ -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 */ diff --git a/common/atoms.list b/common/atoms.list index 9cf41336ee..2061fb743b 100644 --- a/common/atoms.list +++ b/common/atoms.list @@ -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 diff --git a/common/xembed.h b/common/xembed.h index 484f5eb01b..138fd53b51 100644 --- a/common/xembed.h +++ b/common/xembed.h @@ -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; }; diff --git a/event.c b/event.c index cda05049d6..882fa69495 100644 --- a/event.c +++ b/event.c @@ -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. diff --git a/globalconf.h b/globalconf.h index cd4854f750..57d8c9d73d 100644 --- a/globalconf.h +++ b/globalconf.h @@ -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 */ diff --git a/lib/wibox/widget/systray.lua b/lib/wibox/widget/systray.lua index f7a20361bf..9c01d09776 100644 --- a/lib/wibox/widget/systray.lua +++ b/lib/wibox/widget/systray.lua @@ -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 diff --git a/luaa.c b/luaa.c index 233174ef03..a350ed5999 100644 --- a/luaa.c +++ b/luaa.c @@ -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 }, diff --git a/objects/drawin.c b/objects/drawin.c index 3bbd93179c..dbec3ab1cd 100644 --- a/objects/drawin.c +++ b/objects/drawin.c @@ -45,6 +45,7 @@ #include #include +#include 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"); diff --git a/spec/wibox/widget/systray_spec.lua b/spec/wibox/widget/systray_spec.lua index b7bc5e41bc..a20fa8cb16 100644 --- a/spec/wibox/widget/systray_spec.lua +++ b/spec/wibox/widget/systray_spec.lua @@ -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 diff --git a/systray.c b/systray.c index 99986549a9..f573aed0b7 100644 --- a/systray.c +++ b/systray.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include #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 diff --git a/systray.h b/systray.h index b41764006d..8f9ff8143b 100644 --- a/systray.h +++ b/systray.h @@ -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 diff --git a/tests/test-systray.c b/tests/test-systray.c index 971b50d2ae..3f658aa673 100644 --- a/tests/test-systray.c +++ b/tests/test-systray.c @@ -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); diff --git a/tests/test-systray.lua b/tests/test-systray.lua index de4b5f93d3..1631a5812b 100644 --- a/tests/test-systray.lua +++ b/tests/test-systray.lua @@ -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