2010-03-27 10:25:51 -04:00
|
|
|
|
/*
|
|
|
|
|
* vim:ts=4:sw=4:expandtab
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "all.h"
|
|
|
|
|
|
|
|
|
|
/* Stores the X11 window ID of the currently focused window */
|
2011-03-20 11:26:36 -04:00
|
|
|
|
xcb_window_t focused_id = XCB_NONE;
|
2010-03-27 10:25:51 -04:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Describes the X11 state we may modify (map state, position, window stack).
|
|
|
|
|
* There is one entry per container. The state represents the current situation
|
|
|
|
|
* as X11 sees it (with the exception of the order in the state_head CIRCLEQ,
|
|
|
|
|
* which represents the order that will be pushed to X11, while old_state_head
|
|
|
|
|
* represents the current order). It will be updated in x_push_changes().
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
typedef struct con_state {
|
|
|
|
|
xcb_window_t id;
|
|
|
|
|
bool mapped;
|
2010-11-28 16:08:34 -05:00
|
|
|
|
bool child_mapped;
|
2010-09-01 12:11:01 -04:00
|
|
|
|
|
|
|
|
|
/* For reparenting, we have a flag (need_reparent) and the X ID of the old
|
|
|
|
|
* frame this window was in. The latter is necessary because we need to
|
|
|
|
|
* ignore UnmapNotify events (by changing the window event mask). */
|
|
|
|
|
bool need_reparent;
|
|
|
|
|
xcb_window_t old_frame;
|
|
|
|
|
|
2010-03-27 10:25:51 -04:00
|
|
|
|
Rect rect;
|
|
|
|
|
Rect window_rect;
|
|
|
|
|
|
|
|
|
|
bool initial;
|
|
|
|
|
|
2010-11-14 10:41:46 -05:00
|
|
|
|
char *name;
|
|
|
|
|
|
2010-03-27 10:25:51 -04:00
|
|
|
|
CIRCLEQ_ENTRY(con_state) state;
|
|
|
|
|
CIRCLEQ_ENTRY(con_state) old_state;
|
|
|
|
|
} con_state;
|
|
|
|
|
|
|
|
|
|
CIRCLEQ_HEAD(state_head, con_state) state_head =
|
|
|
|
|
CIRCLEQ_HEAD_INITIALIZER(state_head);
|
|
|
|
|
|
|
|
|
|
CIRCLEQ_HEAD(old_state_head, con_state) old_state_head =
|
|
|
|
|
CIRCLEQ_HEAD_INITIALIZER(old_state_head);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Returns the container state for the given frame. This function always
|
|
|
|
|
* returns a container state (otherwise, there is a bug in the code and the
|
|
|
|
|
* container state of a container for which x_con_init() was not called was
|
|
|
|
|
* requested).
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
static con_state *state_for_frame(xcb_window_t window) {
|
|
|
|
|
con_state *state;
|
|
|
|
|
CIRCLEQ_FOREACH(state, &state_head, state)
|
|
|
|
|
if (state->id == window)
|
|
|
|
|
return state;
|
|
|
|
|
|
|
|
|
|
/* TODO: better error handling? */
|
|
|
|
|
ELOG("No state found\n");
|
2010-05-31 17:08:16 -04:00
|
|
|
|
assert(false);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Initializes the X11 part for the given container. Called exactly once for
|
|
|
|
|
* every container from con_new().
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void x_con_init(Con *con) {
|
|
|
|
|
/* 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;
|
|
|
|
|
|
|
|
|
|
/* We want to know when… */
|
|
|
|
|
mask |= XCB_CW_EVENT_MASK;
|
|
|
|
|
values[1] = FRAME_EVENT_MASK;
|
|
|
|
|
|
|
|
|
|
Rect dims = { -15, -15, 10, 10 };
|
2010-11-26 18:26:51 -05:00
|
|
|
|
con->frame = create_window(conn, dims, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
|
|
|
|
|
struct con_state *state = scalloc(sizeof(struct con_state));
|
|
|
|
|
state->id = con->frame;
|
|
|
|
|
state->mapped = false;
|
|
|
|
|
state->initial = true;
|
|
|
|
|
CIRCLEQ_INSERT_HEAD(&state_head, state, state);
|
|
|
|
|
CIRCLEQ_INSERT_HEAD(&old_state_head, state, old_state);
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("adding new state for window id 0x%08x\n", state->id);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
}
|
|
|
|
|
|
2010-04-17 10:41:20 -04:00
|
|
|
|
/*
|
|
|
|
|
* Re-initializes the associated X window state for this container. You have
|
|
|
|
|
* to call this when you assign a client to an empty container to ensure that
|
|
|
|
|
* its state gets updated correctly.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void x_reinit(Con *con) {
|
|
|
|
|
struct con_state *state;
|
|
|
|
|
|
|
|
|
|
if ((state = state_for_frame(con->frame)) == NULL) {
|
|
|
|
|
ELOG("window state not found\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("resetting state %p to initial\n", state);
|
2010-04-17 10:41:20 -04:00
|
|
|
|
state->initial = true;
|
2010-11-28 16:08:34 -05:00
|
|
|
|
state->child_mapped = false;
|
2010-04-17 10:41:20 -04:00
|
|
|
|
memset(&(state->window_rect), 0, sizeof(Rect));
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-01 12:11:01 -04:00
|
|
|
|
/*
|
|
|
|
|
* Reparents the child window of the given container (necessary for sticky
|
|
|
|
|
* containers). The reparenting happens in the next call of x_push_changes().
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void x_reparent_child(Con *con, Con *old) {
|
|
|
|
|
struct con_state *state;
|
|
|
|
|
if ((state = state_for_frame(con->frame)) == NULL) {
|
|
|
|
|
ELOG("window state for con not found\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state->need_reparent = true;
|
|
|
|
|
state->old_frame = old->frame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Moves a child window from Container src to Container dest.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void x_move_win(Con *src, Con *dest) {
|
|
|
|
|
struct con_state *state_src, *state_dest;
|
|
|
|
|
|
|
|
|
|
if ((state_src = state_for_frame(src->frame)) == NULL) {
|
|
|
|
|
ELOG("window state for src not found\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((state_dest = state_for_frame(dest->frame)) == NULL) {
|
|
|
|
|
ELOG("window state for dest not found\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-05 20:38:21 -05:00
|
|
|
|
Rect zero = { 0, 0, 0, 0 };
|
2010-09-01 12:11:01 -04:00
|
|
|
|
if (memcmp(&(state_dest->window_rect), &(zero), sizeof(Rect)) == 0) {
|
|
|
|
|
memcpy(&(state_dest->window_rect), &(state_src->window_rect), sizeof(Rect));
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("COPYING RECT\n");
|
2010-09-01 12:11:01 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-07-13 05:35:05 -04:00
|
|
|
|
/*
|
|
|
|
|
* Kills the window decoration associated with the given container.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2010-03-27 10:25:51 -04:00
|
|
|
|
void x_con_kill(Con *con) {
|
|
|
|
|
con_state *state;
|
|
|
|
|
|
|
|
|
|
xcb_destroy_window(conn, con->frame);
|
|
|
|
|
state = state_for_frame(con->frame);
|
|
|
|
|
CIRCLEQ_REMOVE(&state_head, state, state);
|
|
|
|
|
CIRCLEQ_REMOVE(&old_state_head, state, old_state);
|
2011-01-04 16:39:24 -05:00
|
|
|
|
FREE(state->name);
|
|
|
|
|
free(state);
|
2010-12-11 11:03:53 -05:00
|
|
|
|
|
|
|
|
|
/* Invalidate focused_id to correctly focus new windows with the same ID */
|
|
|
|
|
focused_id = XCB_NONE;
|
2010-03-27 10:25:51 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
|
|
|
|
|
*
|
|
|
|
|
*/
|
2011-03-18 12:07:56 -04:00
|
|
|
|
bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom) {
|
2010-03-27 10:25:51 -04:00
|
|
|
|
xcb_get_property_cookie_t cookie;
|
2011-03-18 09:36:36 -04:00
|
|
|
|
xcb_icccm_get_wm_protocols_reply_t protocols;
|
2010-03-27 10:25:51 -04:00
|
|
|
|
bool result = false;
|
|
|
|
|
|
2011-03-18 09:36:36 -04:00
|
|
|
|
cookie = xcb_icccm_get_wm_protocols_unchecked(conn, window, A_WM_PROTOCOLS);
|
|
|
|
|
if (xcb_icccm_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
|
2010-03-27 10:25:51 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
/* Check if the client’s protocols have the requested atom set */
|
|
|
|
|
for (uint32_t i = 0; i < protocols.atoms_len; i++)
|
|
|
|
|
if (protocols.atoms[i] == atom)
|
|
|
|
|
result = true;
|
|
|
|
|
|
2011-03-18 09:36:36 -04:00
|
|
|
|
xcb_icccm_get_wm_protocols_reply_wipe(&protocols);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2010-07-13 05:35:05 -04:00
|
|
|
|
/*
|
|
|
|
|
* Kills the given X11 window using WM_DELETE_WINDOW (if supported).
|
|
|
|
|
*
|
|
|
|
|
*/
|
2010-03-27 10:25:51 -04:00
|
|
|
|
void x_window_kill(xcb_window_t window) {
|
|
|
|
|
/* if this window does not support WM_DELETE_WINDOW, we kill it the hard way */
|
2011-03-18 09:36:36 -04:00
|
|
|
|
if (!window_supports_protocol(window, A_WM_DELETE_WINDOW)) {
|
2010-03-27 10:25:51 -04:00
|
|
|
|
LOG("Killing window the hard way\n");
|
|
|
|
|
xcb_kill_client(conn, window);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
xcb_client_message_event_t ev;
|
|
|
|
|
|
|
|
|
|
memset(&ev, 0, sizeof(xcb_client_message_event_t));
|
|
|
|
|
|
|
|
|
|
ev.response_type = XCB_CLIENT_MESSAGE;
|
|
|
|
|
ev.window = window;
|
2011-03-18 09:36:36 -04:00
|
|
|
|
ev.type = A_WM_PROTOCOLS;
|
2010-03-27 10:25:51 -04:00
|
|
|
|
ev.format = 32;
|
2011-03-18 09:36:36 -04:00
|
|
|
|
ev.data.data32[0] = A_WM_DELETE_WINDOW;
|
2010-03-27 10:25:51 -04:00
|
|
|
|
ev.data.data32[1] = XCB_CURRENT_TIME;
|
|
|
|
|
|
|
|
|
|
LOG("Sending WM_DELETE to the client\n");
|
|
|
|
|
xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
|
|
|
|
|
xcb_flush(conn);
|
|
|
|
|
}
|
|
|
|
|
|
2010-07-13 05:35:05 -04:00
|
|
|
|
/*
|
|
|
|
|
* Draws the decoration of the given container onto its parent.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2010-03-27 10:25:51 -04:00
|
|
|
|
void x_draw_decoration(Con *con) {
|
2011-03-20 09:07:16 -04:00
|
|
|
|
const Con *parent = con->parent;
|
2010-11-26 13:24:14 -05:00
|
|
|
|
/* This code needs to run for:
|
2010-11-13 16:39:59 -05:00
|
|
|
|
* • leaf containers
|
2011-02-02 11:56:29 -05:00
|
|
|
|
* • non-leaf containers which are in a stacked/tabbed container
|
2010-11-26 13:24:14 -05:00
|
|
|
|
*
|
|
|
|
|
* It does not need to run for:
|
|
|
|
|
* • floating containers (they don’t have a decoration)
|
2010-11-13 16:39:59 -05:00
|
|
|
|
*/
|
2011-02-02 11:56:29 -05:00
|
|
|
|
if ((!con_is_leaf(con) &&
|
2011-03-20 09:07:16 -04:00
|
|
|
|
parent->layout != L_STACKED &&
|
|
|
|
|
parent->layout != L_TABBED) ||
|
2010-11-26 13:24:14 -05:00
|
|
|
|
con->type == CT_FLOATING_CON)
|
2010-11-13 12:15:15 -05:00
|
|
|
|
return;
|
2010-11-13 16:39:59 -05:00
|
|
|
|
DLOG("decoration should be rendered for con %p\n", con);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
|
2011-03-20 09:07:16 -04:00
|
|
|
|
/* Skip containers whose height is 0 (for example empty dockareas) */
|
|
|
|
|
if (con->rect.height == 0) {
|
|
|
|
|
DLOG("height == 0, not rendering\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 1: build deco_params and compare with cache */
|
|
|
|
|
struct deco_render_params *p = scalloc(sizeof(struct deco_render_params));
|
|
|
|
|
|
|
|
|
|
/* find out which colors to use */
|
2010-11-13 12:15:15 -05:00
|
|
|
|
if (con->urgent)
|
2011-03-20 09:07:16 -04:00
|
|
|
|
p->color = &config.client.urgent;
|
2010-11-13 12:15:15 -05:00
|
|
|
|
else if (con == focused)
|
2011-03-20 09:07:16 -04:00
|
|
|
|
p->color = &config.client.focused;
|
|
|
|
|
else if (con == TAILQ_FIRST(&(parent->focus_head)))
|
|
|
|
|
p->color = &config.client.focused_inactive;
|
2010-11-13 12:15:15 -05:00
|
|
|
|
else
|
2011-03-20 09:07:16 -04:00
|
|
|
|
p->color = &config.client.unfocused;
|
2010-03-27 10:25:51 -04:00
|
|
|
|
|
2011-03-20 09:07:16 -04:00
|
|
|
|
p->border_style = con_border_style(con);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
|
2011-03-18 15:47:59 -04:00
|
|
|
|
Rect *r = &(con->rect);
|
|
|
|
|
Rect *w = &(con->window_rect);
|
2011-03-20 09:07:16 -04:00
|
|
|
|
p->con_rect = (struct width_height){ r->width, r->height };
|
|
|
|
|
p->con_window_rect = (struct width_height){ w->width, w->height };
|
2011-03-20 13:07:07 -04:00
|
|
|
|
p->con_deco_rect = con->deco_rect;
|
2011-03-20 09:07:16 -04:00
|
|
|
|
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) &&
|
|
|
|
|
!con->parent->pixmap_recreated &&
|
|
|
|
|
memcmp(p, con->deco_render_params, sizeof(struct deco_render_params)) == 0) {
|
2011-04-18 15:10:50 -04:00
|
|
|
|
DLOG("CACHE HIT, not re-rendering\n");
|
2011-03-20 09:07:16 -04:00
|
|
|
|
free(p);
|
2011-04-18 15:10:50 -04:00
|
|
|
|
return;
|
2011-03-20 09:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DLOG("CACHE MISS\n");
|
2011-03-20 13:17:18 -04:00
|
|
|
|
Con *next = con;
|
|
|
|
|
while ((next = TAILQ_NEXT(next, nodes))) {
|
|
|
|
|
DLOG("Also invalidating cache of %p\n", next);
|
|
|
|
|
FREE(next->deco_render_params);
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-20 09:07:16 -04:00
|
|
|
|
FREE(con->deco_render_params);
|
|
|
|
|
con->deco_render_params = p;
|
|
|
|
|
|
|
|
|
|
if (con->window != NULL && con->window->name_x_changed)
|
|
|
|
|
con->window->name_x_changed = false;
|
|
|
|
|
|
|
|
|
|
con->parent->pixmap_recreated = false;
|
2011-03-18 15:47:59 -04:00
|
|
|
|
|
2011-05-01 13:46:41 -04:00
|
|
|
|
/* If the con is in fullscreen mode, the decoration height we work with is set to 0 */
|
|
|
|
|
Rect deco_rect = con->deco_rect;
|
|
|
|
|
if (con_get_fullscreen_con(con->parent) == con)
|
|
|
|
|
deco_rect.height = 0;
|
|
|
|
|
|
2011-03-20 09:07:16 -04:00
|
|
|
|
/* 2: draw the client.background, but only for the parts around the client_rect */
|
2011-05-11 16:01:09 -04:00
|
|
|
|
if (con->window != NULL) {
|
|
|
|
|
xcb_rectangle_t background[] = {
|
|
|
|
|
/* top area */
|
|
|
|
|
{ 0, 0, r->width, w->y },
|
|
|
|
|
/* bottom area */
|
|
|
|
|
{ 0, (w->y + w->height), r->width, r->height - (w->y + w->height) },
|
|
|
|
|
/* left area */
|
|
|
|
|
{ 0, 0, w->x, r->height },
|
|
|
|
|
/* right area */
|
|
|
|
|
{ w->x + w->width, 0, r->width - (w->x + w->width), r->height }
|
|
|
|
|
};
|
2011-03-18 15:47:59 -04:00
|
|
|
|
#if 0
|
2011-05-11 16:01:09 -04:00
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
DLOG("rect is (%d, %d) with %d x %d\n",
|
|
|
|
|
background[i].x,
|
|
|
|
|
background[i].y,
|
|
|
|
|
background[i].width,
|
|
|
|
|
background[i].height
|
|
|
|
|
);
|
2011-03-18 15:47:59 -04:00
|
|
|
|
#endif
|
|
|
|
|
|
2011-05-11 16:01:09 -04:00
|
|
|
|
xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, config.client.background);
|
|
|
|
|
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, sizeof(background) / sizeof(xcb_rectangle_t), background);
|
|
|
|
|
}
|
2011-03-18 15:47:59 -04:00
|
|
|
|
|
|
|
|
|
/* 3: draw a rectangle in border color around the client */
|
2011-03-20 09:07:16 -04:00
|
|
|
|
if (p->border_style != BS_NONE && p->con_is_leaf) {
|
2010-11-13 16:39:59 -05:00
|
|
|
|
Rect br = con_border_style_rect(con);
|
|
|
|
|
#if 0
|
|
|
|
|
DLOG("con->rect spans %d x %d\n", con->rect.width, con->rect.height);
|
2011-03-18 15:47:59 -04:00
|
|
|
|
DLOG("border_rect spans (%d, %d) with %d x %d\n", br.x, br.y, br.width, br.height);
|
2010-11-13 16:39:59 -05:00
|
|
|
|
DLOG("window_rect spans (%d, %d) with %d x %d\n", con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height);
|
|
|
|
|
#endif
|
|
|
|
|
|
2010-11-14 15:17:27 -05:00
|
|
|
|
/* These rectangles represents 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". */
|
2011-03-20 09:07:16 -04:00
|
|
|
|
xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, p->color->background);
|
2010-11-14 15:17:27 -05:00
|
|
|
|
xcb_rectangle_t borders[] = {
|
|
|
|
|
{ 0, 0, br.x, r->height },
|
|
|
|
|
{ 0, r->height + br.height + br.y, r->width, r->height },
|
|
|
|
|
{ r->width + br.width + br.x, 0, r->width, r->height }
|
2010-11-13 16:39:59 -05:00
|
|
|
|
};
|
2011-03-20 09:07:16 -04:00
|
|
|
|
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 3, borders);
|
2010-11-13 16:39:59 -05:00
|
|
|
|
/* 1pixel border needs an additional line at the top */
|
2011-03-20 09:07:16 -04:00
|
|
|
|
if (p->border_style == BS_1PIXEL) {
|
2010-11-13 16:39:59 -05:00
|
|
|
|
xcb_rectangle_t topline = { br.x, 0, con->rect.width + br.width + br.x, br.y };
|
2011-03-20 09:07:16 -04:00
|
|
|
|
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline);
|
2010-11-13 16:39:59 -05:00
|
|
|
|
}
|
2010-11-13 12:15:15 -05:00
|
|
|
|
}
|
2010-11-13 16:39:59 -05:00
|
|
|
|
|
|
|
|
|
/* if this is a borderless/1pixel window, we don’t * need to render the
|
|
|
|
|
* decoration. */
|
2011-03-20 09:07:16 -04:00
|
|
|
|
if (p->border_style != BS_NORMAL) {
|
2010-11-13 16:39:59 -05:00
|
|
|
|
DLOG("border style not BS_NORMAL, aborting rendering of decoration\n");
|
2011-04-18 15:10:50 -04:00
|
|
|
|
goto update_pixmaps;
|
2010-11-13 16:39:59 -05:00
|
|
|
|
}
|
2010-03-27 10:25:51 -04:00
|
|
|
|
|
2011-03-18 15:47:59 -04:00
|
|
|
|
/* 4: paint the bar */
|
2011-03-20 09:07:16 -04:00
|
|
|
|
xcb_change_gc_single(conn, parent->pm_gc, XCB_GC_FOREGROUND, p->color->background);
|
2010-11-13 12:15:15 -05:00
|
|
|
|
xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height };
|
2011-03-20 09:07:16 -04:00
|
|
|
|
xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect);
|
2010-04-13 11:46:54 -04:00
|
|
|
|
|
2011-03-18 15:47:59 -04:00
|
|
|
|
/* 5: draw the two lines in border color */
|
2011-03-20 09:07:16 -04:00
|
|
|
|
xcb_draw_line(conn, parent->pixmap, parent->pm_gc, p->color->border,
|
2010-11-13 12:15:15 -05:00
|
|
|
|
con->deco_rect.x, /* x */
|
|
|
|
|
con->deco_rect.y, /* y */
|
|
|
|
|
con->deco_rect.x + con->deco_rect.width, /* to_x */
|
|
|
|
|
con->deco_rect.y); /* to_y */
|
2011-03-20 09:07:16 -04:00
|
|
|
|
xcb_draw_line(conn, parent->pixmap, parent->pm_gc, p->color->border,
|
2010-11-13 12:15:15 -05:00
|
|
|
|
con->deco_rect.x, /* x */
|
|
|
|
|
con->deco_rect.y + con->deco_rect.height - 1, /* y */
|
|
|
|
|
con->deco_rect.x + con->deco_rect.width, /* to_x */
|
|
|
|
|
con->deco_rect.y + con->deco_rect.height - 1); /* to_y */
|
|
|
|
|
|
2011-03-18 15:47:59 -04:00
|
|
|
|
/* 6: draw the title */
|
2010-11-14 18:14:49 -05:00
|
|
|
|
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
|
2011-03-20 09:07:16 -04:00
|
|
|
|
uint32_t values[] = { p->color->text, p->color->background, config.font.id };
|
|
|
|
|
xcb_change_gc(conn, parent->pm_gc, mask, values);
|
2011-03-10 17:20:17 -05:00
|
|
|
|
int text_offset_y = config.font.height + (con->deco_rect.height - config.font.height) / 2 - 1;
|
2010-11-13 16:39:59 -05:00
|
|
|
|
|
|
|
|
|
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"),
|
2011-03-20 09:07:16 -04:00
|
|
|
|
parent->pixmap,
|
|
|
|
|
parent->pm_gc,
|
2010-11-13 16:39:59 -05:00
|
|
|
|
con->deco_rect.x + 2,
|
2010-11-14 18:14:49 -05:00
|
|
|
|
con->deco_rect.y + text_offset_y,
|
2010-11-13 16:39:59 -05:00
|
|
|
|
"another container"
|
|
|
|
|
);
|
2011-03-20 09:07:16 -04:00
|
|
|
|
|
2011-04-18 15:10:50 -04:00
|
|
|
|
goto update_pixmaps;
|
2010-11-13 16:39:59 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int indent_level = 0,
|
|
|
|
|
indent_mult = 0;
|
|
|
|
|
Con *il_parent = con->parent;
|
2011-02-14 10:35:48 -05:00
|
|
|
|
if (il_parent->layout != L_STACKED) {
|
2010-11-13 16:39:59 -05:00
|
|
|
|
while (1) {
|
|
|
|
|
DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout);
|
|
|
|
|
if (il_parent->layout == L_STACKED)
|
|
|
|
|
indent_level++;
|
2011-02-20 17:43:03 -05:00
|
|
|
|
if (il_parent->type == CT_WORKSPACE || il_parent->type == CT_DOCKAREA || il_parent->type == CT_OUTPUT)
|
2010-11-13 16:39:59 -05:00
|
|
|
|
break;
|
|
|
|
|
il_parent = il_parent->parent;
|
|
|
|
|
indent_mult++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
|
|
|
|
|
int indent_px = (indent_level * 5) * indent_mult;
|
|
|
|
|
|
2010-04-13 11:46:54 -04:00
|
|
|
|
if (win->uses_net_wm_name)
|
|
|
|
|
xcb_image_text_16(
|
|
|
|
|
conn,
|
|
|
|
|
win->name_len,
|
2011-03-20 09:07:16 -04:00
|
|
|
|
parent->pixmap,
|
|
|
|
|
parent->pm_gc,
|
2010-11-13 16:39:59 -05:00
|
|
|
|
con->deco_rect.x + 2 + indent_px,
|
2010-11-14 18:14:49 -05:00
|
|
|
|
con->deco_rect.y + text_offset_y,
|
2010-04-13 11:46:54 -04:00
|
|
|
|
(xcb_char2b_t*)win->name_x
|
|
|
|
|
);
|
|
|
|
|
else
|
|
|
|
|
xcb_image_text_8(
|
|
|
|
|
conn,
|
|
|
|
|
win->name_len,
|
2011-03-20 09:07:16 -04:00
|
|
|
|
parent->pixmap,
|
|
|
|
|
parent->pm_gc,
|
2010-11-13 16:39:59 -05:00
|
|
|
|
con->deco_rect.x + 2 + indent_px,
|
2010-11-14 18:14:49 -05:00
|
|
|
|
con->deco_rect.y + text_offset_y,
|
2010-04-13 11:46:54 -04:00
|
|
|
|
win->name_x
|
|
|
|
|
);
|
2011-03-20 09:07:16 -04:00
|
|
|
|
|
2011-04-18 15:10:50 -04:00
|
|
|
|
update_pixmaps:
|
|
|
|
|
xcb_clear_area(conn, false, con->frame, 0, 0, con->rect.width, con->rect.height);
|
|
|
|
|
xcb_clear_area(conn, false, parent->frame, 0, 0, parent->rect.width, parent->rect.height);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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, …).
|
|
|
|
|
* It recursively traverses all children of the given node.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2011-05-01 12:48:30 -04:00
|
|
|
|
void x_push_node(Con *con) {
|
2010-03-27 10:25:51 -04:00
|
|
|
|
Con *current;
|
|
|
|
|
con_state *state;
|
2010-11-14 12:52:40 -05:00
|
|
|
|
Rect rect = con->rect;
|
2010-03-27 10:25:51 -04:00
|
|
|
|
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("Pushing changes for node %p / %s\n", con, con->name);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
state = state_for_frame(con->frame);
|
|
|
|
|
|
2010-11-14 10:41:46 -05:00
|
|
|
|
if (state->name != NULL) {
|
2010-11-14 16:35:44 -05:00
|
|
|
|
DLOG("pushing name %s for con %p\n", state->name, con);
|
2010-11-14 10:41:46 -05:00
|
|
|
|
|
|
|
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->frame,
|
2011-03-18 09:36:36 -04:00
|
|
|
|
A_WM_NAME, A_STRING, 8, strlen(state->name), state->name);
|
2010-11-14 10:41:46 -05:00
|
|
|
|
FREE(state->name);
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-14 12:52:40 -05:00
|
|
|
|
if (con->window == NULL) {
|
|
|
|
|
/* Calculate the height of all window decorations which will be drawn on to
|
|
|
|
|
* this frame. */
|
|
|
|
|
uint32_t max_y = 0, max_height = 0;
|
|
|
|
|
TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
|
|
|
|
|
Rect *dr = &(current->deco_rect);
|
|
|
|
|
if (dr->y >= max_y && dr->height >= max_height) {
|
|
|
|
|
max_y = dr->y;
|
|
|
|
|
max_height = dr->height;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
rect.height = max_y + max_height;
|
|
|
|
|
if (rect.height == 0) {
|
|
|
|
|
DLOG("Unmapping container because it does not contain anything atm.\n");
|
|
|
|
|
con->mapped = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-01 12:11:01 -04:00
|
|
|
|
/* reparent the child window (when the window was moved due to a sticky
|
|
|
|
|
* container) */
|
|
|
|
|
if (state->need_reparent && con->window != NULL) {
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("Reparenting child window\n");
|
2010-09-01 12:11:01 -04:00
|
|
|
|
|
|
|
|
|
/* Temporarily set the event masks to XCB_NONE so that we won’t get
|
|
|
|
|
* UnmapNotify events (otherwise the handler would close the container).
|
|
|
|
|
* These events are generated automatically when reparenting. */
|
|
|
|
|
uint32_t values[] = { XCB_NONE };
|
|
|
|
|
xcb_change_window_attributes(conn, state->old_frame, XCB_CW_EVENT_MASK, values);
|
|
|
|
|
xcb_change_window_attributes(conn, con->window->id, XCB_CW_EVENT_MASK, values);
|
|
|
|
|
|
|
|
|
|
xcb_reparent_window(conn, con->window->id, con->frame, 0, 0);
|
|
|
|
|
|
|
|
|
|
values[0] = FRAME_EVENT_MASK;
|
|
|
|
|
xcb_change_window_attributes(conn, state->old_frame, XCB_CW_EVENT_MASK, values);
|
|
|
|
|
values[0] = CHILD_EVENT_MASK;
|
|
|
|
|
xcb_change_window_attributes(conn, con->window->id, XCB_CW_EVENT_MASK, values);
|
|
|
|
|
|
|
|
|
|
state->old_frame = XCB_NONE;
|
|
|
|
|
state->need_reparent = false;
|
2010-11-20 13:11:43 -05:00
|
|
|
|
|
|
|
|
|
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);
|
2010-09-01 12:11:01 -04:00
|
|
|
|
}
|
|
|
|
|
|
2010-11-27 10:44:45 -05:00
|
|
|
|
bool fake_notify = false;
|
|
|
|
|
/* set new position if rect changed */
|
|
|
|
|
if (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0) {
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height);
|
2010-11-27 10:44:45 -05:00
|
|
|
|
xcb_set_window_rect(conn, con->frame, rect);
|
2011-03-20 09:07:16 -04:00
|
|
|
|
|
|
|
|
|
/* As the pixmap only depends on the size and not on the position, it
|
2011-04-01 15:54:45 -04:00
|
|
|
|
* 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). */
|
|
|
|
|
if (rect.height > 0 &&
|
|
|
|
|
(state->rect.width != rect.width ||
|
|
|
|
|
state->rect.height != rect.height)) {
|
2011-03-20 09:07:16 -04:00
|
|
|
|
DLOG("CACHE: creating new pixmap\n");
|
|
|
|
|
if (con->pixmap == 0) {
|
|
|
|
|
con->pixmap = xcb_generate_id(conn);
|
|
|
|
|
con->pm_gc = xcb_generate_id(conn);
|
|
|
|
|
} else {
|
|
|
|
|
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);
|
|
|
|
|
xcb_create_gc(conn, con->pm_gc, con->pixmap, 0, 0);
|
2011-04-18 15:10:50 -04:00
|
|
|
|
uint32_t values[] = { con->pixmap };
|
|
|
|
|
xcb_change_window_attributes(conn, con->frame, XCB_CW_BACK_PIXMAP, values);
|
2011-03-20 09:07:16 -04:00
|
|
|
|
con->pixmap_recreated = true;
|
|
|
|
|
}
|
2010-11-27 10:44:45 -05:00
|
|
|
|
memcpy(&(state->rect), &rect, sizeof(Rect));
|
|
|
|
|
fake_notify = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* dito, but for child windows */
|
|
|
|
|
if (con->window != NULL &&
|
|
|
|
|
memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) {
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("setting window rect (%d, %d, %d, %d)\n",
|
2010-11-27 10:44:45 -05:00
|
|
|
|
con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height);
|
|
|
|
|
xcb_set_window_rect(conn, con->window->id, con->window_rect);
|
|
|
|
|
memcpy(&(state->window_rect), &(con->window_rect), sizeof(Rect));
|
|
|
|
|
fake_notify = true;
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-27 11:45:23 -05:00
|
|
|
|
/* Map if map state changed, also ensure that the child window
|
2010-04-17 10:41:20 -04:00
|
|
|
|
* is changed if we are mapped *and* in initial state (meaning the
|
2010-11-27 11:45:23 -05:00
|
|
|
|
* container was empty before, but now got a child). Unmaps are handled in
|
|
|
|
|
* x_push_node_unmaps(). */
|
|
|
|
|
if ((state->mapped != con->mapped || (con->mapped && state->initial)) &&
|
|
|
|
|
con->mapped) {
|
|
|
|
|
xcb_void_cookie_t cookie;
|
|
|
|
|
|
|
|
|
|
if (con->window != NULL) {
|
|
|
|
|
/* Set WM_STATE_NORMAL because GTK applications don’t want to
|
|
|
|
|
* drag & drop if we don’t. Also, xprop(1) needs it. */
|
2011-03-18 09:36:36 -04:00
|
|
|
|
long data[] = { XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE };
|
2010-11-27 11:45:23 -05:00
|
|
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id,
|
2011-03-18 09:36:36 -04:00
|
|
|
|
A_WM_STATE, A_WM_STATE, 32, 2, data);
|
2010-11-27 11:45:23 -05:00
|
|
|
|
}
|
2010-11-14 07:53:47 -05:00
|
|
|
|
|
2010-11-28 16:08:34 -05:00
|
|
|
|
if (!state->child_mapped && con->window != NULL) {
|
2010-11-27 11:45:23 -05:00
|
|
|
|
cookie = xcb_map_window(conn, con->window->id);
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("mapping child window (serial %d)\n", cookie.sequence);
|
2010-07-02 14:33:26 -04:00
|
|
|
|
/* Ignore enter_notifies which are generated when mapping */
|
Bugfix: Ignore EnterNotifies generated by UnmapNotifies
Actually, commit 1c5adc6c35cffaedc08c7d1dd1b03a3269d1367c commented out code
without ever fixing it. I think this was responsible for the 'workspace
switching sometimes does not work' bug. My observations:
Had it again today and analyzed a log of it. Looks like after unmapping the
windows on one workspace (in my case: chromium, eclipse, urxvt, focus on
eclipse) we get UnmapNotify events for chromium and eclipse, but then we get an
EnterNotify for the terminal (due to unmapping the other windows and therefore
mapping the terminal under the cursor), only afterwards the UnmapNotify
follows.
So, there are two things wrong with that:
• We handle EnterNotifys for unmapped windows
• Unmapping windows sometimes works in a sequence, sometimes the sequence gets
split. Not sure why (if unmapping can take longer for some windows or if our
syncing is wrong -- but i checked the latter briefly and it looks correct).
Maybe GrabServer helps?
• We don’t ignore EnterNotify events caused by UnmapNotifies. We used to, but
then there was a different problem and we decided to solve the EnterNotify
problem in another way, which actually never happened (commit
1c5adc6c35cffaedc08c7d1dd1b03a3269d1367c).
2011-04-19 15:50:56 -04:00
|
|
|
|
add_ignore_event(cookie.sequence, 0);
|
2010-11-28 16:08:34 -05:00
|
|
|
|
state->child_mapped = true;
|
2010-03-27 10:25:51 -04:00
|
|
|
|
}
|
2010-11-27 11:45:23 -05:00
|
|
|
|
|
|
|
|
|
cookie = xcb_map_window(conn, con->frame);
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("mapping container (serial %d)\n", cookie.sequence);
|
2010-11-27 11:45:23 -05:00
|
|
|
|
/* Ignore enter_notifies which are generated when mapping */
|
Bugfix: Ignore EnterNotifies generated by UnmapNotifies
Actually, commit 1c5adc6c35cffaedc08c7d1dd1b03a3269d1367c commented out code
without ever fixing it. I think this was responsible for the 'workspace
switching sometimes does not work' bug. My observations:
Had it again today and analyzed a log of it. Looks like after unmapping the
windows on one workspace (in my case: chromium, eclipse, urxvt, focus on
eclipse) we get UnmapNotify events for chromium and eclipse, but then we get an
EnterNotify for the terminal (due to unmapping the other windows and therefore
mapping the terminal under the cursor), only afterwards the UnmapNotify
follows.
So, there are two things wrong with that:
• We handle EnterNotifys for unmapped windows
• Unmapping windows sometimes works in a sequence, sometimes the sequence gets
split. Not sure why (if unmapping can take longer for some windows or if our
syncing is wrong -- but i checked the latter briefly and it looks correct).
Maybe GrabServer helps?
• We don’t ignore EnterNotify events caused by UnmapNotifies. We used to, but
then there was a different problem and we decided to solve the EnterNotify
problem in another way, which actually never happened (commit
1c5adc6c35cffaedc08c7d1dd1b03a3269d1367c).
2011-04-19 15:50:56 -04:00
|
|
|
|
add_ignore_event(cookie.sequence, 0);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
state->mapped = con->mapped;
|
|
|
|
|
}
|
|
|
|
|
|
2010-05-31 17:00:36 -04:00
|
|
|
|
if (fake_notify) {
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("Sending fake configure notify\n");
|
2010-05-31 17:00:36 -04:00
|
|
|
|
fake_absolute_configure_notify(con);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
}
|
|
|
|
|
|
2011-04-01 14:40:32 -04:00
|
|
|
|
/* Handle all children and floating windows of this node. We recurse
|
|
|
|
|
* in focus order to display the focused client in a stack first when
|
|
|
|
|
* switching workspaces (reduces flickering). */
|
|
|
|
|
TAILQ_FOREACH(current, &(con->focus_head), focused)
|
2011-05-01 12:48:30 -04:00
|
|
|
|
x_push_node(current);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Recursively calls x_draw_decoration. This cannot be done in x_push_node
|
|
|
|
|
* because x_push_node uses focus order to recurse (see the comment above)
|
|
|
|
|
* while drawing the decoration needs to happen in the actual order.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
static void x_deco_recurse(Con *con) {
|
|
|
|
|
Con *current;
|
|
|
|
|
|
|
|
|
|
TAILQ_FOREACH(current, &(con->nodes_head), nodes)
|
|
|
|
|
x_deco_recurse(current);
|
|
|
|
|
|
|
|
|
|
TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
|
|
|
|
|
x_deco_recurse(current);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
|
2011-05-01 12:48:30 -04:00
|
|
|
|
if ((con->type != CT_ROOT && con->type != CT_OUTPUT) &&
|
2011-04-01 16:40:12 -04:00
|
|
|
|
con->mapped)
|
2010-03-27 10:25:51 -04:00
|
|
|
|
x_draw_decoration(con);
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-27 11:45:23 -05:00
|
|
|
|
/*
|
|
|
|
|
* Same idea as in x_push_node(), but this function only unmaps windows. It is
|
|
|
|
|
* necessary to split this up to handle new fullscreen clients properly: The
|
|
|
|
|
* new window needs to be mapped and focus needs to be set *before* the
|
|
|
|
|
* underlying windows are unmapped. Otherwise, focus will revert to the
|
|
|
|
|
* PointerRoot and will then be set to the new window, generating unnecessary
|
|
|
|
|
* FocusIn/FocusOut events.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
static void x_push_node_unmaps(Con *con) {
|
|
|
|
|
Con *current;
|
|
|
|
|
con_state *state;
|
|
|
|
|
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name);
|
2010-11-27 11:45:23 -05:00
|
|
|
|
state = state_for_frame(con->frame);
|
|
|
|
|
|
|
|
|
|
/* map/unmap if map state changed, also ensure that the child window
|
|
|
|
|
* is changed if we are mapped *and* in initial state (meaning the
|
|
|
|
|
* container was empty before, but now got a child) */
|
|
|
|
|
if ((state->mapped != con->mapped || (con->mapped && state->initial)) &&
|
|
|
|
|
!con->mapped) {
|
|
|
|
|
xcb_void_cookie_t cookie;
|
|
|
|
|
if (con->window != NULL) {
|
|
|
|
|
/* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */
|
2011-03-18 09:36:36 -04:00
|
|
|
|
long data[] = { XCB_ICCCM_WM_STATE_WITHDRAWN, XCB_NONE };
|
2010-11-27 11:45:23 -05:00
|
|
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id,
|
2011-03-18 09:36:36 -04:00
|
|
|
|
A_WM_STATE, A_WM_STATE, 32, 2, data);
|
2010-11-27 11:45:23 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cookie = xcb_unmap_window(conn, con->frame);
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("unmapping container (serial %d)\n", cookie.sequence);
|
2010-11-27 11:45:23 -05:00
|
|
|
|
/* we need to increase ignore_unmap for this container (if it
|
|
|
|
|
* contains a window) and for every window "under" this one which
|
|
|
|
|
* contains a window */
|
|
|
|
|
if (con->window != NULL) {
|
|
|
|
|
con->ignore_unmap++;
|
|
|
|
|
DLOG("ignore_unmap for con %p (frame 0x%08x) now %d\n", con, con->frame, con->ignore_unmap);
|
|
|
|
|
}
|
|
|
|
|
/* Ignore enter_notifies which are generated when unmapping */
|
Bugfix: Ignore EnterNotifies generated by UnmapNotifies
Actually, commit 1c5adc6c35cffaedc08c7d1dd1b03a3269d1367c commented out code
without ever fixing it. I think this was responsible for the 'workspace
switching sometimes does not work' bug. My observations:
Had it again today and analyzed a log of it. Looks like after unmapping the
windows on one workspace (in my case: chromium, eclipse, urxvt, focus on
eclipse) we get UnmapNotify events for chromium and eclipse, but then we get an
EnterNotify for the terminal (due to unmapping the other windows and therefore
mapping the terminal under the cursor), only afterwards the UnmapNotify
follows.
So, there are two things wrong with that:
• We handle EnterNotifys for unmapped windows
• Unmapping windows sometimes works in a sequence, sometimes the sequence gets
split. Not sure why (if unmapping can take longer for some windows or if our
syncing is wrong -- but i checked the latter briefly and it looks correct).
Maybe GrabServer helps?
• We don’t ignore EnterNotify events caused by UnmapNotifies. We used to, but
then there was a different problem and we decided to solve the EnterNotify
problem in another way, which actually never happened (commit
1c5adc6c35cffaedc08c7d1dd1b03a3269d1367c).
2011-04-19 15:50:56 -04:00
|
|
|
|
add_ignore_event(cookie.sequence, 0);
|
2010-11-27 11:45:23 -05:00
|
|
|
|
state->mapped = con->mapped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* handle all children and floating windows of this node */
|
|
|
|
|
TAILQ_FOREACH(current, &(con->nodes_head), nodes)
|
|
|
|
|
x_push_node_unmaps(current);
|
|
|
|
|
|
|
|
|
|
TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
|
|
|
|
|
x_push_node_unmaps(current);
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-27 10:25:51 -04:00
|
|
|
|
/*
|
|
|
|
|
* Pushes all changes (state of each node, see x_push_node() and the window
|
|
|
|
|
* stack) to X11.
|
|
|
|
|
*
|
2011-01-21 15:49:56 -05:00
|
|
|
|
* NOTE: We need to push the stack first so that the windows have the correct
|
|
|
|
|
* stacking order. This is relevant for workspace switching where we map the
|
|
|
|
|
* windows because mapping may generate EnterNotify events. When they are
|
|
|
|
|
* generated in the wrong order, this will cause focus problems when switching
|
|
|
|
|
* workspaces.
|
|
|
|
|
*
|
2010-03-27 10:25:51 -04:00
|
|
|
|
*/
|
|
|
|
|
void x_push_changes(Con *con) {
|
|
|
|
|
con_state *state;
|
|
|
|
|
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("-- PUSHING WINDOW STACK --\n");
|
2011-03-06 15:48:49 -05:00
|
|
|
|
DLOG("Disabling EnterNotify\n");
|
|
|
|
|
uint32_t values[1] = { XCB_NONE };
|
|
|
|
|
CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {
|
|
|
|
|
xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
|
|
|
|
|
}
|
|
|
|
|
DLOG("Done, EnterNotify disabled\n");
|
2010-07-11 17:41:02 -04:00
|
|
|
|
bool order_changed = false;
|
2010-03-27 10:25:51 -04:00
|
|
|
|
/* X11 correctly represents the stack if we push it from bottom to top */
|
|
|
|
|
CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("stack: 0x%08x\n", state->id);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
con_state *prev = CIRCLEQ_PREV(state, state);
|
|
|
|
|
con_state *old_prev = CIRCLEQ_PREV(state, old_state);
|
2010-07-11 17:41:02 -04:00
|
|
|
|
if (prev != old_prev)
|
|
|
|
|
order_changed = true;
|
|
|
|
|
if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) {
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
uint32_t mask = 0;
|
|
|
|
|
mask |= XCB_CONFIG_WINDOW_SIBLING;
|
|
|
|
|
mask |= XCB_CONFIG_WINDOW_STACK_MODE;
|
|
|
|
|
uint32_t values[] = {state->id, XCB_STACK_MODE_ABOVE};
|
|
|
|
|
|
|
|
|
|
xcb_configure_window(conn, prev->id, mask, values);
|
|
|
|
|
}
|
2010-11-26 14:15:08 -05:00
|
|
|
|
state->initial = false;
|
2010-03-27 10:25:51 -04:00
|
|
|
|
}
|
2011-03-06 15:48:49 -05:00
|
|
|
|
DLOG("Re-enabling EnterNotify\n");
|
|
|
|
|
values[0] = FRAME_EVENT_MASK;
|
|
|
|
|
CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {
|
|
|
|
|
xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
|
|
|
|
|
}
|
|
|
|
|
DLOG("Done, EnterNotify re-enabled\n");
|
|
|
|
|
|
2011-01-21 15:49:56 -05:00
|
|
|
|
DLOG("\n\n PUSHING CHANGES\n\n");
|
2011-05-01 12:48:30 -04:00
|
|
|
|
x_push_node(con);
|
|
|
|
|
x_deco_recurse(con);
|
2011-01-21 15:49:56 -05:00
|
|
|
|
|
2010-03-27 10:25:51 -04:00
|
|
|
|
xcb_window_t to_focus = focused->frame;
|
|
|
|
|
if (focused->window != NULL)
|
|
|
|
|
to_focus = focused->window->id;
|
|
|
|
|
|
2010-12-11 11:07:20 -05:00
|
|
|
|
DLOG("focused_id = 0x%08x, to_focus = 0x%08x\n", focused_id, to_focus);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
if (focused_id != to_focus) {
|
2010-12-11 11:07:20 -05:00
|
|
|
|
if (!focused->mapped) {
|
|
|
|
|
DLOG("Not updating focus (to %p / %s), focused window is not mapped.\n", focused, focused->name);
|
2011-03-19 17:54:53 -04:00
|
|
|
|
/* Invalidate focused_id to correctly focus new windows with the same ID */
|
|
|
|
|
focused_id = XCB_NONE;
|
2010-12-11 11:07:20 -05:00
|
|
|
|
} else {
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name);
|
2011-03-20 11:26:36 -04:00
|
|
|
|
/* 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);
|
|
|
|
|
}
|
2010-12-11 11:07:20 -05:00
|
|
|
|
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME);
|
2011-03-20 11:26:36 -04:00
|
|
|
|
if (focused->window != NULL) {
|
|
|
|
|
values[0] = CHILD_EVENT_MASK;
|
|
|
|
|
xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
|
|
|
|
|
}
|
2011-03-17 17:27:59 -04:00
|
|
|
|
|
2011-03-18 12:07:56 -04:00
|
|
|
|
if (focused->window != NULL &&
|
|
|
|
|
focused->window->needs_take_focus) {
|
|
|
|
|
send_take_focus(to_focus);
|
|
|
|
|
}
|
2011-03-17 17:27:59 -04:00
|
|
|
|
|
2011-03-13 20:09:32 -04:00
|
|
|
|
ewmh_update_active_window(to_focus);
|
2010-12-11 11:07:20 -05:00
|
|
|
|
focused_id = to_focus;
|
|
|
|
|
}
|
2010-03-27 10:25:51 -04:00
|
|
|
|
}
|
|
|
|
|
|
2011-03-19 15:43:06 -04:00
|
|
|
|
if (focused_id == XCB_NONE) {
|
|
|
|
|
DLOG("Still no window focused, better set focus to the root window\n");
|
|
|
|
|
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
|
|
|
|
|
focused_id = root;
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-27 10:25:51 -04:00
|
|
|
|
xcb_flush(conn);
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("\n\n ENDING CHANGES\n\n");
|
2010-03-27 10:25:51 -04:00
|
|
|
|
|
2010-11-27 11:45:23 -05:00
|
|
|
|
x_push_node_unmaps(con);
|
|
|
|
|
|
2010-03-27 10:25:51 -04:00
|
|
|
|
/* save the current stack as old stack */
|
|
|
|
|
CIRCLEQ_FOREACH(state, &state_head, state) {
|
|
|
|
|
CIRCLEQ_REMOVE(&old_state_head, state, old_state);
|
|
|
|
|
CIRCLEQ_INSERT_TAIL(&old_state_head, state, old_state);
|
|
|
|
|
}
|
|
|
|
|
CIRCLEQ_FOREACH(state, &old_state_head, old_state) {
|
2011-01-07 14:58:58 -05:00
|
|
|
|
DLOG("old stack: 0x%08x\n", state->id);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
}
|
2011-03-20 11:26:36 -04:00
|
|
|
|
|
|
|
|
|
xcb_flush(conn);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Raises the specified container in the internal stack of X windows. The
|
|
|
|
|
* next call to x_push_changes() will make the change visible in X11.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void x_raise_con(Con *con) {
|
|
|
|
|
con_state *state;
|
|
|
|
|
state = state_for_frame(con->frame);
|
2011-03-06 15:48:49 -05:00
|
|
|
|
DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id);
|
2010-03-27 10:25:51 -04:00
|
|
|
|
|
|
|
|
|
CIRCLEQ_REMOVE(&state_head, state, state);
|
|
|
|
|
CIRCLEQ_INSERT_HEAD(&state_head, state, state);
|
|
|
|
|
}
|
2010-11-14 10:41:46 -05:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Sets the WM_NAME property (so, no UTF8, but used only for debugging anyways)
|
|
|
|
|
* of the given name. Used for properly tagging the windows for easily spotting
|
|
|
|
|
* i3 windows in xwininfo -root -all.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void x_set_name(Con *con, const char *name) {
|
|
|
|
|
struct con_state *state;
|
|
|
|
|
|
|
|
|
|
if ((state = state_for_frame(con->frame)) == NULL) {
|
|
|
|
|
ELOG("window state not found\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FREE(state->name);
|
|
|
|
|
state->name = sstrdup(name);
|
|
|
|
|
}
|
2011-03-19 17:26:15 -04:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Sets up i3 specific atoms (I3_SOCKET_PATH and I3_CONFIG_PATH)
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void x_set_i3_atoms() {
|
|
|
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SOCKET_PATH, A_UTF8_STRING, 8,
|
2011-03-20 10:34:34 -04:00
|
|
|
|
(current_socketpath == NULL ? 0 : strlen(current_socketpath)),
|
|
|
|
|
current_socketpath);
|
2011-03-19 17:26:15 -04:00
|
|
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8,
|
|
|
|
|
strlen(current_configpath), current_configpath);
|
|
|
|
|
}
|