diff --git a/include/atoms.xmacro b/include/atoms.xmacro index 5d2ffb1b..9b5a132c 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -11,6 +11,7 @@ xmacro(_NET_WM_WINDOW_TYPE_TOOLBAR) xmacro(_NET_WM_WINDOW_TYPE_SPLASH) xmacro(_NET_WM_DESKTOP) xmacro(_NET_WM_STRUT_PARTIAL) +xmacro(_NET_CLIENT_LIST_STACKING) xmacro(_NET_CURRENT_DESKTOP) xmacro(_NET_ACTIVE_WINDOW) xmacro(_NET_WORKAREA) diff --git a/include/ewmh.h b/include/ewmh.h index 2f2bf431..54c83f40 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -39,4 +39,16 @@ void ewmh_update_active_window(xcb_window_t window); */ void ewmh_update_workarea(); +/** + * Updates the _NET_CLIENT_LIST_STACKING hint. Necessary to move tabs in + * Chromium correctly. + * + * EWMH: These arrays contain all X Windows managed by the Window Manager. + * _NET_CLIENT_LIST has initial mapping order, starting with the oldest window. + * _NET_CLIENT_LIST_STACKING has bottom-to-top stacking order. These properties + * SHOULD be set and updated by the Window Manager. + * + */ +void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows); + #endif diff --git a/src/ewmh.c b/src/ewmh.c index 7b2cc3e4..1736523d 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -110,3 +110,20 @@ void ewmh_update_workarea() { free(workarea); xcb_flush(conn); } + +/* + * Updates the _NET_CLIENT_LIST_STACKING hint. + * + */ +void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows) { + DLOG("Updating _NET_CLIENT_LIST_STACKING\n"); + xcb_change_property( + conn, + XCB_PROP_MODE_REPLACE, + root, + A__NET_CLIENT_LIST_STACKING, + A_WINDOW, + 32, + num_windows, + stack); +} diff --git a/src/main.c b/src/main.c index d7a5aedb..53067744 100644 --- a/src/main.c +++ b/src/main.c @@ -397,7 +397,7 @@ int main(int argc, char *argv[]) { #include "atoms.xmacro" #undef xmacro }; - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, A_ATOM, 32, 15, supported_atoms); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, A_ATOM, 32, 16, supported_atoms); /* Set up the window manager’s name */ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTING_WM_CHECK, A_WINDOW, 32, 1, &root); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); diff --git a/src/util.c b/src/util.c index 1ad43d3f..fc0a5ceb 100644 --- a/src/util.c +++ b/src/util.c @@ -77,7 +77,8 @@ void *scalloc(size_t size) { void *srealloc(void *ptr, size_t size) { void *result = realloc(ptr, size); - exit_if_null(result, "Error: out memory (realloc(%zd))\n", size); + if (result == NULL && size > 0) + die("Error: out memory (realloc(%zd))\n", size); return result; } diff --git a/src/x.c b/src/x.c index e93e90e7..f231d7fb 100644 --- a/src/x.c +++ b/src/x.c @@ -7,6 +7,11 @@ /* Stores the X11 window ID of the currently focused window */ xcb_window_t focused_id = XCB_NONE; +/* The bottom-to-top window stack of all windows which are managed by i3. + * Used for x_get_window_stack(). */ +static xcb_window_t *btt_stack; +static int btt_stack_num; + /* * Describes the X11 state we may modify (map state, position, window stack). * There is one entry per container. The state represents the current situation @@ -21,6 +26,9 @@ typedef struct con_state { bool unmap_now; bool child_mapped; + /** The con for which this state is. */ + Con *con; + /* 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). */ @@ -112,6 +120,7 @@ void x_reinit(Con *con) { DLOG("resetting state %p to initial\n", state); state->initial = true; state->child_mapped = false; + state->con = con; memset(&(state->window_rect), 0, sizeof(Rect)); } @@ -148,6 +157,9 @@ void x_move_win(Con *src, Con *dest) { return; } + state_dest->con = state_src->con; + state_src->con = NULL; + Rect zero = { 0, 0, 0, 0 }; if (memcmp(&(state_dest->window_rect), &(zero), sizeof(Rect)) == 0) { memcpy(&(state_dest->window_rect), &(state_src->window_rect), sizeof(Rect)); @@ -753,14 +765,31 @@ void x_push_changes(Con *con) { } //DLOG("Done, EnterNotify disabled\n"); bool order_changed = false; + + /* count first, necessary to (re)allocate memory for the bottom-to-top + * stack afterwards */ + int cnt = 0; + CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) + if (state->con && state->con->window) + cnt++; + + if (cnt != btt_stack_num) { + btt_stack = srealloc(btt_stack, sizeof(xcb_window_t) * cnt); + btt_stack_num = cnt; + } + + xcb_window_t *walk = btt_stack; + /* X11 correctly represents the stack if we push it from bottom to top */ CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { + if (state->con && state->con->window) + memcpy(walk++, &(state->con->window->id), sizeof(xcb_window_t)); + //DLOG("stack: 0x%08x\n", state->id); con_state *prev = CIRCLEQ_PREV(state, state); con_state *old_prev = CIRCLEQ_PREV(state, old_state); - if (prev != old_prev) + if ((prev != old_prev || state->initial) && prev != CIRCLEQ_END(&state_head)) { order_changed = true; - if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) { DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id); uint32_t mask = 0; mask |= XCB_CONFIG_WINDOW_SIBLING; @@ -771,6 +800,12 @@ void x_push_changes(Con *con) { } state->initial = false; } + + /* If we re-stacked something (or a new window appeared), we need to update + * the _NET_CLIENT_LIST_STACKING hint */ + if (order_changed) + ewmh_update_client_list_stacking(btt_stack, btt_stack_num); + //DLOG("Re-enabling EnterNotify\n"); values[0] = FRAME_EVENT_MASK; CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {