From 56c57979056ec23cbd4982f3b81f6d80895fbe46 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Fri, 3 Jan 2014 16:51:16 +0100 Subject: [PATCH] Finish C-side support for window shapes (FS#1051) A drawin's and a client's bounding and clip shape can now be queried and is returned as a cairo surface. Also, a client window's shape (e.g. xeyes setting its own shape) can be queried via c.shape_client_bounding and c.shape_client_clip. All of these properties now emit signals when changed. (This also silently fixes a bug in luaA_drawin_set_shape_bounding() which forgot to include the drawin's border in its size calculation) Signed-off-by: Uli Schlachter --- awesome.c | 10 ++++-- event.c | 30 ++++++++++++++++ globalconf.h | 2 ++ luadoc/client.lua | 6 ++-- objects/client.c | 90 ++++++++++++++++++++++++++++++++++++++++++++--- objects/drawin.c | 44 +++++++++++++++++++++-- xwindow.c | 67 +++++++++++++++++++++++++++++++++++ xwindow.h | 1 + 8 files changed, 238 insertions(+), 12 deletions(-) diff --git a/awesome.c b/awesome.c index 665e43a93..f37552f9a 100644 --- a/awesome.c +++ b/awesome.c @@ -460,9 +460,13 @@ main(int argc, char **argv) xcb_prefetch_maximum_request_length(globalconf.connection); /* check for xtest extension */ - const xcb_query_extension_reply_t *xtest_query; - xtest_query = xcb_get_extension_data(globalconf.connection, &xcb_test_id); - globalconf.have_xtest = xtest_query->present; + const xcb_query_extension_reply_t *query; + query = xcb_get_extension_data(globalconf.connection, &xcb_test_id); + globalconf.have_xtest = query->present; + + /* check for shape extension */ + query = xcb_get_extension_data(globalconf.connection, &xcb_shape_id); + globalconf.have_shape = query->present; /* Allocate the key symbols */ globalconf.keysyms = xcb_key_symbols_alloc(globalconf.connection); diff --git a/event.c b/event.c index 9bd02651c..1881a9e2a 100644 --- a/event.c +++ b/event.c @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -686,6 +687,24 @@ event_handle_randr_screen_change_notify(xcb_randr_screen_change_notify_event_t * awesome_restart(); } +/** The shape notify event handler. + * \param ev The event. + */ +static void +event_handle_shape_notify(xcb_shape_notify_event_t *ev) +{ + client_t *c = client_getbywin(ev->affected_window); + if (c) + { + luaA_object_push(globalconf.L, c); + if (ev->shape_kind == XCB_SHAPE_SK_BOUNDING) + luaA_object_emit_signal(globalconf.L, -1, "property::shape_client_bounding", 0); + if (ev->shape_kind == XCB_SHAPE_SK_CLIP) + luaA_object_emit_signal(globalconf.L, -1, "property::shape_client_clip", 0); + lua_pop(globalconf.L, 1); + } +} + /** The client message event handler. * \param ev The event. */ @@ -829,6 +848,7 @@ void event_handle(xcb_generic_event_t *event) } static uint8_t randr_screen_change_notify = 0; + static uint8_t shape_notify = 0; if(randr_screen_change_notify == 0) { @@ -838,9 +858,19 @@ void event_handle(xcb_generic_event_t *event) if(randr_query->present) randr_screen_change_notify = randr_query->first_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY; } + if(shape_notify == 0) + { + /* check for shape extension */ + const xcb_query_extension_reply_t *shape_query; + shape_query = xcb_get_extension_data(globalconf.connection, &xcb_shape_id); + if(shape_query->present) + shape_notify = shape_query->first_event + XCB_SHAPE_NOTIFY; + } if (response_type == randr_screen_change_notify) event_handle_randr_screen_change_notify((void *) event); + if (response_type == shape_notify) + event_handle_shape_notify((void *) event); } // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/globalconf.h b/globalconf.h index ca8c4a3f6..e0652d6f5 100644 --- a/globalconf.h +++ b/globalconf.h @@ -83,6 +83,8 @@ typedef struct uint16_t numlockmask, shiftlockmask, capslockmask, modeswitchmask; /** Check for XTest extension */ bool have_xtest; + /** Check for SHAPE extension */ + bool have_shape; /** Clients list */ client_array_t clients; /** Embedded windows */ diff --git a/luadoc/client.lua b/luadoc/client.lua index dbcaef169..b3a3bc350 100644 --- a/luadoc/client.lua +++ b/luadoc/client.lua @@ -41,8 +41,10 @@ module("client") -- @field sticky Set the client sticky, i.e. available on all tags. -- @field modal Indicate if the client is modal. -- @field focusable True if the client can receive the input focus. --- @field shape_bounding The client's bounding shape as a (native) cairo surface. --- @field shape_clip The client's clip shape as a (native) cairo surface. +-- @field shape_bounding The client's bounding shape as set by awesome as a (native) cairo surface. +-- @field shape_clip The client's clip shape as set by awesome as a (native) cairo surface. +-- @field shape_client_bounding The client's bounding shape as set by the program as a (native) cairo surface. +-- @field shape_client_clip The client's clip shape as set by the program as a (native) cairo surface. -- @class table -- @name client diff --git a/objects/client.c b/objects/client.c index bdbcaaeb6..3838cbcce 100644 --- a/objects/client.c +++ b/objects/client.c @@ -440,6 +440,8 @@ client_manage(xcb_window_t w, xcb_get_geometry_reply_t *wgeom, bool startup) /* Make sure the window is automatically mapped if awesome exits or dies. */ xcb_change_save_set(globalconf.connection, XCB_SET_MODE_INSERT, w); + if (globalconf.have_shape) + xcb_shape_select_input(globalconf.connection, w, 1); client_t *c = client_new(globalconf.L); xcb_screen_t *s = globalconf.screen; @@ -1198,6 +1200,8 @@ client_unmanage(client_t *c, bool window_valid) /* Remove this window from the save set since this shouldn't be made visible * after a restart anymore. */ xcb_change_save_set(globalconf.connection, XCB_SET_MODE_DELETE, c->window); + if (globalconf.have_shape) + xcb_shape_select_input(globalconf.connection, c->window, 0); /* Do this last to avoid races with clients. According to ICCCM, clients * arent allowed to re-use the window until after this. */ @@ -1972,7 +1976,39 @@ luaA_client_get_size_hints(lua_State *L, client_t *c) return 1; } -/** Set the client's bounding shape. +/** Get the client's child window bounding shape. + * \param L The Lua VM state. + * \param client The client object. + * \return The number of elements pushed on stack. + */ +static int +luaA_client_get_client_shape_bounding(lua_State *L, client_t *c) +{ + cairo_surface_t *surf = xwindow_get_shape(c->window, XCB_SHAPE_SK_BOUNDING); + if (!surf) + return 0; + /* lua has to make sure to free the ref or we have a leak */ + lua_pushlightuserdata(L, surf); + return 1; +} + +/** Get the client's frame window bounding shape. + * \param L The Lua VM state. + * \param client The client object. + * \return The number of elements pushed on stack. + */ +static int +luaA_client_get_shape_bounding(lua_State *L, client_t *c) +{ + cairo_surface_t *surf = xwindow_get_shape(c->frame_window, XCB_SHAPE_SK_BOUNDING); + if (!surf) + return 0; + /* lua has to make sure to free the ref or we have a leak */ + lua_pushlightuserdata(L, surf); + return 1; +} + +/** Set the client's frame window bounding shape. * \param L The Lua VM state. * \param client The client object. * \return The number of elements pushed on stack. @@ -1987,10 +2023,43 @@ luaA_client_set_shape_bounding(lua_State *L, client_t *c) c->geometry.width + (c->border_width * 2), c->geometry.height + (c->border_width * 2), XCB_SHAPE_SK_BOUNDING, surf, -c->border_width); + luaA_object_emit_signal(L, -3, "property::shape_bounding", 0); return 0; } -/** Set the client's clip shape. +/** Get the client's child window clip shape. + * \param L The Lua VM state. + * \param client The client object. + * \return The number of elements pushed on stack. + */ +static int +luaA_client_get_client_shape_clip(lua_State *L, client_t *c) +{ + cairo_surface_t *surf = xwindow_get_shape(c->window, XCB_SHAPE_SK_CLIP); + if (!surf) + return 0; + /* lua has to make sure to free the ref or we have a leak */ + lua_pushlightuserdata(L, surf); + return 1; +} + +/** Get the client's frame window clip shape. + * \param L The Lua VM state. + * \param client The client object. + * \return The number of elements pushed on stack. + */ +static int +luaA_client_get_shape_clip(lua_State *L, client_t *c) +{ + cairo_surface_t *surf = xwindow_get_shape(c->frame_window, XCB_SHAPE_SK_CLIP); + if (!surf) + return 0; + /* lua has to make sure to free the ref or we have a leak */ + lua_pushlightuserdata(L, surf); + return 1; +} + +/** Set the client's frame window clip shape. * \param L The Lua VM state. * \param client The client object. * \return The number of elements pushed on stack. @@ -2003,6 +2072,7 @@ luaA_client_set_shape_clip(lua_State *L, client_t *c) surf = (cairo_surface_t *)lua_touserdata(L, -1); xwindow_set_shape(c->frame_window, c->geometry.width, c->geometry.height, XCB_SHAPE_SK_CLIP, surf, 0); + luaA_object_emit_signal(L, -3, "property::shape_clip", 0); return 0; } @@ -2226,12 +2296,20 @@ client_class_setup(lua_State *L) NULL); luaA_class_add_property(&client_class, "shape_bounding", (lua_class_propfunc_t) luaA_client_set_shape_bounding, - NULL, + (lua_class_propfunc_t) luaA_client_get_shape_bounding, (lua_class_propfunc_t) luaA_client_set_shape_bounding); luaA_class_add_property(&client_class, "shape_clip", (lua_class_propfunc_t) luaA_client_set_shape_clip, - NULL, + (lua_class_propfunc_t) luaA_client_get_shape_clip, (lua_class_propfunc_t) luaA_client_set_shape_clip); + luaA_class_add_property(&client_class, "client_shape_bounding", + NULL, + (lua_class_propfunc_t) luaA_client_get_client_shape_bounding, + NULL); + luaA_class_add_property(&client_class, "client_shape_clip", + NULL, + (lua_class_propfunc_t) luaA_client_get_client_shape_clip, + NULL); signal_add(&client_class.signals, "focus"); signal_add(&client_class.signals, "list"); @@ -2263,6 +2341,10 @@ client_class_setup(lua_State *L) signal_add(&client_class.signals, "property::pid"); signal_add(&client_class.signals, "property::role"); signal_add(&client_class.signals, "property::screen"); + signal_add(&client_class.signals, "property::shape_bounding"); + signal_add(&client_class.signals, "property::shape_client_bounding"); + signal_add(&client_class.signals, "property::shape_client_clip"); + signal_add(&client_class.signals, "property::shape_clip"); signal_add(&client_class.signals, "property::size_hints_honor"); signal_add(&client_class.signals, "property::skip_taskbar"); signal_add(&client_class.signals, "property::sticky"); diff --git a/objects/drawin.c b/objects/drawin.c index b570211cb..66d8f67e2 100644 --- a/objects/drawin.c +++ b/objects/drawin.c @@ -536,6 +536,22 @@ luaA_drawin_get_drawable(lua_State *L, drawin_t *drawin) return 1; } +/** Get the drawin's bounding shape. + * \param L The Lua VM state. + * \param drawin The drawin object. + * \return The number of elements pushed on stack. + */ +static int +luaA_drawin_get_shape_bounding(lua_State *L, drawin_t *drawin) +{ + cairo_surface_t *surf = xwindow_get_shape(drawin->window, XCB_SHAPE_SK_BOUNDING); + if (!surf) + return 0; + /* lua has to make sure to free the ref or we have a leak */ + lua_pushlightuserdata(L, surf); + return 1; +} + /** Set the drawin's bounding shape. * \param L The Lua VM state. * \param drawin The drawin object. @@ -547,11 +563,30 @@ luaA_drawin_set_shape_bounding(lua_State *L, drawin_t *drawin) cairo_surface_t *surf = NULL; if(!lua_isnil(L, -1)) surf = (cairo_surface_t *)lua_touserdata(L, -1); - xwindow_set_shape(drawin->window, drawin->geometry.width, drawin->geometry.height, + xwindow_set_shape(drawin->window, + drawin->geometry.width + 2*drawin->border_width, + drawin->geometry.height + 2*drawin->border_width, XCB_SHAPE_SK_BOUNDING, surf, -drawin->border_width); + luaA_object_emit_signal(L, -3, "property::shape_bounding", 0); return 0; } +/** Get the drawin's clip shape. + * \param L The Lua VM state. + * \param drawin The drawin object. + * \return The number of elements pushed on stack. + */ +static int +luaA_drawin_get_shape_clip(lua_State *L, drawin_t *drawin) +{ + cairo_surface_t *surf = xwindow_get_shape(drawin->window, XCB_SHAPE_SK_CLIP); + if (!surf) + return 0; + /* lua has to make sure to free the ref or we have a leak */ + lua_pushlightuserdata(L, surf); + return 1; +} + /** Set the drawin's clip shape. * \param L The Lua VM state. * \param drawin The drawin object. @@ -565,6 +600,7 @@ luaA_drawin_set_shape_clip(lua_State *L, drawin_t *drawin) surf = (cairo_surface_t *)lua_touserdata(L, -1); xwindow_set_shape(drawin->window, drawin->geometry.width, drawin->geometry.height, XCB_SHAPE_SK_CLIP, surf, 0); + luaA_object_emit_signal(L, -3, "property::shape_clip", 0); return 0; } @@ -630,13 +666,15 @@ drawin_class_setup(lua_State *L) (lua_class_propfunc_t) luaA_window_set_type); luaA_class_add_property(&drawin_class, "shape_bounding", (lua_class_propfunc_t) luaA_drawin_set_shape_bounding, - NULL, + (lua_class_propfunc_t) luaA_drawin_get_shape_bounding, (lua_class_propfunc_t) luaA_drawin_set_shape_bounding); luaA_class_add_property(&drawin_class, "shape_clip", (lua_class_propfunc_t) luaA_drawin_set_shape_clip, - NULL, + (lua_class_propfunc_t) luaA_drawin_get_shape_clip, (lua_class_propfunc_t) luaA_drawin_set_shape_clip); + signal_add(&client_class.signals, "property::shape_bounding"); + signal_add(&client_class.signals, "property::shape_clip"); signal_add(&drawin_class.signals, "property::border_width"); signal_add(&drawin_class.signals, "property::cursor"); signal_add(&drawin_class.signals, "property::height"); diff --git a/xwindow.c b/xwindow.c index 7c2f8b993..6556ce726 100644 --- a/xwindow.c +++ b/xwindow.c @@ -258,6 +258,70 @@ xwindow_set_border_color(xcb_window_t w, color_t *color) xcb_change_window_attributes(globalconf.connection, w, XCB_CW_BORDER_PIXEL, &color->pixel); } +/** Get one of a window's shapes as a cairo surface */ +cairo_surface_t * +xwindow_get_shape(xcb_window_t win, enum xcb_shape_sk_t kind) +{ + if (!globalconf.have_shape) + return NULL; + + xcb_shape_query_extents_cookie_t ecookie = xcb_shape_query_extents(globalconf.connection, win); + xcb_shape_get_rectangles_cookie_t rcookie = xcb_shape_get_rectangles(globalconf.connection, win, kind); + xcb_shape_query_extents_reply_t *extents = xcb_shape_query_extents_reply(globalconf.connection, ecookie, NULL); + xcb_shape_get_rectangles_reply_t *rects_reply = xcb_shape_get_rectangles_reply(globalconf.connection, rcookie, NULL); + + if (!extents || !rects_reply) + { + free(extents); + free(rects_reply); + /* Create a cairo surface in an error state */ + return cairo_image_surface_create(CAIRO_FORMAT_INVALID, -1, -1); + } + + int16_t x, y; + uint16_t width, height; + bool shaped; + if (kind == XCB_SHAPE_SK_BOUNDING) + { + x = extents->bounding_shape_extents_x; + y = extents->bounding_shape_extents_y; + width = extents->bounding_shape_extents_width; + height = extents->bounding_shape_extents_height; + shaped = extents->bounding_shaped; + } else { + assert(kind == XCB_SHAPE_SK_CLIP); + x = extents->clip_shape_extents_x; + y = extents->clip_shape_extents_y; + width = extents->clip_shape_extents_width; + height = extents->clip_shape_extents_height; + shaped = extents->clip_shaped; + } + + if (!shaped) + { + free(extents); + free(rects_reply); + return NULL; + } + + cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_A1, width, height); + cairo_t *cr = cairo_create(surface); + int num_rects = xcb_shape_get_rectangles_rectangles_length(rects_reply); + xcb_rectangle_t *rects = xcb_shape_get_rectangles_rectangles(rects_reply); + + cairo_surface_set_device_offset(surface, -x, -y); + cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING); + + for (int i = 0; i < num_rects; i++) + cairo_rectangle(cr, rects[i].x, rects[i].y, rects[i].width, rects[i].height); + cairo_fill(cr); + + cairo_destroy(cr); + free(extents); + free(rects_reply); + return surface; +} + /** Turn a cairo surface into a pixmap with depth 1 */ static xcb_pixmap_t xwindow_shape_pixmap(int width, int height, cairo_surface_t *surf) @@ -286,6 +350,9 @@ xwindow_shape_pixmap(int width, int height, cairo_surface_t *surf) void xwindow_set_shape(xcb_window_t win, int width, int height, enum xcb_shape_sk_t kind, cairo_surface_t *surf, int offset) { + if (!globalconf.have_shape) + return; + xcb_pixmap_t pixmap = XCB_NONE; if (surf) pixmap = xwindow_shape_pixmap(width, height, surf); diff --git a/xwindow.h b/xwindow.h index 561a5dcf9..be15a8490 100644 --- a/xwindow.h +++ b/xwindow.h @@ -40,6 +40,7 @@ void xwindow_grabkeys(xcb_window_t, key_array_t *); void xwindow_takefocus(xcb_window_t); void xwindow_set_cursor(xcb_window_t, xcb_cursor_t); void xwindow_set_border_color(xcb_window_t, color_t *); +cairo_surface_t *xwindow_get_shape(xcb_window_t, enum xcb_shape_sk_t); void xwindow_set_shape(xcb_window_t, int, int, enum xcb_shape_sk_t, cairo_surface_t *, int); void xwindow_translate_for_gravity(xcb_gravity_t, int16_t, int16_t, int16_t, int16_t, int16_t *, int16_t *);