From 3a672bc930237e1b10c81a4c3a8d51eb3c4ae3f5 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 11 Apr 2020 22:57:29 +0200 Subject: [PATCH 1/5] i3-dmenu-desktop: Support symlinks follow_fast is passed to find() in order to support this. Since we check ourselves for duplicates, the fast option can be used. Fixes #3973 --- i3-dmenu-desktop | 1 + 1 file changed, 1 insertion(+) diff --git a/i3-dmenu-desktop b/i3-dmenu-desktop index d57e9a38c..5c34b2bb6 100755 --- a/i3-dmenu-desktop +++ b/i3-dmenu-desktop @@ -143,6 +143,7 @@ find( $desktops{$relative} = $File::Find::name; }, no_chdir => 1, + follow_fast => 1, }, @searchdirs ); From a87e8c8d5bc4d3dd30ed6424877b1b5561f6d6ae Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 12 Apr 2020 00:48:01 +0200 Subject: [PATCH 2/5] Fix load_layout crash when floating node doesn't have CT_FLOATING_CON parent Fixes #3901 --- src/load_layout.c | 13 ++++++++ testcases/t/215-layout-restore-crash.t | 46 +++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/load_layout.c b/src/load_layout.c index 5d0d16eb6..b69b388c8 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -170,6 +170,19 @@ static int json_end_map(void *ctx) { con_attach(json_node, json_node->parent, true); LOG("Creating window\n"); x_con_init(json_node); + + /* Fix erroneous JSON input regarding floating containers to avoid + * crashing, see #3901. */ + const int old_floating_mode = json_node->floating; + if (old_floating_mode >= FLOATING_AUTO_ON && json_node->parent->type != CT_FLOATING_CON) { + LOG("Fixing floating node without CT_FLOATING_CON parent\n"); + + /* Force floating_enable to work */ + json_node->floating = FLOATING_AUTO_OFF; + floating_enable(json_node, false); + json_node->floating = old_floating_mode; + } + json_node = json_node->parent; incomplete--; DLOG("incomplete = %d\n", incomplete); diff --git a/testcases/t/215-layout-restore-crash.t b/testcases/t/215-layout-restore-crash.t index 0306425a8..d9fcd54a7 100644 --- a/testcases/t/215-layout-restore-crash.t +++ b/testcases/t/215-layout-restore-crash.t @@ -212,7 +212,7 @@ subtest 'issue 2755' => sub { EOT $fh->flush; $reply = cmd "append_layout $filename"; - ok(!$reply->[0]->{success}, 'IPC reply indicated success'); + ok(!$reply->[0]->{success}, 'IPC reply did not indicate success'); does_i3_live; @@ -276,5 +276,49 @@ does_i3_live; close($fh); +################################################################################ +# Issue with floating key being set, without proper parent +# See #3901 +################################################################################ +subtest 'issue 3901' => sub { + kill_all_windows; + $ws = fresh_workspace; + is(scalar @{get_ws($ws)->{floating_nodes}}, 0, 'No floating nodes yet'); + + ($fh, $filename) = tempfile(UNLINK => 1); + print $fh <<'EOT'; +// vim:ts=4:sw=4:et +{ + "border": "pixel", + "current_border_width": 1, + "floating": "auto_on", // crashes: user_on, auto_on, no crash: user_off, auto_off + "geometry": { + "height": 400, + "width": 300, + "x": 820, + "y": 350 + }, + "name": "Click me to crash", + "percent": 0.5, // still crashes if this field is absent + "swallows": [ + { + "class": "^this doesn't matter as long as it doesn't match a new window$" + } + ], + "type": "con" +} + +EOT + $fh->flush; + $reply = cmd "append_layout $filename"; + ok($reply->[0]->{success}, 'IPC reply indicated success'); + + cmd '[floating] focus'; + is(scalar @{get_ws($ws)->{floating_nodes}}, 1, 'one floating node on this ws'); + + does_i3_live; + + close($fh); +}; done_testing; From e7191af8b398e42264551d6caf5e1f68ed436c81 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 12 Apr 2020 11:07:43 +0200 Subject: [PATCH 3/5] pod2html: render without stylesheet by default (#4016) fixes #3956 --- docs/i3-pod2html | 16 ++++++++++++---- travis/docs.sh | 8 ++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/i3-pod2html b/docs/i3-pod2html index bd797fcbb..e2de51b36 100755 --- a/docs/i3-pod2html +++ b/docs/i3-pod2html @@ -4,8 +4,14 @@ use strict; use warnings; use Pod::Simple::HTML; +use Getopt::Long; use v5.10; +my $stylesurl = ''; + +GetOptions("stylesurl=s" => \$stylesurl) + or die "parsing flags"; + $Pod::Simple::HTML::Tagmap{'Verbatim'} = '
';
 $Pod::Simple::HTML::Tagmap{'VerbatimFormatted'} = '
