bool need_reparent;
xcb_window_t old_frame;
+ /* The container was child of floating container during the previous call of
+ * x_push_node(). This is used to remove the shape when the container is no
+ * longer floating. */
+ bool was_floating;
+
Rect rect;
Rect window_rect;
x_draw_title_border(con, p);
}
+/*
+ * Get rectangles representing the border around the child window. Some borders
+ * are adjacent to the screen-edge and thus not returned. Return value is the
+ * number of rectangles.
+ *
+ */
+static size_t x_get_border_rectangles(Con *con, xcb_rectangle_t rectangles[4]) {
+ size_t count = 0;
+ int border_style = con_border_style(con);
+
+ if (border_style != BS_NONE && con_is_leaf(con)) {
+ adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
+ Rect br = con_border_style_rect(con);
+
+ if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) {
+ rectangles[count++] = (xcb_rectangle_t){
+ .x = 0,
+ .y = 0,
+ .width = br.x,
+ .height = con->rect.height,
+ };
+ }
+ if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) {
+ rectangles[count++] = (xcb_rectangle_t){
+ .x = con->rect.width + (br.width + br.x),
+ .y = 0,
+ .width = -(br.width + br.x),
+ .height = con->rect.height,
+ };
+ }
+ if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) {
+ rectangles[count++] = (xcb_rectangle_t){
+ .x = br.x,
+ .y = con->rect.height + (br.height + br.y),
+ .width = con->rect.width + br.width,
+ .height = -(br.height + br.y),
+ };
+ }
+ /* pixel border have an additional line at the top */
+ if (border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
+ rectangles[count++] = (xcb_rectangle_t){
+ .x = br.x,
+ .y = 0,
+ .width = con->rect.width + br.width,
+ .height = br.y,
+ };
+ }
+ }
+
+ return count;
+}
+
/*
* Draws the decoration of the given container onto its parent.
*
/* 3: draw a rectangle in border color around the client */
if (p->border_style != BS_NONE && p->con_is_leaf) {
- /* We might hide some borders adjacent to the screen-edge */
- adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
- Rect br = con_border_style_rect(con);
-
- /* These rectangles represent the border around the child window
- * (left, bottom and right part). We don’t just fill the whole
- * rectangle because some children are not freely resizable and we want
- * their background color to "shine through". */
- if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) {
- draw_util_rectangle(&(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height);
- }
- if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) {
- draw_util_rectangle(&(con->frame_buffer),
- p->color->child_border, r->width + (br.width + br.x), 0,
- -(br.width + br.x), r->height);
- }
- if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) {
- draw_util_rectangle(&(con->frame_buffer),
- p->color->child_border, br.x, r->height + (br.height + br.y),
- r->width + br.width, -(br.height + br.y));
- }
- /* pixel border needs an additional line at the top */
- if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
- draw_util_rectangle(&(con->frame_buffer),
- p->color->child_border, br.x, 0, r->width + br.width, br.y);
+ /* Fill the border. We don’t just fill the whole rectangle because some
+ * children are not freely resizable and we want their background color
+ * to "shine through". */
+ xcb_rectangle_t rectangles[4];
+ size_t rectangles_count = x_get_border_rectangles(con, rectangles);
+ for (size_t i = 0; i < rectangles_count; i++) {
+ draw_util_rectangle(&(con->frame_buffer), p->color->child_border,
+ rectangles[i].x,
+ rectangles[i].y,
+ rectangles[i].width,
+ rectangles[i].height);
}
/* Highlight the side of the border at which the next window will be
* opened if we are rendering a single window within a split container
* (which is undistinguishable from a single window outside a split
* container otherwise. */
+ Rect br = con_border_style_rect(con);
if (TAILQ_NEXT(con, nodes) == NULL &&
TAILQ_PREV(con, nodes_head, nodes) == NULL &&
con->parent->type != CT_FLOATING_CON) {
state->is_hidden = should_be_hidden;
}
+/*
+ * Set the container frame shape as the union of the window shape and the
+ * shape of the frame borders.
+ */
+static void x_shape_frame(Con *con, xcb_shape_sk_t shape_kind) {
+ assert(con->window);
+
+ xcb_shape_combine(conn, XCB_SHAPE_SO_SET, shape_kind, shape_kind,
+ con->frame.id,
+ con->window_rect.x + con->border_width,
+ con->window_rect.y + con->border_width,
+ con->window->id);
+ xcb_rectangle_t rectangles[4];
+ size_t rectangles_count = x_get_border_rectangles(con, rectangles);
+ if (rectangles_count) {
+ xcb_shape_rectangles(conn, XCB_SHAPE_SO_UNION, shape_kind,
+ XCB_CLIP_ORDERING_UNSORTED, con->frame.id,
+ 0, 0, rectangles_count, rectangles);
+ }
+}
+
+/*
+ * Reset the container frame shape.
+ */
+static void x_unshape_frame(Con *con, xcb_shape_sk_t shape_kind) {
+ assert(con->window);
+
+ xcb_shape_mask(conn, XCB_SHAPE_SO_SET, shape_kind, con->frame.id, 0, 0, XCB_PIXMAP_NONE);
+}
+
+/*
+ * Shape or unshape container frame based on the con state.
+ */
+static void set_shape_state(Con *con, bool need_reshape) {
+ if (!shape_supported || con->window == NULL) {
+ return;
+ }
+
+ struct con_state *state;
+ if ((state = state_for_frame(con->frame.id)) == NULL) {
+ ELOG("window state for con %p not found\n", con);
+ return;
+ }
+
+ if (need_reshape && con_is_floating(con)) {
+ /* We need to reshape the window frame only if it already has shape. */
+ if (con->window->shaped) {
+ x_shape_frame(con, XCB_SHAPE_SK_BOUNDING);
+ }
+ if (con->window->input_shaped) {
+ x_shape_frame(con, XCB_SHAPE_SK_INPUT);
+ }
+ }
+
+ if (state->was_floating && !con_is_floating(con)) {
+ /* Remove the shape when container is no longer floating. */
+ if (con->window->shaped) {
+ x_unshape_frame(con, XCB_SHAPE_SK_BOUNDING);
+ }
+ if (con->window->input_shaped) {
+ x_unshape_frame(con, XCB_SHAPE_SK_INPUT);
+ }
+ }
+}
+
/*
* This function pushes the properties of each node of the layout tree to
* X11 if they have changed (like the map state, position of the window, …).
con->mapped = false;
}
+ bool need_reshape = false;
+
/* reparent the child window (when the window was moved due to a sticky
* container) */
if (state->need_reparent && con->window != NULL) {
con->ignore_unmap++;
DLOG("ignore_unmap for reparenting of con %p (win 0x%08x) is now %d\n",
con, con->window->id, con->ignore_unmap);
+
+ need_reshape = true;
}
+ /* We need to update shape when window frame dimensions is updated. */
+ need_reshape |= state->rect.width != rect.width ||
+ state->rect.height != rect.height ||
+ state->window_rect.width != con->window_rect.width ||
+ state->window_rect.height != con->window_rect.height;
+
+ /* We need to set shape when container becomes floating. */
+ need_reshape |= con_is_floating(con) && !state->was_floating;
+
/* The pixmap of a borderless leaf container will not be used except
* for the titlebar in a stack or tabs (issue #1013). */
bool is_pixmap_needed = (con->border_style != BS_NONE ||
fake_notify = true;
}
+ set_shape_state(con, need_reshape);
+
/* Map if map state changed, also ensure that the child window
* is changed if we are mapped and there is a new, unmapped child window.
* Unmaps are handled in x_push_node_unmaps(). */
}
state->unmap_now = (state->mapped != con->mapped) && !con->mapped;
+ state->was_floating = con_is_floating(con);
if (fake_notify) {
DLOG("Sending fake configure notify\n");
xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
}
}
+
+/*
+ * Enables or disables nonrectangular shape of the container frame.
+ */
+void x_set_shape(Con *con, xcb_shape_sk_t kind, bool enable) {
+ struct con_state *state;
+ if ((state = state_for_frame(con->frame.id)) == NULL) {
+ ELOG("window state for con %p not found\n", con);
+ return;
+ }
+
+ switch (kind) {
+ case XCB_SHAPE_SK_BOUNDING:
+ con->window->shaped = enable;
+ break;
+ case XCB_SHAPE_SK_INPUT:
+ con->window->input_shaped = enable;
+ break;
+ default:
+ ELOG("Received unknown shape event kind for con %p. This is a bug.\n",
+ con);
+ return;
+ }
+
+ if (con_is_floating(con)) {
+ if (enable) {
+ x_shape_frame(con, kind);
+ } else {
+ x_unshape_frame(con, kind);
+ }
+
+ xcb_flush(conn);
+ }
+}
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Test shape support.
+# Ticket: #2742
+use i3test;
+use ExtUtils::PkgConfig;
+
+my %sn_config;
+BEGIN {
+ %sn_config = ExtUtils::PkgConfig->find('xcb-shape');
+}
+
+use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
+use Inline C => <<'END_OF_C_CODE';
+#include <xcb/shape.h>
+
+static xcb_connection_t *conn;
+
+void init_ctx(void *connptr) {
+ conn = (xcb_connection_t*)connptr;
+}
+
+/*
+ * Set the shape for the window consisting of the following zones:
+ *
+ * +---+---+
+ * | A | B |
+ * +---+---+
+ * | C |
+ * +-------+
+ *
+ * - Zone A is completly opaque.
+ * - Zone B is clickable through (input shape).
+ * - Zone C is completly transparent (bounding shape).
+ */
+void set_shape(long window_id) {
+ xcb_rectangle_t bounding_rectangle = { 0, 0, 100, 50 };
+ xcb_shape_rectangles(conn, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING,
+ XCB_CLIP_ORDERING_UNSORTED, window_id,
+ 0, 0, 1, &bounding_rectangle);
+ xcb_rectangle_t input_rectangle = { 0, 0, 50, 50 };
+ xcb_shape_rectangles(conn, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT,
+ XCB_CLIP_ORDERING_UNSORTED, window_id,
+ 0, 0, 1, &input_rectangle);
+ xcb_flush(conn);
+}
+END_OF_C_CODE
+
+init_ctx($x->get_xcb_conn());
+
+my ($ws, $win1, $win1_focus, $win2, $win2_focus);
+
+################################################################################
+# Case 1: make floating window, then set shape
+################################################################################
+
+$ws = fresh_workspace;
+
+$win1 = open_floating_window(rect => [0, 0, 100, 100], background_color => '#ff0000');
+$win1_focus = get_focused($ws);
+
+$win2 = open_floating_window(rect => [0, 0, 100, 100], background_color => '#00ff00');
+$win2_focus = get_focused($ws);
+set_shape($win2->id);
+
+$win1->warp_pointer(75, 25);
+sync_with_i3;
+is(get_focused($ws), $win1_focus, 'focus switched to the underlying window');
+
+$win1->warp_pointer(25, 25);
+sync_with_i3;
+is(get_focused($ws), $win2_focus, 'focus switched to the top window');
+
+kill_all_windows;
+
+################################################################################
+# Case 2: set shape first, then make window floating
+################################################################################
+
+$ws = fresh_workspace;
+
+$win1 = open_window(rect => [0, 0, 100, 100], background_color => '#ff0000');
+$win1_focus = get_focused($ws);
+cmd 'floating toggle';
+
+$win2 = open_window(rect => [0, 0, 100, 100], background_color => '#00ff00');
+$win2_focus = get_focused($ws);
+set_shape($win2->id);
+cmd 'floating toggle';
+sync_with_i3;
+
+$win1->warp_pointer(75, 25);
+sync_with_i3;
+is(get_focused($ws), $win1_focus, 'focus switched to the underlying window');
+
+$win1->warp_pointer(25, 25);
+sync_with_i3;
+is(get_focused($ws), $win2_focus, 'focus switched to the top window');
+
+done_testing;