diff --git a/common/atoms.list b/common/atoms.list index 9cf41336ee8..2061fb743be 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 484f5eb01b4..138fd53b51b 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 cda05049d64..882fa694952 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 cd4854f750f..94f968de940 100644 --- a/globalconf.h +++ b/globalconf.h @@ -187,6 +187,8 @@ typedef struct drawin_t *parent; /** Background color */ uint32_t background_pixel; + /** Whether systray is composited */ + bool is_composited; } systray; /** The monitor of startup notifications */ SnMonitorContext *snmonitor; diff --git a/lib/wibox/widget/systray.lua b/lib/wibox/widget/systray.lua index f7a20361bf8..8e24b43ef24 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 ignore systray background when compositing the systray icons. +-- +-- @beautiful beautiful.systray_transparent +-- @tparam[opt=0] boolean Whether to ignore 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 width, height = + base * rows + spacing * (rows - 1), + base * cols + spacing * (cols - 1) + if is_rotated then + width, height = height, width + end + local surf_raw = capi.awesome.systray_surface(width, height) + if surf_raw then + local surf = cairo.Surface(surf_raw) + if not beautiful.systray_transparent 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 233174ef03b..a350ed59994 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 3bbd93179c9..8d3c037a169 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.have_composite) + 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/systray.c b/systray.c index 99986549a99..9cf23f5d6da 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,30 @@ 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 }); + globalconf.systray.is_composited = globalconf.have_composite && globalconf.have_damage; + if (globalconf.systray.is_composited) { + 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 +149,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[] = { @@ -149,6 +170,19 @@ systray_request_handle(xcb_window_t embed_win) xcb_change_window_attributes(globalconf.connection, embed_win, XCB_CW_EVENT_MASK, select_input_val); + + 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); + if (globalconf.systray.is_composited && em.depth != globalconf.default_depth) { + 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 +410,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.systray.is_composited) { + 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 +456,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.systray.is_composited) { + 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 b41764006d3..8f9ff8143bd 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 971b50d2aee..539af8feae0 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(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 de4b5f93d3a..98825e38b9a 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) + 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