';
 $Pod::Simple::HTML::Tagmap{'/Verbatim'} = '
'; @@ -22,8 +28,9 @@ open(my $out, '>', $ARGV[1]) or die "Couldn’t open $ARGV[1] for writing: $!\n" my $parser = Pod::Simple::HTML->new(); $parser->index(1); -$parser->html_header_before_title( -<<'EOF' +if ($stylesurl ne '') { + $parser->html_header_before_title( +< @@ -31,7 +38,7 @@ $parser->html_header_before_title( - + EOF -); + ); +} $parser->html_header_after_title( <<'EOF' diff --git a/travis/docs.sh b/travis/docs.sh index b11c097a1..043104a90 100755 --- a/travis/docs.sh +++ b/travis/docs.sh @@ -7,10 +7,10 @@ for f in $(grep '\.html$' debian/i3-wm.docs | grep -v 'docs/refcard.html' | grep do asciidoc -a linkcss -a stylesdir=https://i3wm.org/css -a scriptsdir=https://i3wm.org/js --backend=xhtml11 -f docs/asciidoc-git.conf $(dirname $f)/$(basename $f .html) done -./docs/i3-pod2html i3-dmenu-desktop man/i3-dmenu-desktop.html -./docs/i3-pod2html i3-save-tree man/i3-save-tree.html -./docs/i3-pod2html build/testcases/lib/i3test.pm docs/lib-i3test.html -./docs/i3-pod2html testcases/lib/i3test/Test.pm docs/lib-i3test-test.html +./docs/i3-pod2html --stylesurl=https://i3wm.org/css i3-dmenu-desktop man/i3-dmenu-desktop.html +./docs/i3-pod2html --stylesurl=https://i3wm.org/css i3-save-tree man/i3-save-tree.html +./docs/i3-pod2html --stylesurl=https://i3wm.org/css build/testcases/lib/i3test.pm docs/lib-i3test.html +./docs/i3-pod2html --stylesurl=https://i3wm.org/css testcases/lib/i3test/Test.pm docs/lib-i3test-test.html for file in $(sed 's/\.1$/.man/g' debian/i3-wm.manpages) do [ -f "$file" ] && asciidoc -a linkcss -a stylesdir=https://i3wm.org/css -a scriptsdir=https://i3wm.org/js --backend=xhtml11 -f docs/asciidoc-git.conf "$file" From ae757c6848b49d7a0423973a39791ba6eca29308 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 12 Apr 2020 13:49:08 +0200 Subject: [PATCH 4/5] Extend tiling/floating criteria with optional auto/user values (#4006) The default `tiling` and `floating` behavior is preserved and matches both cases. Adds a new handler to `remanage_window` on A_I3_FLOATING_WINDOW change. Mainly in order to `run_assignments`, this makes `for_window [floating]` directives to work for windows which where initially opened as tiling. Now, when floating is enabled, `for_window` will trigger correctly. Same applies to `for_window [tiling]`. The obvious solution of `run_assignments` after `floating_{enable,disable}` doesn't work because `run_assignments` modifies the parser state in src/assignments.c:51. Fixes #3588 Co-Authored-By: Michael Stapelberg --- docs/userguide | 10 +++ generate-command-parser.pl | 2 + include/data.h | 4 ++ parser-specs/config.spec | 38 ++++++++--- src/handlers.c | 24 ++++++- src/manage.c | 1 + src/match.c | 70 ++++++++++++++++++--- testcases/t/201-config-parser.t | 35 +++++++++++ testcases/t/271-for_window_tilingfloating.t | 58 ++++++++++++++--- 9 files changed, 216 insertions(+), 26 deletions(-) diff --git a/docs/userguide b/docs/userguide index 4cda35f8d..a886d73be 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1900,8 +1900,18 @@ con_id:: to match only the currently focused window. floating:: Only matches floating windows. This criterion requires no value. +floating_from:: + Like +floating+ but this criterion takes two possible values: "auto" + and "user". With "auto", only windows that were automatically opened as + floating are matched. With "user", only windows that the user made + floating are matched. tiling:: Only matches tiling windows. This criterion requires no value. +tiling_from:: + Like +tiling+ but this criterion takes two possible values: "auto" and + "user". With "auto", only windows that were automatically opened as + tiling are matched. With "user", only windows that the user made tiling + are matched. The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for diff --git a/generate-command-parser.pl b/generate-command-parser.pl index 052e4c663..a017edab2 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -218,6 +218,8 @@ sub slurp { # quote of the literal. We can do strdup(literal + 1); then :). $token_name =~ s/'$//; } + # Escape double quotes: + $token_name =~ s,",\\",g; my $next_state = $token->{next_state}; if ($next_state =~ /^call /) { ($call_identifier) = ($next_state =~ /^call ([0-9]+)$/); diff --git a/include/data.h b/include/data.h index c0e34b413..e686d2c72 100644 --- a/include/data.h +++ b/include/data.h @@ -530,7 +530,11 @@ struct Match { } dock; xcb_window_t id; enum { WM_ANY = 0, + WM_TILING_AUTO, + WM_TILING_USER, WM_TILING, + WM_FLOATING_AUTO, + WM_FLOATING_USER, WM_FLOATING } window_mode; Con *con_id; diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 93901fd99..bb9e226e0 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -181,16 +181,18 @@ state NO_FOCUS_END: # Criteria: Used by for_window and assign. state CRITERIA: - ctype = 'class' -> CRITERION - ctype = 'instance' -> CRITERION - ctype = 'window_role' -> CRITERION - ctype = 'con_id' -> CRITERION - ctype = 'id' -> CRITERION - ctype = 'window_type' -> CRITERION - ctype = 'con_mark' -> CRITERION - ctype = 'title' -> CRITERION - ctype = 'urgent' -> CRITERION - ctype = 'workspace' -> CRITERION + ctype = 'class' -> CRITERION + ctype = 'instance' -> CRITERION + ctype = 'window_role' -> CRITERION + ctype = 'con_id' -> CRITERION + ctype = 'id' -> CRITERION + ctype = 'window_type' -> CRITERION + ctype = 'con_mark' -> CRITERION + ctype = 'title' -> CRITERION + ctype = 'urgent' -> CRITERION + ctype = 'workspace' -> CRITERION + ctype = 'floating_from' -> CRITERION_FROM + ctype = 'tiling_from' -> CRITERION_FROM ctype = 'tiling', 'floating' -> call cfg_criteria_add($ctype, NULL); CRITERIA ']' @@ -199,6 +201,22 @@ state CRITERIA: state CRITERION: '=' -> CRITERION_STR +state CRITERION_FROM: + '=' -> CRITERION_FROM_STR_START + +state CRITERION_FROM_STR_START: + '"' -> CRITERION_FROM_STR + kind = 'auto', 'user' + -> call cfg_criteria_add($ctype, $kind); CRITERIA + +state CRITERION_FROM_STR: + kind = 'auto', 'user' + -> CRITERION_FROM_STR_END + +state CRITERION_FROM_STR_END: + '"' + -> call cfg_criteria_add($ctype, $kind); CRITERIA + state CRITERION_STR: cvalue = word -> call cfg_criteria_add($ctype, $cvalue); CRITERIA diff --git a/src/handlers.c b/src/handlers.c index 22a974ac8..ed3f6c3ed 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -842,6 +842,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { DLOG("The window was requested to be visible on all workspaces, making it sticky and floating.\n"); floating_enable(con, false); + con->floating = FLOATING_AUTO_ON; con->sticky = true; ewmh_update_sticky(con->window->id, true); @@ -1156,6 +1157,25 @@ static bool handle_strut_partial_change(Con *con, xcb_get_property_reply_t *prop return true; } +/* + * Handles the _I3_FLOATING_WINDOW property to properly run assignments for + * floating window changes. + * + * This is needed to correctly run the assignments after changes in floating + * windows which are triggered by user commands (floating enable | disable). In + * that case, we can't call run_assignments because it will modify the parser + * state when it needs to parse the user-specified action, breaking the parser + * state for the original command. + * + */ +static bool handle_i3_floating(Con *con, xcb_get_property_reply_t *prop) { + DLOG("floating change for con %p\n", con); + + remanage_window(con); + + return true; +} + /* Returns false if the event could not be processed (e.g. the window could not * be found), true otherwise */ typedef bool (*cb_property_handler_t)(Con *con, xcb_get_property_reply_t *property); @@ -1177,6 +1197,7 @@ static struct property_handler_t property_handlers[] = { {0, 128, handle_class_change}, {0, UINT_MAX, handle_strut_partial_change}, {0, UINT_MAX, handle_window_type}, + {0, UINT_MAX, handle_i3_floating}, {0, 5 * sizeof(uint64_t), handle_motif_hints_change}}; #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t)) @@ -1198,7 +1219,8 @@ void property_handlers_init(void) { property_handlers[7].atom = XCB_ATOM_WM_CLASS; property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL; property_handlers[9].atom = A__NET_WM_WINDOW_TYPE; - property_handlers[10].atom = A__MOTIF_WM_HINTS; + property_handlers[10].atom = A_I3_FLOATING_WINDOW; + property_handlers[11].atom = A__MOTIF_WM_HINTS; } static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { diff --git a/src/manage.c b/src/manage.c index 726c714cc..eecba95c5 100644 --- a/src/manage.c +++ b/src/manage.c @@ -538,6 +538,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki bool automatic_border = (motif_border_style == BS_NORMAL); floating_enable(nc, automatic_border); + nc->floating = FLOATING_AUTO_ON; } /* explicitly set the border width to the default */ diff --git a/src/match.c b/src/match.c index a8850af4e..6ac312e5d 100644 --- a/src/match.c +++ b/src/match.c @@ -215,15 +215,43 @@ bool match_matches_window(Match *match, i3Window *window) { } if (match->window_mode != WM_ANY) { - if ((con = con_by_window_id(window->id)) == NULL) + if ((con = con_by_window_id(window->id)) == NULL) { return false; + } - const bool floating = (con_inside_floating(con) != NULL); - - if ((match->window_mode == WM_TILING && floating) || - (match->window_mode == WM_FLOATING && !floating)) { - LOG("window_mode does not match\n"); - return false; + switch (match->window_mode) { + case WM_TILING_AUTO: + if (con->floating != FLOATING_AUTO_OFF) { + return false; + } + break; + case WM_TILING_USER: + if (con->floating != FLOATING_USER_OFF) { + return false; + } + break; + case WM_TILING: + if (con_inside_floating(con) != NULL) { + return false; + } + break; + case WM_FLOATING_AUTO: + if (con->floating != FLOATING_AUTO_ON) { + return false; + } + break; + case WM_FLOATING_USER: + if (con->floating != FLOATING_USER_ON) { + return false; + } + break; + case WM_FLOATING: + if (con_inside_floating(con) == NULL) { + return false; + } + break; + case WM_ANY: + assert(false); } LOG("window_mode matches\n"); @@ -367,10 +395,38 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { return; } + if (strcmp(ctype, "tiling_from") == 0 && + cvalue != NULL && + strcmp(cvalue, "auto") == 0) { + match->window_mode = WM_TILING_AUTO; + return; + } + + if (strcmp(ctype, "tiling_from") == 0 && + cvalue != NULL && + strcmp(cvalue, "user") == 0) { + match->window_mode = WM_TILING_USER; + return; + } + if (strcmp(ctype, "floating") == 0) { match->window_mode = WM_FLOATING; return; } + if (strcmp(ctype, "floating_from") == 0 && + cvalue != NULL && + strcmp(cvalue, "auto") == 0) { + match->window_mode = WM_FLOATING_AUTO; + return; + } + + if (strcmp(ctype, "floating_from") == 0 && + cvalue != NULL && + strcmp(cvalue, "user") == 0) { + match->window_mode = WM_FLOATING_USER; + return; + } + ELOG("Unknown criterion: %s\n", ctype); } diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index a8d45325b..e2d885bad 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -98,18 +98,53 @@ is(parser_calls($config), ################################################################################ $config = <<'EOT'; +for_window [] nop empty for_window [class="^Chrome"] floating enable +for_window [class=^Chrome] floating enable +for_window [floating_from = "auto" class= ==Class== ] nop floating +for_window [tiling_from=auto class="==Class=="]nop floating EOT $expected = <<'EOT'; +cfg_for_window(nop empty) cfg_criteria_add(class, ^Chrome) cfg_for_window(floating enable) +cfg_criteria_add(class, ^Chrome) +cfg_for_window(floating enable) +cfg_criteria_add(floating_from, auto) +cfg_criteria_add(class, ==Class==) +cfg_for_window(nop floating) +cfg_criteria_add(tiling_from, auto) +cfg_criteria_add(class, ==Class==) +cfg_for_window(nop floating) EOT is(parser_calls($config), $expected, 'for_window okay'); +$config = <<'EOT'; +for_window [tiling_from=typo] nop typo +for_window [tiling_from="typo"] nop typo +EOT + +$expected = <<'EOT'; +ERROR: CONFIG: Expected one of these tokens: '"', 'auto', 'user' +ERROR: CONFIG: (in file ) +ERROR: CONFIG: Line 1: for_window [tiling_from=typo] nop typo +ERROR: CONFIG: ^^^^^^^^^^^^^^ +ERROR: CONFIG: Line 2: for_window [tiling_from="typo"] nop typo +ERROR: CONFIG: Expected one of these tokens: 'auto', 'user' +ERROR: CONFIG: (in file ) +ERROR: CONFIG: Line 1: for_window [tiling_from=typo] nop typo +ERROR: CONFIG: Line 2: for_window [tiling_from="typo"] nop typo +ERROR: CONFIG: ^^^^^^^^^^^^^^^ +EOT + +is(parser_calls($config), + $expected, + 'for_window errors okay'); + ################################################################################ # assign ################################################################################ diff --git a/testcases/t/271-for_window_tilingfloating.t b/testcases/t/271-for_window_tilingfloating.t index caee328df..f3947d6e8 100644 --- a/testcases/t/271-for_window_tilingfloating.t +++ b/testcases/t/271-for_window_tilingfloating.t @@ -17,27 +17,69 @@ use i3test i3_config => <{nodes}}; cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace'); -is_deeply($nodes[0]->{marks}, [ 'tiled' ], "mark set for 'tiling' criterion"); +is_deeply($nodes[0]->{marks}, [ 'tiling', 'tiling_auto' ], "mark set for 'tiling' criterion"); + +@nodes = @{get_ws($tmp)->{floating_nodes}}; +cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); +is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'floating', 'floating_auto' ], "mark set for 'floating' criterion"); + +################################################################################ +# Check that the user tiling / floating criteria work. +# The following rules are triggered here: 'tiling', 'tiling_user', 'floating', +# 'floating_user'. Therefore, the old marks 'tiling' and 'floating' are +# replaced but the 'tiling_auto' and 'floating_auto' marks are preserved. +################################################################################ + +cmd '[id=' . $A->{id} . '] floating enable'; +cmd '[id=' . $B->{id} . '] floating disable'; + +@nodes = @{get_ws($tmp)->{nodes}}; +cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace'); +is_deeply($nodes[0]->{marks}, [ 'floating_auto', 'tiling', 'tiling_user' ], "Marks updated after 'floating_user' criterion"); + +@nodes = @{get_ws($tmp)->{floating_nodes}}; +cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); +is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'tiling_auto', 'floating', 'floating_user' ], "Marks updated after 'tiling_user' criterion"); + +################################################################################ +# Check that the default and auto rules do not re-trigger +# Here, the windows are returned to their original state but since the rules +# `tiling`, `tiling_auto`, `floating` and `floating_auto` where already +# triggered, only the `tiling_user` and `floating_user` rules should trigger. +################################################################################ + +# Use 'mark' to clear old marks +cmd '[id=' . $A->{id} . '] mark A, floating disable'; +cmd '[id=' . $B->{id} . '] mark B, floating enable'; + +@nodes = @{get_ws($tmp)->{nodes}}; +cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace'); +is_deeply($nodes[0]->{marks}, [ 'A', 'tiling_user' ], "Only 'tiling_user' rule triggered"); @nodes = @{get_ws($tmp)->{floating_nodes}}; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); -is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'floated' ], "mark set for 'floating' criterion"); +is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'B', 'floating_user' ], "Only 'floating_user' rule triggered"); ############################################################## From 831a52de9abae1837077ef544e9c3ce52b5d2848 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 12 Apr 2020 13:52:05 +0200 Subject: [PATCH 5/5] Move content for non-existing output containers (#3767) Probably fixes various related issues: Closes #2276 Closes #2459 Closes #2936 Closes #3766 Tested using bindsym $mod+Shift+i exec xrandr --output DVI-D-0 --mode 1920x1080 --pos 0x0 --rotate normal --output DVI-I-1 --off, restart The core issue here is that the JSON read during a restart might contain invalid outputs when the new process is done reading it. --- src/randr.c | 154 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 66 deletions(-) diff --git a/src/randr.c b/src/randr.c index e465d4654..b163eca7d 100644 --- a/src/randr.c +++ b/src/randr.c @@ -826,6 +826,76 @@ static void randr_query_outputs_14(void) { FREE(res); } +/* + * Move the content of an outputs container to the first output. + * + * TODO: Maybe use an on_destroy callback which is implement differently for + * different container types (CT_CONTENT vs. CT_DOCKAREA)? + * + */ +static void move_content(Con *con) { + Con *first = get_first_output()->con; + Con *first_content = output_get_content(first); + + /* We need to move the workspaces from the disappearing output to the first output */ + /* 1: Get the con to focus next */ + Con *next = focused; + + /* 2: iterate through workspaces and re-assign them, fixing the coordinates + * of floating containers as we go */ + Con *current; + Con *old_content = output_get_content(con); + while (!TAILQ_EMPTY(&(old_content->nodes_head))) { + current = TAILQ_FIRST(&(old_content->nodes_head)); + if (current != next && TAILQ_EMPTY(&(current->focus_head))) { + /* the workspace is empty and not focused, get rid of it */ + DLOG("Getting rid of current = %p / %s (empty, unfocused)\n", current, current->name); + tree_close_internal(current, DONT_KILL_WINDOW, false); + continue; + } + DLOG("Detaching current = %p / %s\n", current, current->name); + con_detach(current); + DLOG("Re-attaching current = %p / %s\n", current, current->name); + con_attach(current, first_content, false); + DLOG("Fixing the coordinates of floating containers\n"); + Con *floating_con; + TAILQ_FOREACH (floating_con, &(current->floating_head), floating_windows) { + floating_fix_coordinates(floating_con, &(con->rect), &(first->rect)); + } + } + + /* Restore focus after con_detach / con_attach. next can be NULL, see #3523. */ + if (next) { + DLOG("now focusing next = %p\n", next); + con_focus(next); + workspace_show(con_get_workspace(next)); + } + + /* 3: move the dock clients to the first output */ + Con *child; + TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + if (child->type != CT_DOCKAREA) { + continue; + } + DLOG("Handling dock con %p\n", child); + Con *dock; + while (!TAILQ_EMPTY(&(child->nodes_head))) { + dock = TAILQ_FIRST(&(child->nodes_head)); + Con *nc; + Match *match; + nc = con_for_window(first, dock->window, &match); + DLOG("Moving dock client %p to nc %p\n", dock, nc); + con_detach(dock); + DLOG("Re-attaching\n"); + con_attach(dock, nc, false); + DLOG("Done\n"); + } + } + + DLOG("Destroying disappearing con %p\n", con); + tree_close_internal(con, DONT_KILL_WINDOW, true); +} + /* * (Re-)queries the outputs via RandR and stores them in the list of outputs. * @@ -868,7 +938,7 @@ void randr_query_outputs(void) { other->rect.y != output->rect.y) continue; - DLOG("output %p has the same position, his mode = %d x %d\n", + DLOG("output %p has the same position, its mode = %d x %d\n", other, other->rect.width, other->rect.height); uint32_t width = min(other->rect.width, output->rect.width); uint32_t height = min(other->rect.height, output->rect.height); @@ -901,6 +971,21 @@ void randr_query_outputs(void) { } } + /* Ensure that all containers with type CT_OUTPUT have a valid + * corresponding entry in outputs. This can happen in situations related to + * those mentioned #3767 e.g. when a CT_OUTPUT is created from an in-place + * restart's layout but the output is disabled by a randr query happening + * at the same time. */ + Con *con; + for (con = TAILQ_FIRST(&(croot->nodes_head)); con;) { + Con *next = TAILQ_NEXT(con, nodes); + if (!con_is_internal(con) && get_output_by_name(con->name, true) == NULL) { + DLOG("No output %s found, moving its old content to first output\n", con->name); + move_content(con); + } + con = next; + } + /* Handle outputs which have a new mode or are disabled now (either * because the user disabled them or because they are clones) */ TAILQ_FOREACH (output, &outputs, outputs) { @@ -952,74 +1037,11 @@ void randr_disable_output(Output *output) { output->active = false; DLOG("Output %s disabled, re-assigning workspaces/docks\n", output_primary_name(output)); - Output *first = get_first_output(); - - /* TODO: refactor the following code into a nice function. maybe - * use an on_destroy callback which is implement differently for - * different container types (CT_CONTENT vs. CT_DOCKAREA)? */ - Con *first_content = output_get_content(first->con); - if (output->con != NULL) { - /* We need to move the workspaces from the disappearing output to the first output */ - /* 1: Get the con to focus next */ - Con *next = focused; - - /* 2: iterate through workspaces and re-assign them, fixing the coordinates - * of floating containers as we go */ - Con *current; - Con *old_content = output_get_content(output->con); - while (!TAILQ_EMPTY(&(old_content->nodes_head))) { - current = TAILQ_FIRST(&(old_content->nodes_head)); - if (current != next && TAILQ_EMPTY(&(current->focus_head))) { - /* the workspace is empty and not focused, get rid of it */ - DLOG("Getting rid of current = %p / %s (empty, unfocused)\n", current, current->name); - tree_close_internal(current, DONT_KILL_WINDOW, false); - continue; - } - DLOG("Detaching current = %p / %s\n", current, current->name); - con_detach(current); - DLOG("Re-attaching current = %p / %s\n", current, current->name); - con_attach(current, first_content, false); - DLOG("Fixing the coordinates of floating containers\n"); - Con *floating_con; - TAILQ_FOREACH (floating_con, &(current->floating_head), floating_windows) { - floating_fix_coordinates(floating_con, &(output->con->rect), &(first->con->rect)); - } - } - - /* Restore focus after con_detach / con_attach. next can be NULL, see #3523. */ - if (next) { - DLOG("now focusing next = %p\n", next); - con_focus(next); - workspace_show(con_get_workspace(next)); - } - - /* 3: move the dock clients to the first output */ - Con *child; - TAILQ_FOREACH (child, &(output->con->nodes_head), nodes) { - if (child->type != CT_DOCKAREA) - continue; - DLOG("Handling dock con %p\n", child); - Con *dock; - while (!TAILQ_EMPTY(&(child->nodes_head))) { - dock = TAILQ_FIRST(&(child->nodes_head)); - Con *nc; - Match *match; - nc = con_for_window(first->con, dock->window, &match); - DLOG("Moving dock client %p to nc %p\n", dock, nc); - con_detach(dock); - DLOG("Re-attaching\n"); - con_attach(dock, nc, false); - DLOG("Done\n"); - } - } - - DLOG("destroying disappearing con %p\n", output->con); + /* clear the pointer before move_content calls tree_close_internal in which the memory is freed */ Con *con = output->con; - /* clear the pointer before calling tree_close_internal in which the memory is freed */ output->con = NULL; - tree_close_internal(con, DONT_KILL_WINDOW, true); - DLOG("Done. Should be fine now\n"); + move_content(con); } output->to_be_disabled = false;