diff options
author | Orestis Floros <orestisflo@gmail.com> | 2021-11-11 20:14:22 +0100 |
---|---|---|
committer | Orestis Floros <orestisflo@gmail.com> | 2021-11-11 20:29:02 +0100 |
commit | e1d3e6b2f6c721c03022e4f80a9844f189d1862c (patch) | |
tree | e28157734e022d61d8ac09f1f3d91f0e62d344bc | |
parent | 43e805a00d1835db4fc0730ce5003956824b350d (diff) | |
download | i3-e1d3e6b2f6c721c03022e4f80a9844f189d1862c.tar.gz i3-e1d3e6b2f6c721c03022e4f80a9844f189d1862c.zip |
Fix transient_for endless loop
Other approaches would be:
- Slow/fast pointer technique.
- Using a set/associative map to save 'seen' nodes. i3 does not have
such data structure.
Counting the total amount of windows is the simpler to implement.
I've also extracted the logic in a function and re-used it in render.c.
Fixes #4404
-rw-r--r-- | include/con.h | 8 | ||||
-rw-r--r-- | release-notes/bugfixes/3-transient_for | 1 | ||||
-rw-r--r-- | src/con.c | 35 | ||||
-rw-r--r-- | src/manage.c | 23 | ||||
-rw-r--r-- | src/render.c | 28 | ||||
-rw-r--r-- | testcases/t/316-transient-for-loop.t | 42 |
6 files changed, 92 insertions, 45 deletions
diff --git a/include/con.h b/include/con.h index d8330098..3cea6780 100644 --- a/include/con.h +++ b/include/con.h @@ -209,6 +209,14 @@ Con *con_by_frame_id(xcb_window_t frame); Con *con_by_mark(const char *mark); /** + * Start from a container and traverse the transient_for linked list. Returns + * true if target window is found in the list. Protects againsts potential + * cycles. + * + */ +bool con_find_transient_for_window(Con *start, xcb_window_t target); + +/** * Returns true if and only if the given containers holds the mark. * */ diff --git a/release-notes/bugfixes/3-transient_for b/release-notes/bugfixes/3-transient_for new file mode 100644 index 00000000..c652874f --- /dev/null +++ b/release-notes/bugfixes/3-transient_for @@ -0,0 +1 @@ +Fix endless loop with transient_for windows @@ -731,6 +731,41 @@ Con *con_by_mark(const char *mark) { } /* + * Start from a container and traverse the transient_for linked list. Returns + * true if target window is found in the list. Protects againsts potential + * cycles. + * + */ +bool con_find_transient_for_window(Con *start, xcb_window_t target) { + Con *transient_con = start; + int count = con_num_windows(croot); + while (transient_con != NULL && + transient_con->window != NULL && + transient_con->window->transient_for != XCB_NONE) { + DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, target = 0x%08x\n", + transient_con->window->id, transient_con->window->transient_for, target); + if (transient_con->window->transient_for == target) { + return true; + } + Con *next_transient = con_by_window_id(transient_con->window->transient_for); + if (next_transient == NULL) { + break; + } + /* Some clients (e.g. x11-ssh-askpass) actually set WM_TRANSIENT_FOR to + * their own window id, so break instead of looping endlessly. */ + if (transient_con == next_transient) { + break; + } + transient_con = next_transient; + + if (count-- <= 0) { /* Avoid cycles, see #4404 */ + break; + } + } + return false; +} + +/* * Returns true if and only if the given containers holds the mark. * */ diff --git a/src/manage.c b/src/manage.c index ee046f0f..5f9aeb53 100644 --- a/src/manage.c +++ b/src/manage.c @@ -485,34 +485,17 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki (cwindow->leader != XCB_NONE && cwindow->leader != cwindow->id && con_by_window_id(cwindow->leader) != NULL)) { - LOG("This window is transient for another window, setting floating\n"); + DLOG("This window is transient for another window, setting floating\n"); want_floating = true; if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN && fs != NULL) { - LOG("There is a fullscreen window, leaving fullscreen mode\n"); + DLOG("There is a fullscreen window, leaving fullscreen mode\n"); con_toggle_fullscreen(fs, CF_OUTPUT); } else if (config.popup_during_fullscreen == PDF_SMART && fs != NULL && fs->window != NULL) { - i3Window *transient_win = cwindow; - while (transient_win != NULL && - transient_win->transient_for != XCB_NONE) { - if (transient_win->transient_for == fs->window->id) { - LOG("This floating window belongs to the fullscreen window (popup_during_fullscreen == smart)\n"); - set_focus = true; - break; - } - Con *next_transient = con_by_window_id(transient_win->transient_for); - if (next_transient == NULL) - break; - /* Some clients (e.g. x11-ssh-askpass) actually set - * WM_TRANSIENT_FOR to their own window id, so break instead of - * looping endlessly. */ - if (transient_win == next_transient->window) - break; - transient_win = next_transient->window; - } + set_focus = con_find_transient_for_window(nc, fs->window->id); } } diff --git a/src/render.c b/src/render.c index cf4fd45f..cc1c01f8 100644 --- a/src/render.c +++ b/src/render.c @@ -226,34 +226,12 @@ static void render_root(Con *con, Con *fullscreen) { } Con *floating_child = con_descend_focused(child); - Con *transient_con = floating_child; - bool is_transient_for = false; - while (transient_con != NULL && - transient_con->window != NULL && - transient_con->window->transient_for != XCB_NONE) { - DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, fullscreen_id = 0x%08x\n", - transient_con->window->id, transient_con->window->transient_for, fullscreen->window->id); - if (transient_con->window->transient_for == fullscreen->window->id) { - is_transient_for = true; - break; - } - Con *next_transient = con_by_window_id(transient_con->window->transient_for); - if (next_transient == NULL) - break; - /* Some clients (e.g. x11-ssh-askpass) actually set - * WM_TRANSIENT_FOR to their own window id, so break instead of - * looping endlessly. */ - if (transient_con == next_transient) - break; - transient_con = next_transient; - } - - if (!is_transient_for) - continue; - else { + if (con_find_transient_for_window(floating_child, fullscreen->window->id)) { DLOG("Rendering floating child even though in fullscreen mode: " "floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n", floating_child->window->transient_for, fullscreen->window->id); + } else { + continue; } } DLOG("floating child at (%d,%d) with %d x %d\n", diff --git a/testcases/t/316-transient-for-loop.t b/testcases/t/316-transient-for-loop.t new file mode 100644 index 00000000..336a8d8d --- /dev/null +++ b/testcases/t/316-transient-for-loop.t @@ -0,0 +1,42 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test that i3 does not get stuck in an endless loop between two windows that +# set transient_for for each other. +# Ticket: #4404 +# Bug still in: 4.20-69-g43e805a00 +# +use i3test i3_config => <<EOT; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +popup_during_fullscreen smart; +EOT + +my $fs = open_window; +cmd 'fullscreen enable'; + +my $w1 = open_window({ dont_map => 1 }); +my $w2 = open_window({ dont_map => 1 }); + +$w1->transient_for($w2); +$w2->transient_for($w1); +$w1->map; +$w2->map; + +does_i3_live; + +done_testing; |