bool child_mapped;
bool is_hidden;
- /** The con for which this state is. */
+ /* The con for which this state is. */
Con *con;
/* For reparenting, we have a flag (need_reparent) and the X ID of the old
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;
return state;
/* TODO: better error handling? */
- ELOG("No state found\n");
+ ELOG("No state found for window 0x%08x\n", window);
assert(false);
return NULL;
}
+/*
+ * Changes the atoms on the root window and the windows themselves to properly
+ * reflect the current focus for ewmh compliance.
+ *
+ */
+static void change_ewmh_focus(xcb_window_t new_focus, xcb_window_t old_focus) {
+ if (new_focus == old_focus) {
+ return;
+ }
+
+ ewmh_update_active_window(new_focus);
+
+ if (new_focus != XCB_WINDOW_NONE) {
+ ewmh_update_focused(new_focus, true);
+ }
+
+ if (old_focus != XCB_WINDOW_NONE) {
+ ewmh_update_focused(old_focus, false);
+ }
+}
+
/*
* Initializes the X11 part for the given container. Called exactly once for
* every container from con_new().
}
}
-/*
- * Kills the window decoration associated with the given container.
- *
- */
-void x_con_kill(Con *con) {
+static void _x_con_kill(Con *con) {
con_state *state;
if (con->colormap != XCB_NONE) {
draw_util_surface_free(conn, &(con->frame));
draw_util_surface_free(conn, &(con->frame_buffer));
- xcb_destroy_window(conn, con->frame.id);
xcb_free_pixmap(conn, con->frame_buffer.id);
state = state_for_frame(con->frame.id);
CIRCLEQ_REMOVE(&state_head, state, state);
focused_id = last_focused = XCB_NONE;
}
+/*
+ * Kills the window decoration associated with the given container.
+ *
+ */
+void x_con_kill(Con *con) {
+ _x_con_kill(con);
+ xcb_destroy_window(conn, con->frame.id);
+}
+
+/*
+ * Completely reinitializes the container's frame, without destroying the old window.
+ *
+ */
+void x_con_reframe(Con *con) {
+ _x_con_kill(con);
+ x_con_init(con);
+}
+
/*
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
*
assert(con->parent != NULL);
Rect *dr = &(con->deco_rect);
- adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
- int deco_diff_l = borders_to_hide & ADJ_LEFT_SCREEN_EDGE ? 0 : con->current_border_width;
- int deco_diff_r = borders_to_hide & ADJ_RIGHT_SCREEN_EDGE ? 0 : con->current_border_width;
- if (con->parent->layout == L_TABBED ||
- (con->parent->layout == L_STACKED && TAILQ_NEXT(con, nodes) != NULL)) {
- deco_diff_l = 0;
- deco_diff_r = 0;
- }
+ /* Left */
+ draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
+ dr->x, dr->y, 1, dr->height);
+
+ /* Right */
+ draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
+ dr->x + dr->width - 1, dr->y, 1, dr->height);
+
+ /* Top */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
dr->x, dr->y, dr->width, 1);
+ /* Bottom */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
- dr->x + deco_diff_l, dr->y + dr->height - 1, dr->width - (deco_diff_l + deco_diff_r), 1);
+ dr->x, dr->y + dr->height - 1, dr->width, 1);
}
static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p) {
dr->height);
}
- /* Draw a 1px separator line before and after every tab, so that tabs can
- * be easily distinguished. */
- if (con->parent->layout == L_TABBED) {
- /* Left side */
- draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
- dr->x, dr->y, 1, dr->height);
+ /* Redraw the border. */
+ 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);
- /* Right side */
- draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
- dr->x + dr->width - 1, dr->y, 1, dr->height);
+ 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,
+ };
+ }
}
- /* Redraw the border. */
- x_draw_title_border(con, p);
+ return count;
}
/*
/* 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 = ADJ_NONE;
- 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 childs 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) {
draw_util_rectangle(&(parent->frame_buffer), p->color->background,
con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height);
- /* 5: draw two unconnected horizontal lines in border color */
+ /* 5: draw title border */
x_draw_title_border(con, p);
/* 6: draw the title */
goto after_title;
}
+ const int title_padding = logical_px(2);
+ const int deco_width = (int)con->deco_rect.width;
int mark_width = 0;
if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
char *formatted_mark = sstrdup("");
i3String *mark = i3string_from_utf8(formatted_mark);
mark_width = predict_text_width(mark);
+ int mark_offset_x = (config.title_align == ALIGN_RIGHT)
+ ? title_padding
+ : deco_width - mark_width - title_padding;
+
draw_util_text(mark, &(parent->frame_buffer),
p->color->text, p->color->background,
- con->deco_rect.x + con->deco_rect.width - mark_width - logical_px(2),
+ con->deco_rect.x + mark_offset_x,
con->deco_rect.y + text_offset_y, mark_width);
-
I3STRING_FREE(mark);
+
+ mark_width += title_padding;
}
FREE(formatted_mark);
goto copy_pixmaps;
}
+ int title_offset_x;
+ switch (config.title_align) {
+ case ALIGN_LEFT:
+ /* (pad)[text ](pad)[mark + its pad) */
+ title_offset_x = title_padding;
+ break;
+ case ALIGN_CENTER:
+ /* (pad)[ text ](pad)[mark + its pad)
+ * To center the text inside its allocated space, the surface
+ * between the brackets, we use the formula
+ * (surface_width - predict_text_width) / 2
+ * where surface_width = deco_width - 2 * pad - mark_width
+ * so, offset = pad + (surface_width - predict_text_width) / 2 =
+ * = … = (deco_width - mark_width - predict_text_width) / 2 */
+ title_offset_x = max(title_padding, (deco_width - mark_width - predict_text_width(title)) / 2);
+ break;
+ case ALIGN_RIGHT:
+ /* [mark + its pad](pad)[ text](pad) */
+ title_offset_x = max(title_padding + mark_width, deco_width - title_padding - predict_text_width(title));
+ break;
+ }
+
draw_util_text(title, &(parent->frame_buffer),
p->color->text, p->color->background,
- con->deco_rect.x + logical_px(2),
+ con->deco_rect.x + title_offset_x,
con->deco_rect.y + text_offset_y,
- con->deco_rect.width - mark_width - 2 * logical_px(2));
+ deco_width - mark_width - 2 * title_padding);
if (con->title_format != NULL) {
I3STRING_FREE(title);
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 ||
* background and only afterwards change the window size. This reduces
* flickering. */
- /* As the pixmap only depends on the size and not on the position, it
- * is enough to check if width/height have changed. Also, we don’t
- * create a pixmap at all when the window is actually not visible
- * (height == 0) or when it is not needed. */
- bool has_rect_changed = (state->rect.width != rect.width || state->rect.height != rect.height);
+ bool has_rect_changed = (state->rect.x != rect.x || state->rect.y != rect.y ||
+ state->rect.width != rect.width || state->rect.height != rect.height);
/* Check if the container has an unneeded pixmap left over from
* previously having a border or titlebar. */
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");
to_focus, focused, focused->name);
send_take_focus(to_focus, last_timestamp);
- ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE));
+ change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused);
if (to_focus != last_focused && is_con_attached(focused))
ipc_send_window_event("focus", focused);
xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
}
- ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE));
+ change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused);
if (to_focus != XCB_NONE && to_focus != last_focused && focused->window != NULL && is_con_attached(focused))
ipc_send_window_event("focus", focused);
* root window in order to avoid an X11 fallback mechanism causing a ghosting effect (see #1378). */
DLOG("Still no window focused, better set focus to the EWMH support window (%d)\n", ewmh_window);
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, ewmh_window, last_timestamp);
- ewmh_update_active_window(XCB_WINDOW_NONE);
+ change_ewmh_focus(XCB_WINDOW_NONE, last_focused);
+
focused_id = ewmh_window;
}
* Set up the I3_SHMLOG_PATH atom.
*
*/
-void update_shmlog_atom() {
+void update_shmlog_atom(void) {
if (*shmlogname == '\0') {
xcb_delete_property(conn, root, A_I3_SHMLOG_PATH);
} else {
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);
+ }
+}