diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 205e277a..81f0bb4a 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1525,6 +1525,50 @@ void realloc_sl_buffer(void) { } } +/* Strut partial tells i3 where to reserve space for i3bar. This is determined + * by the `position` bar config directive. */ +xcb_void_cookie_t config_strut_partial(i3_output *output) { + /* A local struct to save the strut_partial property */ + struct { + uint32_t left; + uint32_t right; + uint32_t top; + uint32_t bottom; + uint32_t left_start_y; + uint32_t left_end_y; + uint32_t right_start_y; + uint32_t right_end_y; + uint32_t top_start_x; + uint32_t top_end_x; + uint32_t bottom_start_x; + uint32_t bottom_end_x; + } __attribute__((__packed__)) strut_partial; + memset(&strut_partial, 0, sizeof(strut_partial)); + + switch (config.position) { + case POS_NONE: + break; + case POS_TOP: + strut_partial.top = bar_height; + strut_partial.top_start_x = output->rect.x; + strut_partial.top_end_x = output->rect.x + output->rect.w; + break; + case POS_BOT: + strut_partial.bottom = bar_height; + strut_partial.bottom_start_x = output->rect.x; + strut_partial.bottom_end_x = output->rect.x + output->rect.w; + break; + } + return xcb_change_property(xcb_connection, + XCB_PROP_MODE_REPLACE, + output->bar, + atoms[_NET_WM_STRUT_PARTIAL], + XCB_ATOM_CARDINAL, + 32, + 12, + &strut_partial); +} + /* * Reconfigure all bars and create new bars for recently activated outputs * @@ -1624,49 +1668,7 @@ void reconfig_windows(bool redraw_bars) { 1, (unsigned char *)&atoms[_NET_WM_WINDOW_TYPE_DOCK]); - /* We need to tell i3, where to reserve space for i3bar */ - /* left, right, top, bottom, left_start_y, left_end_y, - * right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x, - * bottom_end_x */ - /* A local struct to save the strut_partial property */ - struct { - uint32_t left; - uint32_t right; - uint32_t top; - uint32_t bottom; - uint32_t left_start_y; - uint32_t left_end_y; - uint32_t right_start_y; - uint32_t right_end_y; - uint32_t top_start_x; - uint32_t top_end_x; - uint32_t bottom_start_x; - uint32_t bottom_end_x; - } __attribute__((__packed__)) strut_partial; - memset(&strut_partial, 0, sizeof(strut_partial)); - - switch (config.position) { - case POS_NONE: - break; - case POS_TOP: - strut_partial.top = bar_height; - strut_partial.top_start_x = walk->rect.x; - strut_partial.top_end_x = walk->rect.x + walk->rect.w; - break; - case POS_BOT: - strut_partial.bottom = bar_height; - strut_partial.bottom_start_x = walk->rect.x; - strut_partial.bottom_end_x = walk->rect.x + walk->rect.w; - break; - } - xcb_void_cookie_t strut_cookie = xcb_change_property(xcb_connection, - XCB_PROP_MODE_REPLACE, - walk->bar, - atoms[_NET_WM_STRUT_PARTIAL], - XCB_ATOM_CARDINAL, - 32, - 12, - &strut_partial); + xcb_void_cookie_t strut_cookie = config_strut_partial(walk); /* We also want a graphics context for the bars (it defines the properties * with which we draw to them) */ @@ -1726,6 +1728,9 @@ void reconfig_windows(bool redraw_bars) { values[3] = bar_height; values[4] = XCB_STACK_MODE_ABOVE; + DLOG("Reconfiguring strut partial property for output %s\n", walk->name); + xcb_void_cookie_t strut_cookie = config_strut_partial(walk); + DLOG("Destroying buffer for output %s\n", walk->name); xcb_free_pixmap(xcb_connection, walk->buffer); @@ -1774,6 +1779,7 @@ void reconfig_windows(bool redraw_bars) { if (xcb_request_failed(cfg_cookie, "Could not reconfigure window") || xcb_request_failed(chg_cookie, "Could not change window") || xcb_request_failed(pm_cookie, "Could not create pixmap") || + xcb_request_failed(strut_cookie, "Could not set strut") || (redraw_bars && (xcb_request_failed(umap_cookie, "Could not unmap window") || (config.hide_on_modifier == M_DOCK && xcb_request_failed(map_cookie, "Could not map window"))))) { exit(EXIT_FAILURE); diff --git a/src/handlers.c b/src/handlers.c index 0cd397fd..4b5c87d4 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1159,6 +1159,87 @@ static bool handle_class_change(void *data, xcb_connection_t *conn, uint8_t stat return true; } +/* + * Handles the _NET_WM_STRUT_PARTIAL property for allocating space for dock clients. + * + */ +static bool handle_strut_partial_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, + xcb_atom_t name, xcb_get_property_reply_t *prop) { + DLOG("strut partial change for window 0x%08x\n", window); + + Con *con; + if ((con = con_by_window_id(window)) == NULL || con->window == NULL) { + return false; + } + + if (prop == NULL) { + xcb_generic_error_t *err = NULL; + xcb_get_property_cookie_t strut_cookie = xcb_get_property(conn, false, window, A__NET_WM_STRUT_PARTIAL, + XCB_GET_PROPERTY_TYPE_ANY, 0, UINT32_MAX); + prop = xcb_get_property_reply(conn, strut_cookie, &err); + + if (err != NULL) { + DLOG("got error when getting strut partial property: %d\n", err->error_code); + free(err); + return false; + } + + if (prop == NULL) { + return false; + } + } + + DLOG("That is con %p / %s\n", con, con->name); + + window_update_strut_partial(con->window, prop); + + /* we only handle this change for dock clients */ + if (con->parent == NULL || con->parent->type != CT_DOCKAREA) { + return true; + } + + Con *search_at = croot; + Con *output = con_get_output(con); + if (output != NULL) { + DLOG("Starting search at output %s\n", output->name); + search_at = output; + } + + /* find out the desired position of this dock window */ + if (con->window->reserved.top > 0 && con->window->reserved.bottom == 0) { + DLOG("Top dock client\n"); + con->window->dock = W_DOCK_TOP; + } else if (con->window->reserved.top == 0 && con->window->reserved.bottom > 0) { + DLOG("Bottom dock client\n"); + con->window->dock = W_DOCK_BOTTOM; + } else { + DLOG("Ignoring invalid reserved edges (_NET_WM_STRUT_PARTIAL), using position as fallback:\n"); + if (con->geometry.y < (int16_t)(search_at->rect.height / 2)) { + DLOG("geom->y = %d < rect.height / 2 = %d, it is a top dock client\n", + con->geometry.y, (search_at->rect.height / 2)); + con->window->dock = W_DOCK_TOP; + } else { + DLOG("geom->y = %d >= rect.height / 2 = %d, it is a bottom dock client\n", + con->geometry.y, (search_at->rect.height / 2)); + con->window->dock = W_DOCK_BOTTOM; + } + } + + /* find the dockarea */ + Con *dockarea = con_for_window(search_at, con->window, NULL); + assert(dockarea != NULL); + + /* attach the dock to the dock area */ + con_detach(con); + con->parent = dockarea; + TAILQ_INSERT_HEAD(&(dockarea->focus_head), con, focused); + TAILQ_INSERT_HEAD(&(dockarea->nodes_head), con, nodes); + + tree_render(); + + return true; +} + /* Returns false if the event could not be processed (e.g. the window could not * be found), true otherwise */ typedef bool (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property); @@ -1177,7 +1258,8 @@ static struct property_handler_t property_handlers[] = { {0, UINT_MAX, handle_clientleader_change}, {0, UINT_MAX, handle_transient_for}, {0, 128, handle_windowrole_change}, - {0, 128, handle_class_change}}; + {0, 128, handle_class_change}, + {0, UINT_MAX, handle_strut_partial_change}}; #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t)) /* @@ -1196,6 +1278,7 @@ void property_handlers_init(void) { property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR; property_handlers[6].atom = A_WM_WINDOW_ROLE; property_handlers[7].atom = XCB_ATOM_WM_CLASS; + property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL; } static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { diff --git a/testcases/t/102-dock.t b/testcases/t/102-dock.t index 1bac40f0..bb9c71c9 100644 --- a/testcases/t/102-dock.t +++ b/testcases/t/102-dock.t @@ -143,6 +143,22 @@ wait_for_map $window; @docked = get_dock_clients('top'); is(@docked, 1, 'dock client on top'); +# now change strut_partial to reserve space on the bottom and the dock should +# be moved to the bottom dock area +$x->change_property( + PROP_MODE_REPLACE, + $window->id, + $atomname->id, + $atomtype->id, + 32, # 32 bit integer + 12, + pack('L12', 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 1280, 0) +); + +sync_with_i3; +@docked = get_dock_clients('bottom'); +is(@docked, 1, 'dock client on bottom'); + $window->destroy; wait_for_unmap $window;