From 3b7f4d428ed2176bc2ceaa8d20497b2aef8b2e89 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 21 Jan 2012 18:35:15 +0000 Subject: [PATCH] Correctly restore focus after in-place restarts Note: This change requires two in-place restarts when you are upgrading in-place from an old version. Fixes #611 --- include/data.h | 9 ++++ src/ipc.c | 4 +- src/load_layout.c | 68 ++++++++++++++++++++++--- src/manage.c | 8 +-- testcases/t/188-regress-focus-restart.t | 34 +++++++++++++ 5 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 testcases/t/188-regress-focus-restart.t diff --git a/include/data.h b/include/data.h index b498e02a..fe648d3d 100644 --- a/include/data.h +++ b/include/data.h @@ -357,6 +357,11 @@ struct Match { */ enum { M_HERE = 0, M_ASSIGN_WS, M_BELOW } insert_where; + /* Whether this match was generated when restarting i3 inplace. + * Leads to not setting focus when managing a new window, because the old + * focus stack should be restored. */ + bool restart_mode; + TAILQ_ENTRY(Match) matches; }; @@ -514,6 +519,10 @@ struct Con { SCRATCHPAD_FRESH = 1, SCRATCHPAD_CHANGED = 2 } scratchpad_state; + + /* The ID of this container before restarting. Necessary to correctly + * interpret back-references in the JSON (such as the focus stack). */ + int old_id; }; #endif diff --git a/src/ipc.c b/src/ipc.c index 2fd3a224..ee418945 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) * * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol). * @@ -311,6 +311,8 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { y(map_open); ystr("id"); y(integer, con->window->id); + ystr("restart_mode"); + y(bool, true); y(map_close); } } diff --git a/src/load_layout.c b/src/load_layout.c index 08d03bd1..a8063dca 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) * * load_layout.c: Restore (parts of) the layout, for example after an inplace * restart. @@ -24,8 +24,19 @@ static bool parsing_swallows; static bool parsing_rect; static bool parsing_window_rect; static bool parsing_geometry; +static bool parsing_focus; struct Match *current_swallow; +/* This list is used for reordering the focus stack after parsing the 'focus' + * array. */ +struct focus_mapping { + int old_id; + TAILQ_ENTRY(focus_mapping) focus_mappings; +}; + +static TAILQ_HEAD(focus_mappings_head, focus_mapping) focus_mappings = + TAILQ_HEAD_INITIALIZER(focus_mappings); + static int json_start_map(void *ctx) { LOG("start of map, last_key = %s\n", last_key); if (parsing_swallows) { @@ -70,6 +81,29 @@ static int json_end_map(void *ctx) { static int json_end_array(void *ctx) { LOG("end of array\n"); parsing_swallows = false; + if (parsing_focus) { + /* Clear the list of focus mappings */ + struct focus_mapping *mapping; + TAILQ_FOREACH_REVERSE(mapping, &focus_mappings, focus_mappings_head, focus_mappings) { + LOG("focus (reverse) %d\n", mapping->old_id); + Con *con; + TAILQ_FOREACH(con, &(json_node->focus_head), focused) { + if (con->old_id != mapping->old_id) + continue; + LOG("got it! %p\n", con); + /* Move this entry to the top of the focus list. */ + TAILQ_REMOVE(&(json_node->focus_head), con, focused); + TAILQ_INSERT_HEAD(&(json_node->focus_head), con, focused); + break; + } + } + while (!TAILQ_EMPTY(&focus_mappings)) { + mapping = TAILQ_FIRST(&focus_mappings); + TAILQ_REMOVE(&focus_mappings, mapping, focus_mappings); + free(mapping); + } + parsing_focus = false; + } return 1; } @@ -82,15 +116,21 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) { FREE(last_key); last_key = scalloc((len+1) * sizeof(char)); memcpy(last_key, val, len); - if (strcasecmp(last_key, "swallows") == 0) { + if (strcasecmp(last_key, "swallows") == 0) parsing_swallows = true; - } + if (strcasecmp(last_key, "rect") == 0) parsing_rect = true; + if (strcasecmp(last_key, "window_rect") == 0) parsing_window_rect = true; + if (strcasecmp(last_key, "geometry") == 0) parsing_geometry = true; + + if (strcasecmp(last_key, "focus") == 0) + parsing_focus = true; + return 1; } @@ -190,15 +230,24 @@ static int json_int(void *ctx, long long val) { static int json_int(void *ctx, long val) { LOG("int %ld for key %s\n", val, last_key); #endif - if (strcasecmp(last_key, "type") == 0) { + if (strcasecmp(last_key, "type") == 0) json_node->type = val; - } - if (strcasecmp(last_key, "fullscreen_mode") == 0) { + + if (strcasecmp(last_key, "fullscreen_mode") == 0) json_node->fullscreen_mode = val; - } + if (strcasecmp(last_key, "num") == 0) json_node->num = val; + if (!parsing_swallows && strcasecmp(last_key, "id") == 0) + json_node->old_id = val; + + if (parsing_focus) { + struct focus_mapping *focus_mapping = scalloc(sizeof(struct focus_mapping)); + focus_mapping->old_id = val; + TAILQ_INSERT_TAIL(&focus_mappings, focus_mapping, focus_mappings); + } + if (parsing_rect || parsing_window_rect || parsing_geometry) { Rect *r; if (parsing_rect) @@ -239,6 +288,11 @@ static int json_bool(void *ctx, int val) { to_focus = json_node; } + if (parsing_swallows) { + if (strcasecmp(last_key, "restart_mode") == 0) + current_swallow->restart_mode = val; + } + return 1; } diff --git a/src/manage.c b/src/manage.c index a87807b4..653de156 100644 --- a/src/manage.c +++ b/src/manage.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) * * manage.c: Initially managing new windows (or existing ones on restart). * @@ -216,7 +216,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki DLOG("Initial geometry: (%d, %d, %d, %d)\n", geom->x, geom->y, geom->width, geom->height); Con *nc = NULL; - Match *match; + Match *match = NULL; Assignment *assignment; /* TODO: two matches for one container */ @@ -286,7 +286,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki Con *target_output = con_get_output(ws); if (workspace_is_visible(ws) && current_output == target_output) { - con_focus(nc); + if (!match || !match->restart_mode) { + con_focus(nc); + } else DLOG("not focusing, matched with restart_mode == true\n"); } else DLOG("workspace not visible, not focusing\n"); } else DLOG("dock, not focusing\n"); } else { diff --git a/testcases/t/188-regress-focus-restart.t b/testcases/t/188-regress-focus-restart.t new file mode 100644 index 00000000..ba3bb072 --- /dev/null +++ b/testcases/t/188-regress-focus-restart.t @@ -0,0 +1,34 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Verifies that i3 survives inplace restarts with fullscreen containers +# +use i3test; + +my $tmp = fresh_workspace; + +open_window(name => 'first'); +open_window(name => 'second'); + +cmd 'focus left'; + +my ($nodes, $focus) = get_ws_content($tmp); +is(scalar @$nodes, 2, 'two tiling nodes on workspace'); +is($nodes->[0]->{name}, 'first', 'first node name ok'); +is($nodes->[1]->{name}, 'second', 'second node name ok'); +is($focus->[0], $nodes->[0]->{id}, 'first node focused'); +is($focus->[1], $nodes->[1]->{id}, 'second node second in focus stack'); + +cmd 'restart'; +sleep 1; + +does_i3_live; + +($nodes, $focus) = get_ws_content($tmp); +is(scalar @$nodes, 2, 'still two tiling nodes on workspace'); +is($nodes->[0]->{name}, 'first', 'first node name ok'); +is($nodes->[1]->{name}, 'second', 'second node name ok'); +is($focus->[0], $nodes->[0]->{id}, 'first node focused'); +is($focus->[1], $nodes->[1]->{id}, 'second node second in focus stack'); + +done_testing;