/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * x.c: Interface to X11, transfers our in-memory state to X11 (see also
+ * render.c). Basically a big state machine.
+ *
*/
-
#include "all.h"
/* Stores the X11 window ID of the currently focused window */
* every container from con_new().
*
*/
-void x_con_init(Con *con) {
+void x_con_init(Con *con, uint16_t depth) {
/* TODO: maybe create the window when rendering first? we could then even
* get the initial geometry right */
uint32_t mask = 0;
- uint32_t values[2];
-
- /* our own frames should not be managed */
- mask |= XCB_CW_OVERRIDE_REDIRECT;
- values[0] = 1;
-
- /* see include/xcb.h for the FRAME_EVENT_MASK */
- mask |= XCB_CW_EVENT_MASK;
- values[1] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW;
+ uint32_t values[5];
+
+ xcb_visualid_t visual = XCB_COPY_FROM_PARENT;
+ xcb_colormap_t win_colormap = XCB_NONE;
+ if (depth != root_depth && depth != XCB_COPY_FROM_PARENT) {
+ /* For custom visuals, we need to create a colormap before creating
+ * this window. It will be freed directly after creating the window. */
+ visual = get_visualid_by_depth(depth);
+ win_colormap = xcb_generate_id(conn);
+ xcb_create_colormap_checked(conn, XCB_COLORMAP_ALLOC_NONE, win_colormap, root, visual);
+
+ /* We explicitly set a background color and border color (even though we
+ * don’t even have a border) because the X11 server requires us to when
+ * using 32 bit color depths, see
+ * http://stackoverflow.com/questions/3645632 */
+ mask |= XCB_CW_BACK_PIXEL;
+ values[0] = root_screen->black_pixel;
+
+ mask |= XCB_CW_BORDER_PIXEL;
+ values[1] = root_screen->black_pixel;
+
+ /* our own frames should not be managed */
+ mask |= XCB_CW_OVERRIDE_REDIRECT;
+ values[2] = 1;
+
+ /* see include/xcb.h for the FRAME_EVENT_MASK */
+ mask |= XCB_CW_EVENT_MASK;
+ values[3] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW;
+
+ mask |= XCB_CW_COLORMAP;
+ values[4] = win_colormap;
+ } else {
+ /* our own frames should not be managed */
+ mask = XCB_CW_OVERRIDE_REDIRECT;
+ values[0] = 1;
+
+ /* see include/xcb.h for the FRAME_EVENT_MASK */
+ mask |= XCB_CW_EVENT_MASK;
+ values[1] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW;
+
+ mask |= XCB_CW_COLORMAP;
+ values[2] = colormap;
+ }
Rect dims = { -15, -15, 10, 10 };
- con->frame = create_window(conn, dims, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values);
+ con->frame = create_window(conn, dims, depth, visual, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values);
+
+ if (win_colormap != XCB_NONE)
+ xcb_free_colormap(conn, win_colormap);
struct con_state *state = scalloc(sizeof(struct con_state));
state->id = con->frame;
parent->layout != L_TABBED) ||
con->type == CT_FLOATING_CON)
return;
- DLOG("decoration should be rendered for con %p\n", con);
/* Skip containers whose height is 0 (for example empty dockareas) */
- if (con->rect.height == 0) {
- DLOG("height == 0, not rendering\n");
+ if (con->rect.height == 0)
return;
- }
/* Skip containers whose pixmap has not yet been created (can happen when
* decoration rendering happens recursively for a window for which
* x_push_node() was not yet called) */
- if (leaf && con->pixmap == XCB_NONE) {
- DLOG("pixmap not yet created, not rendering\n");
+ if (leaf && con->pixmap == XCB_NONE)
return;
- }
/* 1: build deco_params and compare with cache */
struct deco_render_params *p = scalloc(sizeof(struct deco_render_params));
p->con_deco_rect = con->deco_rect;
p->background = config.client.background;
p->con_is_leaf = con_is_leaf(con);
- p->font = config.font.id;
if (con->deco_render_params != NULL &&
(con->window == NULL || !con->window->name_x_changed) &&
!parent->pixmap_recreated &&
!con->pixmap_recreated &&
memcmp(p, con->deco_render_params, sizeof(struct deco_render_params)) == 0) {
- DLOG("CACHE HIT, copying existing pixmaps\n");
free(p);
goto copy_pixmaps;
}
- DLOG("CACHE MISS\n");
Con *next = con;
while ((next = TAILQ_NEXT(next, nodes))) {
- DLOG("Also invalidating cache of %p\n", next);
FREE(next->deco_render_params);
}
);
#endif
- xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, config.client.background);
+ xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) { config.client.background });
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, sizeof(background) / sizeof(xcb_rectangle_t), background);
}
* (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". */
- xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, p->color->background);
+ xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background });
xcb_rectangle_t borders[] = {
{ 0, 0, br.x, r->height },
{ 0, r->height + br.height + br.y, r->width, r->height },
xcb_rectangle_t topline = { br.x, 0, con->rect.width + br.width + br.x, br.y };
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline);
}
+
+ /* 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. */
+ if (TAILQ_NEXT(con, nodes) == NULL &&
+ TAILQ_PREV(con, nodes_head, nodes) == NULL &&
+ con->parent->type != CT_FLOATING_CON) {
+ xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->indicator });
+ if (con_orientation(con->parent) == HORIZ)
+ xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]){
+ { r->width + br.width + br.x, 0, r->width, r->height + br.height } });
+ else
+ xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]){
+ { br.x, r->height + br.height + br.y, r->width - (2 * br.x), r->height } });
+ }
+
}
- /* if this is a borderless/1pixel window, we don’t * need to render the
+ /* if this is a borderless/1pixel window, we don’t need to render the
* decoration. */
- if (p->border_style != BS_NORMAL) {
- DLOG("border style not BS_NORMAL, aborting rendering of decoration\n");
+ if (p->border_style != BS_NORMAL)
goto copy_pixmaps;
- }
/* 4: paint the bar */
- xcb_change_gc_single(conn, parent->pm_gc, XCB_GC_FOREGROUND, p->color->background);
+ xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background });
xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height };
xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect);
/* 5: draw two unconnected lines in border color */
- xcb_change_gc_single(conn, parent->pm_gc, XCB_GC_FOREGROUND, p->color->border);
+ xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
Rect *dr = &(con->deco_rect);
xcb_segment_t segments[] = {
{ dr->x, dr->y,
xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments);
/* 6: draw the title */
- uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
- uint32_t values[] = { p->color->text, p->color->background, config.font.id };
- xcb_change_gc(conn, parent->pm_gc, mask, values);
- int text_offset_y = config.font.height + (con->deco_rect.height - config.font.height) / 2 - 1;
+ set_font_colors(parent->pm_gc, p->color->text, p->color->background);
+ int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
struct Window *win = con->window;
if (win == NULL || win->name_x == NULL) {
/* this is a non-leaf container, we need to make up a good description */
// TODO: use a good description instead of just "another container"
- xcb_image_text_8(
- conn,
- strlen("another container"),
- parent->pixmap,
- parent->pm_gc,
- con->deco_rect.x + 2,
- con->deco_rect.y + text_offset_y,
- "another container"
- );
-
+ draw_text("another container", strlen("another container"), false,
+ parent->pixmap, parent->pm_gc,
+ con->deco_rect.x + 2, con->deco_rect.y + text_offset_y,
+ con->deco_rect.width - 2);
goto copy_pixmaps;
}
Con *il_parent = parent;
if (il_parent->layout != L_STACKED) {
while (1) {
- DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout);
+ //DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout);
if (il_parent->layout == L_STACKED)
indent_level++;
if (il_parent->type == CT_WORKSPACE || il_parent->type == CT_DOCKAREA || il_parent->type == CT_OUTPUT)
indent_mult++;
}
}
- DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
+ //DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
int indent_px = (indent_level * 5) * indent_mult;
- if (win->uses_net_wm_name)
- xcb_image_text_16(
- conn,
- win->name_len,
- parent->pixmap,
- parent->pm_gc,
- con->deco_rect.x + 2 + indent_px,
- con->deco_rect.y + text_offset_y,
- (xcb_char2b_t*)win->name_x
- );
- else
- xcb_image_text_8(
- conn,
- win->name_len,
- parent->pixmap,
- parent->pm_gc,
- con->deco_rect.x + 2 + indent_px,
- con->deco_rect.y + text_offset_y,
- win->name_x
- );
+ draw_text(win->name_x, win->name_len, win->uses_net_wm_name,
+ parent->pixmap, parent->pm_gc,
+ con->deco_rect.x + 2 + indent_px, con->deco_rect.y + text_offset_y,
+ con->deco_rect.width - 2 - indent_px);
+
+ /* Since we don’t clip the text at all, it might in some cases be painted
+ * on the border pixels on the right side of a window. Therefore, we draw
+ * the right border again after rendering the text (and the unconnected
+ * lines in border color). */
+
+ /* Draw a separator line after every tab (except the last one), so that
+ * tabs can be easily distinguished. */
+ if (parent->layout == L_TABBED && TAILQ_NEXT(con, nodes) != NULL) {
+ xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
+ } else {
+ xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background });
+ }
+ xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, parent->pixmap, parent->pm_gc, 4,
+ (xcb_point_t[]){
+ { dr->x + dr->width - 1, dr->y },
+ { dr->x + dr->width - 1, dr->y + dr->height },
+ { dr->x + dr->width - 2, dr->y },
+ { dr->x + dr->width - 2, dr->y + dr->height }
+ });
+
+ xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
+ xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments);
copy_pixmaps:
xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height);
}
}
rect.height = max_y + max_height;
- if (rect.height == 0) {
- DLOG("Unmapping container %p because it does not contain anything.\n", con);
+ if (rect.height == 0)
con->mapped = false;
- }
}
/* reparent the child window (when the window was moved due to a sticky
* (height == 0). */
if ((state->rect.width != rect.width ||
state->rect.height != rect.height)) {
- DLOG("CACHE: creating new pixmap for con %p (old: %d x %d, new: %d x %d)\n",
- con, state->rect.width, state->rect.height,
- rect.width, rect.height);
if (con->pixmap == 0) {
con->pixmap = xcb_generate_id(conn);
con->pm_gc = xcb_generate_id(conn);
xcb_free_pixmap(conn, con->pixmap);
xcb_free_gc(conn, con->pm_gc);
}
- xcb_create_pixmap(conn, root_depth, con->pixmap, con->frame, rect.width, rect.height);
+
+ uint16_t win_depth = root_depth;
+ if (con->window)
+ win_depth = con->window->depth;
+
+ xcb_create_pixmap(conn, win_depth, con->pixmap, con->frame, rect.width, rect.height);
+
/* For the graphics context, we disable GraphicsExposure events.
* Those will be sent when a CopyArea request cannot be fulfilled
* properly due to parts of the source being unmapped or otherwise
*/
void x_push_changes(Con *con) {
con_state *state;
+ xcb_query_pointer_cookie_t pointercookie;
+
+ /* If we need to warp later, we request the pointer position as soon as possible */
+ if (warp_to) {
+ pointercookie = xcb_query_pointer(conn, root);
+ }
DLOG("-- PUSHING WINDOW STACK --\n");
//DLOG("Disabling EnterNotify\n");
order_changed = true;
if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) {
stacking_changed = true;
- DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id);
+ //DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id);
uint32_t mask = 0;
mask |= XCB_CONFIG_WINDOW_SIBLING;
mask |= XCB_CONFIG_WINDOW_STACK_MODE;
if (stacking_changed)
ewmh_update_client_list_stacking(btt_stack, btt_stack_num);
+ DLOG("PUSHING CHANGES\n");
+ x_push_node(con);
+
+ if (warp_to) {
+ xcb_query_pointer_reply_t *pointerreply = xcb_query_pointer_reply(conn, pointercookie, NULL);
+ if (!pointerreply) {
+ ELOG("Could not query pointer position, not warping pointer\n");
+ } else {
+ int mid_x = warp_to->x + (warp_to->width / 2);
+ int mid_y = warp_to->y + (warp_to->height / 2);
+
+ Output *current = get_output_containing(pointerreply->root_x, pointerreply->root_y);
+ Output *target = get_output_containing(mid_x, mid_y);
+ if (current != target)
+ xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
+ }
+ warp_to = NULL;
+ }
+
//DLOG("Re-enabling EnterNotify\n");
values[0] = FRAME_EVENT_MASK;
CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {
}
//DLOG("Done, EnterNotify re-enabled\n");
- DLOG("\n\n PUSHING CHANGES\n\n");
- x_push_node(con);
x_deco_recurse(con);
xcb_window_t to_focus = focused->frame;
if (focused->window != NULL)
to_focus = focused->window->id;
- DLOG("focused_id = 0x%08x, to_focus = 0x%08x\n", focused_id, to_focus);
if (focused_id != to_focus) {
if (!focused->mapped) {
DLOG("Not updating focus (to %p / %s), focused window is not mapped.\n", focused, focused->name);
/* Invalidate focused_id to correctly focus new windows with the same ID */
focused_id = XCB_NONE;
} else {
- DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name);
- /* We remove XCB_EVENT_MASK_FOCUS_CHANGE from the event mask to get
- * no focus change events for our own focus changes. We only want
- * these generated by the clients. */
- if (focused->window != NULL) {
- values[0] = CHILD_EVENT_MASK & ~(XCB_EVENT_MASK_FOCUS_CHANGE);
- xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
- }
- xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME);
- if (focused->window != NULL) {
- values[0] = CHILD_EVENT_MASK;
- xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
- }
-
+ bool set_focus = true;
if (focused->window != NULL &&
focused->window->needs_take_focus) {
+ DLOG("Updating focus by sending WM_TAKE_FOCUS to window 0x%08x (focused: %p / %s)\n",
+ to_focus, focused, focused->name);
send_take_focus(to_focus);
+ set_focus = !focused->window->doesnt_accept_focus;
+ DLOG("set_focus = %d\n", set_focus);
+ }
+
+ if (set_focus) {
+ DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name);
+ /* We remove XCB_EVENT_MASK_FOCUS_CHANGE from the event mask to get
+ * no focus change events for our own focus changes. We only want
+ * these generated by the clients. */
+ if (focused->window != NULL) {
+ values[0] = CHILD_EVENT_MASK & ~(XCB_EVENT_MASK_FOCUS_CHANGE);
+ xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
+ }
+ xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME);
+ if (focused->window != NULL) {
+ values[0] = CHILD_EVENT_MASK;
+ xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
+ }
+
+ ewmh_update_active_window(to_focus);
}
- ewmh_update_active_window(to_focus);
focused_id = to_focus;
}
}
focused_id = root;
}
- if (warp_to) {
- xcb_warp_pointer_rect(conn, warp_to);
- warp_to = NULL;
- }
-
xcb_flush(conn);
- DLOG("\n\n ENDING CHANGES\n\n");
+ DLOG("ENDING CHANGES\n");
/* Disable EnterWindow events for windows which will be unmapped in
* x_push_node_unmaps() now. Unmapping windows happens when switching
* Sets up i3 specific atoms (I3_SOCKET_PATH and I3_CONFIG_PATH)
*
*/
-void x_set_i3_atoms() {
+void x_set_i3_atoms(void) {
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SOCKET_PATH, A_UTF8_STRING, 8,
(current_socketpath == NULL ? 0 : strlen(current_socketpath)),
current_socketpath);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8,
strlen(current_configpath), current_configpath);
+ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
+ strlen(shmlogname), shmlogname);
}
/*
{
warp_to = rect;
}
+
+/*
+ * Applies the given mask to the event mask of every i3 window decoration X11
+ * window. This is useful to disable EnterNotify while resizing so that focus
+ * is untouched.
+ *
+ */
+void x_mask_event_mask(uint32_t mask) {
+ uint32_t values[] = { FRAME_EVENT_MASK & mask };
+
+ con_state *state;
+ CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {
+ if (state->mapped)
+ xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
+ }
+}