From 536f60e230713fa131cdeeebf80bc5518c62eeee Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Fri, 15 Sep 2017 01:59:50 +0000 Subject: [PATCH 01/99] docs/userguide: Update description of focus wrapping Focus wrapping applies to all kinds of containers, not just tabbed/stacked ones. --- docs/userguide | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/userguide b/docs/userguide index cc9b5a01a..7064dcefe 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1039,11 +1039,12 @@ popup_during_fullscreen smart === Focus wrapping -When being in a tabbed or stacked container, the first container will be -focused when you use +focus down+ on the last container -- the focus wraps. If -however there is another stacked/tabbed container in that direction, focus will -be set on that container. This is the default behavior so you can navigate to -all your windows without having to use +focus parent+. +When in a container with several windows or child containers, the opposite +window will be focused when trying to move the focus over the edge of a +container (and there are no other containers in that direction) -- the focus +wraps. If however there is another window or container in that direction, focus +will be set on that window or container. This is the default behavior so you +can navigate to all your windows without having to use +focus parent+. If you want the focus to *always* wrap and you are aware of using +focus parent+ to switch to different containers, you can use the From 28f7e14650882d89fae2eee78291eeec8dd4e8fd Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Fri, 15 Sep 2017 02:57:55 +0000 Subject: [PATCH 02/99] Add "focus_wrapping" option Fixes #2352. --- docs/userguide | 28 ++++++++++--- include/config_directives.h | 1 + include/configuration.h | 8 ++++ parser-specs/config.spec | 6 +++ src/config.c | 2 + src/config_directives.c | 4 ++ src/tree.c | 2 +- testcases/t/201-config-parser.t | 1 + testcases/t/539-disable_focus_wrapping.t | 51 ++++++++++++++++++++++++ 9 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 testcases/t/539-disable_focus_wrapping.t diff --git a/docs/userguide b/docs/userguide index 7064dcefe..fdfb121be 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1039,12 +1039,28 @@ popup_during_fullscreen smart === Focus wrapping -When in a container with several windows or child containers, the opposite -window will be focused when trying to move the focus over the edge of a -container (and there are no other containers in that direction) -- the focus -wraps. If however there is another window or container in that direction, focus -will be set on that window or container. This is the default behavior so you -can navigate to all your windows without having to use +focus parent+. +By default, when in a container with several windows or child containers, the +opposite window will be focused when trying to move the focus over the edge of +a container (and there are no other containers in that direction) -- the focus +wraps. + +If desired, you can disable this behavior using the +focus_wrapping+ +configuration directive: + +*Syntax*: +--------------------- +focus_wrapping yes|no +--------------------- + +*Example*: +----------------- +focus_wrapping no +----------------- + +By default, focus wrapping does not occur if there is another window or +container in the specified direction, and focus will instead be set on that +window or container. This is the default behavior so you can navigate to all +your windows without having to use +focus parent+. If you want the focus to *always* wrap and you are aware of using +focus parent+ to switch to different containers, you can use the diff --git a/include/config_directives.h b/include/config_directives.h index b729e7288..66defa8f9 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -49,6 +49,7 @@ CFGFUN(workspace_layout, const char *layout); CFGFUN(workspace_back_and_forth, const char *value); CFGFUN(focus_follows_mouse, const char *value); CFGFUN(mouse_warping, const char *value); +CFGFUN(focus_wrapping, const char *value); CFGFUN(force_focus_wrapping, const char *value); CFGFUN(force_xinerama, const char *value); CFGFUN(disable_randr15, const char *value); diff --git a/include/configuration.h b/include/configuration.h index 4f6e5ce83..33df2c2df 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -137,6 +137,14 @@ struct Config { * comes with i3. Thus, you can turn it off entirely. */ bool disable_workspace_bar; + /** When focus wrapping is enabled (the default), attempting to + * move focus past the edge of the screen (in other words, in a + * direction in which there are no more containers to focus) will + * cause the focus to wrap to the opposite edge of the current + * container. When it is disabled, nothing happens; the current + * focus is preserved. */ + bool focus_wrapping; + /** Think of the following layout: Horizontal workspace with a tabbed * con on the left of the screen and a terminal on the right of the * screen. You are in the second container in the tabbed container and diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 665b046ae..3a10bbc1c 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -36,6 +36,7 @@ state INITIAL: 'no_focus' -> NO_FOCUS 'focus_follows_mouse' -> FOCUS_FOLLOWS_MOUSE 'mouse_warping' -> MOUSE_WARPING + 'focus_wrapping' -> FOCUS_WRAPPING 'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING 'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA 'disable_randr15', 'disable-randr15' -> DISABLE_RANDR15 @@ -203,6 +204,11 @@ state MOUSE_WARPING: value = 'none', 'output' -> call cfg_mouse_warping($value) +# focus_wrapping +state FOCUS_WRAPPING: + value = word + -> call cfg_focus_wrapping($value) + # force_focus_wrapping state FORCE_FOCUS_WRAPPING: value = word diff --git a/src/config.c b/src/config.c index 7e08b5208..c8e9bd6b9 100644 --- a/src/config.c +++ b/src/config.c @@ -227,6 +227,8 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, if (config.workspace_urgency_timer == 0) config.workspace_urgency_timer = 0.5; + config.focus_wrapping = true; + parse_configuration(override_configpath, true); if (reload) { diff --git a/src/config_directives.c b/src/config_directives.c index 376397e8a..41d21decd 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -264,6 +264,10 @@ CFGFUN(disable_randr15, const char *value) { config.disable_randr15 = eval_boolstr(value); } +CFGFUN(focus_wrapping, const char *value) { + config.focus_wrapping = eval_boolstr(value); +} + CFGFUN(force_focus_wrapping, const char *value) { config.force_focus_wrapping = eval_boolstr(value); } diff --git a/src/tree.c b/src/tree.c index 7f4665831..97c027982 100644 --- a/src/tree.c +++ b/src/tree.c @@ -675,7 +675,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) * */ void tree_next(char way, orientation_t orientation) { - _tree_next(focused, way, orientation, true); + _tree_next(focused, way, orientation, config.focus_wrapping); } /* diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index e8080a734..3e2c42972 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -470,6 +470,7 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: , ' no_focus focus_follows_mouse mouse_warping + focus_wrapping force_focus_wrapping force_xinerama force-xinerama diff --git a/testcases/t/539-disable_focus_wrapping.t b/testcases/t/539-disable_focus_wrapping.t new file mode 100644 index 000000000..8d2e84721 --- /dev/null +++ b/testcases/t/539-disable_focus_wrapping.t @@ -0,0 +1,51 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://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) +# +# Tests that focus does not wrap when focus_wrapping is disabled in +# the configuration. +# Ticket: #2352 +# Bug still in: 4.14-72-g6411130c +use i3test i3_config => <input_focus, $win2->id, "Second window focused initially"); + cmd "focus $prev"; + is($x->input_focus, $win1->id, "First window focused"); + cmd "focus $prev"; + is($x->input_focus, $win1->id, "First window still focused"); + cmd "focus $next"; + is($x->input_focus, $win2->id, "Second window focused"); + cmd "focus $next"; + is($x->input_focus, $win2->id, "Second window still focused"); +} + +test_orientation('v', 'up', 'down'); +test_orientation('h', 'left', 'right'); + +done_testing; From 65d00e0305424bc18f321dd1715a680c87c2371a Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 25 Sep 2017 04:43:00 +0300 Subject: [PATCH 03/99] Correct remaining string length for fill_rmlvo_from_root Fixes #2538. --- src/bindings.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings.c b/src/bindings.c index 42a2b79b8..c145b9560 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -910,7 +910,6 @@ static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) { int remaining = xcb_get_property_value_length(prop_reply); for (int i = 0; i < 5 && remaining > 0; i++) { const int len = strnlen(walk, remaining); - remaining -= len; switch (i) { case 0: sasprintf((char **)&(xkb_names->rules), "%.*s", len, walk); @@ -930,6 +929,7 @@ static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) { } DLOG("component %d of _XKB_RULES_NAMES is \"%.*s\"\n", i, len, walk); walk += (len + 1); + remaining -= (len + 1); } free(atom_reply); From 89d6287110ad1d1556facf091943554e25fbcdd8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 25 Sep 2017 09:04:31 +0200 Subject: [PATCH 04/99] update debian/changelog and release.sh after release (#2987) --- debian/changelog | 10 ++++++++-- release.sh | 12 ++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/debian/changelog b/debian/changelog index bed38f8e0..b2cab272f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,14 @@ -i3-wm (4.14.1-1) unstable; urgency=medium +i3-wm (4.14.2-1) unstable; urgency=medium * UNRELEASED - -- Michael Stapelberg Mon, 04 Sep 2017 08:17:05 +0200 + -- Michael Stapelberg Mon, 25 Sep 2017 08:55:22 +0200 + +i3-wm (4.14.1-1) unstable; urgency=medium + + * New upstream release. + + -- Michael Stapelberg Sun, 24 Sep 2017 19:21:15 +0200 i3-wm (4.14-1) unstable; urgency=medium diff --git a/release.sh b/release.sh index 8f537c0e2..cfb4beae4 100755 --- a/release.sh +++ b/release.sh @@ -1,9 +1,9 @@ #!/bin/zsh # This script is used to prepare a new release of i3. -export RELEASE_VERSION="4.13" -export PREVIOUS_VERSION="4.12" -export RELEASE_BRANCH="next" +export RELEASE_VERSION="4.14.1" +export PREVIOUS_VERSION="4.14" +export RELEASE_BRANCH="master" if [ ! -e "../i3.github.io" ] then @@ -232,9 +232,9 @@ echo "" echo " cd ${TMPDIR}" echo " sendmail -t < email.txt" echo "" -echo "Update milestones on GitHub:" -echo " Set due date of ${RELEASE_VERSION} to $(date +'%Y-$m-%d') and close the milestone" -echo " Create milestone for the next version with unset due date" +echo "Update milestones on GitHub (only for new major versions):" +echo " Set due date of ${RELEASE_VERSION} to $(date +'%Y-%m-%d') and close the milestone" +echo " Create milestone for the next major version with unset due date" echo "" echo "Announce on:" echo " twitter" From 999bebc692af1cc2e0428e2f1f42a0d126285dcb Mon Sep 17 00:00:00 2001 From: Pietro Cerutti Date: Mon, 25 Sep 2017 17:11:15 +0100 Subject: [PATCH 05/99] WIFEXITED needs sys/wait.h (#2989) --- testcases/inject_randr1.5.c | 1 + 1 file changed, 1 insertion(+) diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c index 6cccfa762..5506d67e7 100644 --- a/testcases/inject_randr1.5.c +++ b/testcases/inject_randr1.5.c @@ -23,6 +23,7 @@ #include #include #include +#include #include static void uds_connection_cb(EV_P_ ev_io *w, int revents); From 50edf495aa3971bfb67471c3aaf2eb72e7abd443 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Fri, 22 Sep 2017 23:41:38 +0000 Subject: [PATCH 06/99] Merge "force_focus_wrapping" option into "focus_wrapping force" Allow enabling forced focus wrapping by specifying "focus_wrapping force" in i3's configuration. This syntax supersedes the previous "force_focus_wrapping yes" one, which remains available for backwards compatibility. --- docs/userguide | 44 ++++++++++++++++++---------------------- include/configuration.h | 25 ++++++++++++----------- include/data.h | 9 ++++++++ parser-specs/config.spec | 2 +- src/config.c | 2 +- src/config_directives.c | 19 +++++++++++++++-- src/tree.c | 5 +++-- 7 files changed, 64 insertions(+), 42 deletions(-) diff --git a/docs/userguide b/docs/userguide index fdfb121be..bfb3da661 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1044,38 +1044,34 @@ opposite window will be focused when trying to move the focus over the edge of a container (and there are no other containers in that direction) -- the focus wraps. -If desired, you can disable this behavior using the +focus_wrapping+ -configuration directive: +If desired, you can disable this behavior by setting the +focus_wrapping+ +configuration directive to the value +no+. -*Syntax*: ---------------------- -focus_wrapping yes|no ---------------------- - -*Example*: ------------------ -focus_wrapping no ------------------ - -By default, focus wrapping does not occur if there is another window or -container in the specified direction, and focus will instead be set on that -window or container. This is the default behavior so you can navigate to all -your windows without having to use +focus parent+. +When enabled, focus wrapping does not occur by default if there is another +window or container in the specified direction, and focus will instead be set +on that window or container. This is the default behavior so you can navigate +to all your windows without having to use +focus parent+. If you want the focus to *always* wrap and you are aware of using +focus -parent+ to switch to different containers, you can use the -+force_focus_wrapping+ configuration directive. After enabling it, the focus -will always wrap. +parent+ to switch to different containers, you can instead set +focus_wrapping+ +to the value +force+. *Syntax*: --------------------------- -force_focus_wrapping yes|no ---------------------------- +focus_wrapping yes|no|force -*Example*: ------------------------- +# Legacy syntax, equivalent to "focus_wrapping force" force_focus_wrapping yes ------------------------- +--------------------------- + +*Examples*: +----------------- +# Disable focus wrapping +focus_wrapping no + +# Force focus wrapping +focus_wrapping force +----------------- === Forcing Xinerama diff --git a/include/configuration.h b/include/configuration.h index 33df2c2df..8f1ce3320 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -142,18 +142,19 @@ struct Config { * direction in which there are no more containers to focus) will * cause the focus to wrap to the opposite edge of the current * container. When it is disabled, nothing happens; the current - * focus is preserved. */ - bool focus_wrapping; - - /** Think of the following layout: Horizontal workspace with a tabbed - * con on the left of the screen and a terminal on the right of the - * screen. You are in the second container in the tabbed container and - * focus to the right. By default, i3 will set focus to the terminal on - * the right. If you are in the first container in the tabbed container - * however, focusing to the left will wrap. This option forces i3 to - * always wrap, which will result in you having to use "focus parent" - * more often. */ - bool force_focus_wrapping; + * focus is preserved. + * + * Additionally, focus wrapping may be forced. Think of the + * following layout: Horizontal workspace with a tabbed con on the + * left of the screen and a terminal on the right of the + * screen. You are in the second container in the tabbed container + * and focus to the right. By default, i3 will set focus to the + * terminal on the right. If you are in the first container in the + * tabbed container however, focusing to the left will + * wrap. Setting focus_wrapping to FOCUS_WRAPPING_FORCE forces i3 + * to always wrap, which will result in you having to use "focus + * parent" more often. */ + focus_wrapping_t focus_wrapping; /** By default, use the RandR API for multi-monitor setups. * Unfortunately, the nVidia binary graphics driver doesn't support diff --git a/include/data.h b/include/data.h index 7411ac20c..258ea94fe 100644 --- a/include/data.h +++ b/include/data.h @@ -133,6 +133,15 @@ typedef enum { POINTER_WARPING_NONE = 1 } warping_t; +/** + * Focus wrapping modes. + */ +typedef enum { + FOCUS_WRAPPING_OFF = 0, + FOCUS_WRAPPING_ON = 1, + FOCUS_WRAPPING_FORCE = 2 +} focus_wrapping_t; + /** * Stores a rectangle, for example the size of a window, the child window etc. * It needs to be packed so that the compiler will not add any padding bytes. diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 3a10bbc1c..6b8265437 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -206,7 +206,7 @@ state MOUSE_WARPING: # focus_wrapping state FOCUS_WRAPPING: - value = word + value = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive', 'force' -> call cfg_focus_wrapping($value) # force_focus_wrapping diff --git a/src/config.c b/src/config.c index c8e9bd6b9..24c7b541e 100644 --- a/src/config.c +++ b/src/config.c @@ -227,7 +227,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, if (config.workspace_urgency_timer == 0) config.workspace_urgency_timer = 0.5; - config.focus_wrapping = true; + config.focus_wrapping = FOCUS_WRAPPING_ON; parse_configuration(override_configpath, true); diff --git a/src/config_directives.c b/src/config_directives.c index 41d21decd..8d5cf1f05 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -265,11 +265,26 @@ CFGFUN(disable_randr15, const char *value) { } CFGFUN(focus_wrapping, const char *value) { - config.focus_wrapping = eval_boolstr(value); + if (strcmp(value, "force") == 0) { + config.focus_wrapping = FOCUS_WRAPPING_FORCE; + } else if (eval_boolstr(value)) { + config.focus_wrapping = FOCUS_WRAPPING_ON; + } else { + config.focus_wrapping = FOCUS_WRAPPING_OFF; + } } CFGFUN(force_focus_wrapping, const char *value) { - config.force_focus_wrapping = eval_boolstr(value); + /* Legacy syntax. */ + if (eval_boolstr(value)) { + config.focus_wrapping = FOCUS_WRAPPING_FORCE; + } else { + /* For "force_focus_wrapping off", don't enable or disable + * focus wrapping, just ensure it's not forced. */ + if (config.focus_wrapping == FOCUS_WRAPPING_FORCE) { + config.focus_wrapping = FOCUS_WRAPPING_ON; + } + } } CFGFUN(workspace_back_and_forth, const char *value) { diff --git a/src/tree.c b/src/tree.c index 97c027982..fc9526e6d 100644 --- a/src/tree.c +++ b/src/tree.c @@ -641,7 +641,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) next = TAILQ_PREV(current, nodes_head, nodes); if (!next) { - if (!config.force_focus_wrapping) { + if (config.focus_wrapping != FOCUS_WRAPPING_FORCE) { /* If there is no next/previous container, we check if we can focus one * when going higher (without wrapping, though). If so, we are done, if * not, we wrap */ @@ -675,7 +675,8 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) * */ void tree_next(char way, orientation_t orientation) { - _tree_next(focused, way, orientation, config.focus_wrapping); + _tree_next(focused, way, orientation, + config.focus_wrapping != FOCUS_WRAPPING_OFF); } /* From 42571075092eb5e18d3414f1ff13117ad355478d Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 26 Sep 2017 02:50:10 +0300 Subject: [PATCH 07/99] i3bar: fix segfault when no status_command is provided Fixes #2933. --- i3bar/src/ipc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index c932aaf7d..49c729ae9 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -177,7 +177,7 @@ void got_bar_config_update(char *event) { /* update the configuration with the received settings */ DLOG("Received bar config update \"%s\"\n", event); - char *old_command = sstrdup(config.command); + char *old_command = config.command ? sstrdup(config.command) : NULL; bar_display_mode_t old_mode = config.hide_on_modifier; parse_config_json(event); if (old_mode != config.hide_on_modifier) { @@ -189,7 +189,7 @@ void got_bar_config_update(char *event) { init_colors(&(config.colors)); /* restart status command process */ - if (strcmp(old_command, config.command) != 0) { + if (old_command && strcmp(old_command, config.command) != 0) { kill_child(); start_child(config.command); } From 828ce9c6400f3dddb6189bec6da7f2fd173af10b Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 27 Sep 2017 15:23:32 +0300 Subject: [PATCH 08/99] dump-asy.pl: use correct tmp dirname instead of hardcoded /tmp For example, on some systems, $rep might be saved in /tmp/$USER/ instead of /tmp/. --- contrib/dump-asy.pl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl index 3ebdb858e..478a896a5 100755 --- a/contrib/dump-asy.pl +++ b/contrib/dump-asy.pl @@ -13,6 +13,7 @@ use Data::Dumper; use AnyEvent::I3; use File::Temp; +use File::Basename; use v5.10; my $i3 = i3(); @@ -75,4 +76,5 @@ sub find_node_with_name { close($tmp); my $rep = "$tmp"; $rep =~ s/asy$/eps/; -system("cd /tmp && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep"); +my $tmp_dir = dirname($rep); +system("cd $tmp_dir && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep"); From af78331ee774a0021ed07f8b1d5d41fc8ffcd837 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 24 Sep 2017 10:05:04 +0300 Subject: [PATCH 09/99] Use con_descend_focused for workspaces in _tree_next This way, when changing focus between outputs, the directional focus command will focus the focused window within the parent container that is next in the given direction. Previously, the next window of the given direction was focused which is Inconsistent with changing focus inside the same output. Fixes #1160. --- src/tree.c | 17 +---- testcases/t/510-focus-across-outputs.t | 96 +++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 15 deletions(-) diff --git a/src/tree.c b/src/tree.c index a6cc59fbb..710bb655f 100644 --- a/src/tree.c +++ b/src/tree.c @@ -560,21 +560,10 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) if (!workspace) return false; - workspace_show(workspace); - - /* If a workspace has an active fullscreen container, one of its - * children should always be focused. The above workspace_show() - * should be adequate for that, so return. */ - if (con_get_fullscreen_con(workspace, CF_OUTPUT)) - return true; - - Con *focus = con_descend_direction(workspace, direction); - - /* special case: if there was no tiling con to focus and the workspace - * has a floating con in the focus stack, focus the top of the focus - * stack (which may be floating) */ - if (focus == workspace) + Con *focus = con_descend_tiling_focused(workspace); + if (focus == workspace) { focus = con_descend_focused(workspace); + } if (focus) { con_focus(focus); diff --git a/testcases/t/510-focus-across-outputs.t b/testcases/t/510-focus-across-outputs.t index 21169adbc..07a2115c0 100644 --- a/testcases/t/510-focus-across-outputs.t +++ b/testcases/t/510-focus-across-outputs.t @@ -92,7 +92,7 @@ reset_focus $s3_ws; cmd "workspace $s2_ws"; cmd 'focus right'; -is($x->input_focus, $sixth->id, 'sixth window focused'); +is($x->input_focus, $seventh->id, 'seventh window focused'); reset_focus $s3_ws; cmd "workspace $s2_ws"; @@ -110,6 +110,8 @@ is($x->input_focus, $third->id, 'third window focused'); cmd 'focus parent'; cmd 'focus parent'; cmd 'split v'; +# Focus second or else $first gets to the top of the focus stack. +cmd '[id=' . $second->id . '] focus'; reset_focus $s0_ws; cmd "workspace $s3_ws"; @@ -137,4 +139,96 @@ cmd "workspace $s2_ws"; cmd 'focus up'; is($x->input_focus, $second->id, 'second window focused'); +################################################################### +# Test that focus (left|down|right|up), when focusing across +# outputs, doesn't focus the next window in the given direction but +# the most focused window of the container in the given direction. +# In the following layout: +# [ WS1*[ ] WS2[ H[ A B* ] ] ] +# (where the asterisk denotes the focused container within its +# parent) moving right from WS1 should focus B which is focused +# inside WS2, not A which is the next window on the right of WS1. +# See issue #1160. +################################################################### + +kill_all_windows; + +sync_with_i3; +$x->root->warp_pointer(1025, 0); # Second screen. +sync_with_i3; +$s1_ws = fresh_workspace; +$first = open_window; +$second = open_window; + +sync_with_i3; +$x->root->warp_pointer(0, 0); # First screen. +sync_with_i3; +$s0_ws = fresh_workspace; +open_window; +$third = open_window; + +cmd 'focus right'; +is($x->input_focus, $second->id, 'second window (rightmost) focused'); +cmd 'focus left'; +is($x->input_focus, $first->id, 'first window focused'); +cmd 'focus left'; +is($x->input_focus, $third->id, 'third window focused'); + + +################################################################### +# Similar but with a tabbed layout. +################################################################### + +cmd 'layout tabbed'; +$fourth = open_window; +cmd 'focus left'; +is($x->input_focus, $third->id, 'third window (tabbed) focused'); +cmd "workspace $s1_ws"; +cmd 'focus left'; +is($x->input_focus, $third->id, 'third window (tabbed) focused'); + + +################################################################### +# Similar but with a stacked layout on the bottom screen. +################################################################### + +sync_with_i3; +$x->root->warp_pointer(0, 769); # Third screen. +sync_with_i3; +$s2_ws = fresh_workspace; +cmd 'layout stacked'; +$fifth = open_window; +$sixth = open_window; + +cmd "workspace $s0_ws"; +cmd 'focus down'; +is($x->input_focus, $sixth->id, 'sixth window (stacked) focused'); + +################################################################### +# Similar but with a more complex layout. +################################################################### + +sync_with_i3; +$x->root->warp_pointer(1025, 769); # Fourth screen. +sync_with_i3; +$s3_ws = fresh_workspace; +open_window; +open_window; +cmd 'split v'; +open_window; +open_window; +cmd 'split h'; +my $nested = open_window; +open_window; +cmd 'focus left'; +is($x->input_focus, $nested->id, 'nested window focused'); + +cmd "workspace $s1_ws"; +cmd 'focus down'; +is($x->input_focus, $nested->id, 'nested window focused from workspace above'); + +cmd "workspace $s2_ws"; +cmd 'focus right'; +is($x->input_focus, $nested->id, 'nested window focused from workspace on the left'); + done_testing; From 7a7481e5784fc4db9b5724c9d2755040f5a47fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 28 Sep 2017 22:12:51 +0200 Subject: [PATCH 10/99] Deal with UTF8 in per-workspace-layout.pl. (#2997) fixes #2996 --- contrib/per-workspace-layout.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/per-workspace-layout.pl b/contrib/per-workspace-layout.pl index 48590456a..4a2b4b9e2 100644 --- a/contrib/per-workspace-layout.pl +++ b/contrib/per-workspace-layout.pl @@ -14,6 +14,7 @@ use AnyEvent; use AnyEvent::I3; use v5.10; +use utf8; my %layouts = ( '4' => 'tabbed', From c08ef361999deefce963b1a907d70acbeabe1382 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 29 Sep 2017 14:02:18 -0700 Subject: [PATCH 11/99] docs/ipc: include new Go package, reference example byteorder code (#3000) --- docs/ipc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/ipc b/docs/ipc index 997a3055f..526c8e696 100644 --- a/docs/ipc +++ b/docs/ipc @@ -881,6 +881,7 @@ C++:: * https://github.com/drmgc/i3ipcpp Go:: * https://github.com/mdirkse/i3ipc-go + * https://github.com/i3/go-i3 JavaScript:: * https://github.com/acrisci/i3ipc-gjs Lua:: @@ -958,3 +959,6 @@ detect the byte order i3 is using: payload. Then, receive the pending +COMMAND+ message reply in big endian. 5. From here on out, send/receive all messages using the detected byte order. + +Find an example implementation of this technique in +https://github.com/i3/go-i3/blob/master/byteorder.go From 14c8cf86223a8f2e5863113ed13efcff8eca257b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 30 Sep 2017 11:27:53 +0200 Subject: [PATCH 12/99] tests: implement xtest_sync_with_i3 The regular sync_with_i3 is not sufficient because i3test::XTEST uses a separate X11 connection. --- testcases/lib/i3test/XTEST.pm | 92 ++++++++++++++++++++- testcases/t/257-keypress-group1-fallback.t | 4 + testcases/t/258-keypress-release.t | 4 + testcases/t/286-root-window-mouse-binding.t | 2 +- testcases/t/290-keypress-numlock.t | 26 +++++- 5 files changed, 124 insertions(+), 4 deletions(-) diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm index 1ca964b15..40759cfdb 100644 --- a/testcases/lib/i3test/XTEST.pm +++ b/testcases/lib/i3test/XTEST.pm @@ -14,6 +14,7 @@ use ExtUtils::PkgConfig; use Exporter (); our @EXPORT = qw( inlinec_connect + xtest_sync_with_i3 set_xkb_group xtest_key_press xtest_key_release @@ -38,7 +39,7 @@ i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb # ineffective. my %sn_config; BEGIN { - %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest'); + %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest xcb-util'); } use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags}; @@ -53,8 +54,12 @@ use Inline C => <<'END_OF_C_CODE'; #include #include #include +#include static xcb_connection_t *conn = NULL; +static xcb_window_t sync_window; +static xcb_window_t root_window; +static xcb_atom_t i3_sync_atom; bool inlinec_connect() { int screen; @@ -89,9 +94,90 @@ bool inlinec_connect() { } free(usereply); + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, xcb_intern_atom(conn, 0, strlen("I3_SYNC"), "I3_SYNC"), NULL); + i3_sync_atom = reply->atom; + free(reply); + + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); + root_window = root_screen->root; + sync_window = xcb_generate_id(conn); + xcb_create_window(conn, + XCB_COPY_FROM_PARENT, // depth + sync_window, // window + root_window, // parent + -15, // x + -15, // y + 1, // width + 1, // height + 0, // border_width + XCB_WINDOW_CLASS_INPUT_OUTPUT, // class + XCB_COPY_FROM_PARENT, // visual + XCB_CW_OVERRIDE_REDIRECT, // value_mask + (uint32_t[]){ + 1, // override_redirect + }); // value_list + return true; } +void xtest_sync_with_i3() { + xcb_client_message_event_t ev; + memset(&ev, '\0', sizeof(xcb_client_message_event_t)); + + const int nonce = rand() % 255; + + ev.response_type = XCB_CLIENT_MESSAGE; + ev.window = sync_window; + ev.type = i3_sync_atom; + ev.format = 32; + ev.data.data32[0] = sync_window; + ev.data.data32[1] = nonce; + + xcb_send_event(conn, false, root_window, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev); + xcb_flush(conn); + + xcb_generic_event_t *event = NULL; + while (1) { + free(event); + if ((event = xcb_wait_for_event(conn)) == NULL) { + break; + } + if (event->response_type == 0) { + fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence); + continue; + } + + /* Strip off the highest bit (set if the event is generated) */ + const int type = (event->response_type & 0x7F); + switch (type) { + case XCB_CLIENT_MESSAGE: { + xcb_client_message_event_t *ev = (xcb_client_message_event_t *)event; + { + const uint32_t got = ev->data.data32[0]; + const uint32_t want = sync_window; + if (got != want) { + fprintf(stderr, "Ignoring ClientMessage: unknown window: got %d, want %d\n", got, want); + continue; + } + } + { + const uint32_t got = ev->data.data32[1]; + const uint32_t want = nonce; + if (got != want) { + fprintf(stderr, "Ignoring ClientMessage: unknown nonce: got %d, want %d\n", got, want); + continue; + } + } + return; + } + default: + fprintf(stderr, "Unexpected X11 event of type %d received (XCB_CLIENT_MESSAGE = %d)\n", type, XCB_CLIENT_MESSAGE); + break; + } + } + free(event); +} + // NOTE: while |group| should be a uint8_t, Inline::C will not define the // function unless we use an int. bool set_xkb_group(int group) { @@ -283,6 +369,10 @@ Sends a ButtonRelease event via XTEST, with the specified C<$button>. Returns false when there was an X11 error, true otherwise. +=head2 xtest_sync_with_i3() + +Ensures i3 has processed all X11 events which were triggered by this module. + =head1 AUTHOR Michael Stapelberg diff --git a/testcases/t/257-keypress-group1-fallback.t b/testcases/t/257-keypress-group1-fallback.t index f9166fadb..e3874c92b 100644 --- a/testcases/t/257-keypress-group1-fallback.t +++ b/testcases/t/257-keypress-group1-fallback.t @@ -44,6 +44,7 @@ is(listen_for_binding( sub { xtest_key_press(107); xtest_key_release(107); + xtest_sync_with_i3; }, ), 'Print', @@ -55,6 +56,7 @@ is(listen_for_binding( xtest_key_press(36); # Return xtest_key_release(36); # Return xtest_key_release(133); # Super_L + xtest_sync_with_i3; }, ), 'Mod4+Return', @@ -67,6 +69,7 @@ is(listen_for_binding( sub { xtest_key_press(107); xtest_key_release(107); + xtest_sync_with_i3; }, ), 'Print', @@ -78,6 +81,7 @@ is(listen_for_binding( xtest_key_press(36); # Return xtest_key_release(36); # Return xtest_key_release(133); # Super_L + xtest_sync_with_i3; }, ), 'Mod4+Return', diff --git a/testcases/t/258-keypress-release.t b/testcases/t/258-keypress-release.t index 72bfc8628..bf446003b 100644 --- a/testcases/t/258-keypress-release.t +++ b/testcases/t/258-keypress-release.t @@ -43,6 +43,7 @@ is(listen_for_binding( sub { xtest_key_press(107); # Print xtest_key_release(107); # Print + xtest_sync_with_i3; }, ), 'Print', @@ -54,6 +55,7 @@ is(listen_for_binding( xtest_key_press(107); # Print xtest_key_release(107); # Print xtest_key_release(37); # Control_L + xtest_sync_with_i3; }, ), 'Control+Print', @@ -65,6 +67,7 @@ is(listen_for_binding( xtest_key_press(56); # b xtest_key_release(56); # b xtest_key_release(64); # Alt_L + xtest_sync_with_i3; }, ), 'Mod1+b', @@ -78,6 +81,7 @@ is(listen_for_binding( xtest_key_release(56); # b xtest_key_release(50); # Shift_L xtest_key_release(64); # Alt_L + xtest_sync_with_i3; }, ), 'Mod1+Shift+b release', diff --git a/testcases/t/286-root-window-mouse-binding.t b/testcases/t/286-root-window-mouse-binding.t index 6c1b5d6ac..0da373b74 100644 --- a/testcases/t/286-root-window-mouse-binding.t +++ b/testcases/t/286-root-window-mouse-binding.t @@ -30,7 +30,7 @@ fresh_workspace; xtest_button_press(4, 50, 50); xtest_button_release(4, 50, 50); -sync_with_i3; +xtest_sync_with_i3; is(focused_ws(), 'special', 'the binding was triggered'); diff --git a/testcases/t/290-keypress-numlock.t b/testcases/t/290-keypress-numlock.t index 5137c35fc..b896e4320 100644 --- a/testcases/t/290-keypress-numlock.t +++ b/testcases/t/290-keypress-numlock.t @@ -57,6 +57,7 @@ is(listen_for_binding( sub { xtest_key_press(87); # KP_End xtest_key_release(87); # KP_End + xtest_sync_with_i3; }, ), 'KP_End', @@ -70,6 +71,7 @@ is(listen_for_binding( xtest_key_release(87); # KP_1 xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'KP_1', @@ -81,6 +83,7 @@ is(listen_for_binding( xtest_key_press(38); # a xtest_key_release(38); # a xtest_key_release(133); # Super_L + xtest_sync_with_i3; }, ), 'a', @@ -96,6 +99,7 @@ is(listen_for_binding( xtest_key_release(133); # Super_L xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'a', @@ -105,6 +109,7 @@ is(listen_for_binding( sub { xtest_key_press(9); # Escape xtest_key_release(9); # Escape + xtest_sync_with_i3; }, ), 'Escape', @@ -118,6 +123,7 @@ is(listen_for_binding( xtest_key_release(9); # Escape xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'Escape', @@ -129,6 +135,7 @@ is(listen_for_binding( xtest_key_press(9); # Escape xtest_key_release(9); # Escape xtest_key_release(50); # Shift_L + xtest_sync_with_i3; }, ), 'Shift+Escape', @@ -144,6 +151,7 @@ is(listen_for_binding( xtest_key_release(50); # Shift_L xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'Shift+Escape', @@ -157,6 +165,7 @@ is(listen_for_binding( xtest_key_release(24); # q xtest_key_release(64); # Alt_L xtest_key_release(50); # Shift_L + xtest_sync_with_i3; }, ), 'Mod1+Shift+q', @@ -174,6 +183,7 @@ is(listen_for_binding( xtest_key_release(50); # Shift_L xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'Mod1+Shift+q', @@ -183,6 +193,7 @@ is(listen_for_binding( sub { xtest_key_press(39); # s xtest_key_release(39); # s + xtest_sync_with_i3; }, ), 's', @@ -196,6 +207,7 @@ is(listen_for_binding( xtest_key_release(39); # s xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 's', @@ -228,6 +240,7 @@ is(listen_for_binding( sub { xtest_key_press(133); # Super_L xtest_key_release(133); # Super_L + xtest_sync_with_i3; }, ), 'Super_L', @@ -241,6 +254,7 @@ is(listen_for_binding( xtest_key_release(133); # Super_L xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'Super_L', @@ -252,6 +266,7 @@ is(listen_for_binding( xtest_key_press(36); # Return xtest_key_release(36); # Return xtest_key_release(133); # Super_L + xtest_sync_with_i3; }, ), 'Return', @@ -267,6 +282,7 @@ is(listen_for_binding( xtest_key_release(133); # Super_L xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'Return', @@ -297,6 +313,7 @@ is(listen_for_binding( sub { xtest_key_press(87); # KP_End xtest_key_release(87); # KP_End + xtest_sync_with_i3; }, ), 'KP_End', @@ -306,6 +323,7 @@ is(listen_for_binding( sub { xtest_key_press(88); # KP_Down xtest_key_release(88); # KP_Down + xtest_sync_with_i3; }, ), 'KP_Down', @@ -319,6 +337,7 @@ is(listen_for_binding( xtest_key_release(87); # KP_1 xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'timeout', @@ -332,6 +351,7 @@ is(listen_for_binding( xtest_key_release(88); # KP_2 xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'timeout', @@ -369,6 +389,7 @@ is(listen_for_binding( xtest_button_release(4, 50, 50); xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'button4', @@ -376,8 +397,9 @@ is(listen_for_binding( is(listen_for_binding( sub { - xtest_button_press(4, 50, 50); - xtest_button_release(4, 50, 50); + xtest_button_press(4, 50, 50); + xtest_button_release(4, 50, 50); + xtest_sync_with_i3; }, ), 'button4', From ce21de8ddea4468ffcaf966da763d02d4dbd1e8d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 24 Sep 2017 15:40:30 +0200 Subject: [PATCH 13/99] Implement the tick event This makes our tests less flaky, shorter, and more readable. fixes #2988 --- AnyEvent-I3/lib/AnyEvent/I3.pm | 16 ++- docs/ipc | 41 +++++++ i3-msg/main.c | 4 +- include/i3/ipc.h | 7 ++ include/ipc.h | 4 + src/ipc.c | 51 ++++++++- testcases/lib/i3test.pm.in | 82 ++++++++++++++ testcases/lib/i3test/XTEST.pm | 82 -------------- testcases/t/115-ipc-workspaces.t | 58 ++-------- testcases/t/199-ipc-mode-event.t | 23 +--- testcases/t/205-ipc-windows.t | 43 ++------ testcases/t/219-ipc-window-focus.t | 74 +++---------- testcases/t/220-ipc-window-title.t | 43 ++------ testcases/t/225-ipc-window-fullscreen.t | 42 ++------ testcases/t/227-ipc-workspace-empty.t | 88 ++++----------- testcases/t/231-ipc-floating-event.t | 43 +++----- testcases/t/238-ipc-binding-event.t | 43 +++----- testcases/t/257-keypress-group1-fallback.t | 5 - testcases/t/258-keypress-release.t | 5 - .../t/263-config-reload-reverts-bind-mode.t | 22 +--- testcases/t/265-ipc-mark.t | 39 ++----- testcases/t/275-ipc-window-close.t | 35 ++---- testcases/t/276-ipc-window-move.t | 45 +++----- testcases/t/277-ipc-window-urgent.t | 69 +++++------- testcases/t/289-ipc-shutdown-event.t | 9 +- testcases/t/290-keypress-numlock.t | 32 ++---- testcases/t/514-ipc-workspace-multi-monitor.t | 43 ++------ testcases/t/517-regress-move-direction-ipc.t | 54 +++------- testcases/t/525-i3bar-mouse-bindings.t | 101 ++++++++++-------- 29 files changed, 463 insertions(+), 740 deletions(-) diff --git a/AnyEvent-I3/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm index 08b1c0a71..198c41c9a 100644 --- a/AnyEvent-I3/lib/AnyEvent/I3.pm +++ b/AnyEvent-I3/lib/AnyEvent/I3.pm @@ -99,11 +99,12 @@ use constant TYPE_GET_BAR_CONFIG => 6; use constant TYPE_GET_VERSION => 7; use constant TYPE_GET_BINDING_MODES => 8; use constant TYPE_GET_CONFIG => 9; +use constant TYPE_SEND_TICK => 10; our %EXPORT_TAGS = ( 'all' => [ qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION - TYPE_GET_BINDING_MODES TYPE_GET_CONFIG) + TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -120,6 +121,7 @@ my %events = ( barconfig_update => ($event_mask | 4), binding => ($event_mask | 5), shutdown => ($event_mask | 6), + tick => ($event_mask | 7), _error => 0xFFFFFFFF, ); @@ -519,6 +521,18 @@ sub get_config { $self->message(TYPE_GET_CONFIG); } +=head2 send_tick + +Sends a tick event. Requires i3 >= 4.15 + +=cut +sub send_tick { + my ($self, $payload) = @_; + + $self->_ensure_connection; + + $self->message(TYPE_SEND_TICK, $payload); +} =head2 command($content) diff --git a/docs/ipc b/docs/ipc index 526c8e696..8b767adeb 100644 --- a/docs/ipc +++ b/docs/ipc @@ -64,6 +64,7 @@ to do that). | 7 | +GET_VERSION+ | <<_version_reply,VERSION>> | Gets the i3 version. | 8 | +GET_BINDING_MODES+ | <<_binding_modes_reply,BINDING_MODES>> | Gets the names of all currently configured binding modes. | 9 | +GET_CONFIG+ | <<_config_reply,CONFIG>> | Returns the last loaded i3 config. +| 10 | +SEND_TICK+ | <<_tick_reply,TICK>> | Sends a tick event with the specified payload. |====================================================== So, a typical message could look like this: @@ -126,6 +127,8 @@ BINDING_MODES (8):: Reply to the GET_BINDING_MODES message. GET_CONFIG (9):: Reply to the GET_CONFIG message. +TICK (10):: + Reply to the SEND_TICK message. [[_command_reply]] === COMMAND reply @@ -637,6 +640,19 @@ which is a string containing the config file as loaded by i3 most recently. { "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" } ------------------- +[[_tick_reply]] +=== TICK reply + +The reply is a map containing the "success" member. After the reply was +received, the tick event has been written to all IPC connections which subscribe +to tick events. UNIX sockets are usually buffered, but you can be certain that +once you receive the tick event you just triggered, you must have received all +events generated prior to the +SEND_TICK+ message (happened-before relation). + +*Example:* +------------------- +{ "success": true } +------------------- == Events @@ -694,6 +710,10 @@ binding (5):: mouse shutdown (6):: Sent when the ipc shuts down because of a restart or exit by user command +tick (7):: + Sent when the ipc client subscribes to the tick event (with +"first": + true+) or when any ipc client sends a SEND_TICK message (with +"first": + false+). *Example:* -------------------------------------------------------------------- @@ -866,6 +886,27 @@ because of a user action such as a +restart+ or +exit+ command. The +change } --------------------------- +=== tick event + +This event is triggered by a subscription to tick events or by a +SEND_TICK+ +message. + +*Example (upon subscription):* +-------------------------------------------------------------------------------- +{ + "first": true, + "payload": "" +} +-------------------------------------------------------------------------------- + +*Example (upon +SEND_TICK+ with a payload of +arbitrary string+):* +-------------------------------------------------------------------------------- +{ + "first": false, + "payload": "arbitrary string" +} +-------------------------------------------------------------------------------- + == See also (existing libraries) [[libraries]] diff --git a/i3-msg/main.c b/i3-msg/main.c index 8907a6f7a..a4f948500 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -207,9 +207,11 @@ int main(int argc, char *argv[]) { message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION; } else if (strcasecmp(optarg, "get_config") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG; + } else if (strcasecmp(optarg, "send_tick") == 0) { + message_type = I3_IPC_MESSAGE_TYPE_SEND_TICK; } else { printf("Unknown message type\n"); - printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n"); + printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config, send_tick\n"); exit(EXIT_FAILURE); } } else if (o == 'q') { diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 993a2a248..9e0280c93 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -60,6 +60,9 @@ typedef struct i3_ipc_header { /** Request the raw last loaded i3 config. */ #define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9 +/** Send a tick event to all subscribers. */ +#define I3_IPC_MESSAGE_TYPE_SEND_TICK 10 + /* * Messages from i3 to clients * @@ -74,6 +77,7 @@ typedef struct i3_ipc_header { #define I3_IPC_REPLY_TYPE_VERSION 7 #define I3_IPC_REPLY_TYPE_BINDING_MODES 8 #define I3_IPC_REPLY_TYPE_CONFIG 9 +#define I3_IPC_REPLY_TYPE_TICK 10 /* * Events from i3 to clients. Events have the first bit set high. @@ -101,3 +105,6 @@ typedef struct i3_ipc_header { /** The shutdown event will be triggered when the ipc shuts down */ #define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6) + +/** The tick event will be sent upon a tick IPC message */ +#define I3_IPC_EVENT_TICK (I3_IPC_EVENT_MASK | 7) diff --git a/include/ipc.h b/include/ipc.h index 7ffbf7a83..c6ad35c77 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -31,6 +31,10 @@ typedef struct ipc_client { int num_events; char **events; + /* For clients which subscribe to the tick event: whether the first tick + * event has been sent by i3. */ + bool first_tick_sent; + TAILQ_ENTRY(ipc_client) clients; } ipc_client; diff --git a/src/ipc.c b/src/ipc.c index 759665fe9..99b9e0ec6 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -1046,8 +1046,9 @@ static int add_subscription(void *extra, const unsigned char *s, memcpy(client->events[event], s, len); DLOG("client is now subscribed to:\n"); - for (int i = 0; i < client->num_events; i++) + for (int i = 0; i < client->num_events; i++) { DLOG("event %s\n", client->events[i]); + } DLOG("(done)\n"); return 1; @@ -1099,6 +1100,25 @@ IPC_HANDLER(subscribe) { yajl_free(p); const char *reply = "{\"success\":true}"; ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply); + + if (client->first_tick_sent) { + return; + } + + bool is_tick = false; + for (int i = 0; i < client->num_events; i++) { + if (strcmp(client->events[i], "tick") == 0) { + is_tick = true; + break; + } + } + if (!is_tick) { + return; + } + + client->first_tick_sent = true; + const char *payload = "{\"first\":true,\"payload\":\"\"}"; + ipc_send_message(client->fd, strlen(payload), I3_IPC_EVENT_TICK, (const uint8_t *)payload); } /* @@ -1122,9 +1142,35 @@ IPC_HANDLER(get_config) { y(free); } +/* + * Sends the tick event from the message payload to subscribers. Establishes a + * synchronization point in event-related tests. + */ +IPC_HANDLER(send_tick) { + yajl_gen gen = ygenalloc(); + + y(map_open); + + ystr("payload"); + yajl_gen_string(gen, (unsigned char *)message, message_size); + + y(map_close); + + const unsigned char *payload; + ylength length; + y(get_buf, &payload, &length); + + ipc_send_event("tick", I3_IPC_EVENT_TICK, (const char *)payload); + y(free); + + const char *reply = "{\"success\":true}"; + ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_TICK, (const uint8_t *)reply); + DLOG("Sent tick event\n"); +} + /* The index of each callback function corresponds to the numeric * value of the message type (see include/i3/ipc.h) */ -handler_t handlers[10] = { +handler_t handlers[11] = { handle_run_command, handle_get_workspaces, handle_subscribe, @@ -1135,6 +1181,7 @@ handler_t handlers[10] = { handle_get_version, handle_get_binding_modes, handle_get_config, + handle_send_tick, }; /* diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index a484c91a4..5b24ab391 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -47,6 +47,8 @@ our @EXPORT = qw( wait_for_unmap $x kill_all_windows + events_for + listen_for_binding ); =head1 NAME @@ -900,6 +902,86 @@ sub kill_all_windows { cmd '[title=".*"] kill'; } +=head2 events_for($subscribecb, [ $rettype ], [ $eventcbs ]) + +Helper function which returns an array containing all events of type $rettype +which were generated by i3 while $subscribecb was running. + +Set $eventcbs to subscribe to multiple event types and/or perform your own event +aggregation. + +=cut +sub events_for { + my ($subscribecb, $rettype, $eventcbs) = @_; + + my @events; + $eventcbs //= {}; + if (defined($rettype)) { + $eventcbs->{$rettype} = sub { push @events, shift }; + } + my $subscribed = AnyEvent->condvar; + my $flushed = AnyEvent->condvar; + $eventcbs->{tick} = sub { + my ($event) = @_; + if ($event->{first}) { + $subscribed->send($event); + } else { + $flushed->send($event); + } + }; + my $i3 = i3(get_socket_path(0)); + $i3->connect->recv; + $i3->subscribe($eventcbs)->recv; + $subscribed->recv; + # Subscription established, run the callback. + $subscribecb->(); + # Now generate a tick event, which we know we’ll receive (and at which point + # all other events have been received). + my $nonce = int(rand(255)) + 1; + $i3->send_tick($nonce); + + my $tick = $flushed->recv; + $tester->is_eq($tick->{payload}, $nonce, 'tick nonce received'); + return @events; +} + +=head2 listen_for_binding($cb) + +Helper function to evaluate whether sending KeyPress/KeyRelease events via XTEST +triggers an i3 key binding or not. Expects key bindings to be configured in the +form “bindsym nop ”, e.g. “bindsym Mod4+Return nop +Mod4+Return”. + + is(listen_for_binding( + sub { + xtest_key_press(133); # Super_L + xtest_key_press(36); # Return + xtest_key_release(36); # Return + xtest_key_release(133); # Super_L + xtest_sync_with_i3; + }, + ), + 'Mod4+Return', + 'triggered the "Mod4+Return" keybinding'); + +=cut + +sub listen_for_binding { + my ($cb) = @_; + my $triggered = AnyEvent->condvar; + my @events = events_for( + $cb, + 'binding'); + + $tester->is_eq(scalar @events, 1, 'Received precisely one event'); + $tester->is_eq($events[0]->{change}, 'run', 'change is "run"'); + # We look at the command (which is “nop ”) because that is easier + # than re-assembling the string representation of $event->{binding}. + my $command = $events[0]->{binding}->{command}; + $command =~ s/^nop //g; + return $command; +} + =head1 AUTHOR Michael Stapelberg diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm index 40759cfdb..5c1dfe13a 100644 --- a/testcases/lib/i3test/XTEST.pm +++ b/testcases/lib/i3test/XTEST.pm @@ -20,8 +20,6 @@ our @EXPORT = qw( xtest_key_release xtest_button_press xtest_button_release - listen_for_binding - start_binding_capture binding_events ); @@ -256,86 +254,6 @@ sub import { =cut -my $i3; -our @binding_events; - -=head2 start_binding_capture() - -Captures all binding events sent by i3 in the C<@binding_events> symbol, so -that you can verify the correct number of binding events was generated. - - my $pid = launch_with_config($config); - start_binding_capture; - # … - sync_with_i3; - is(scalar @i3test::XTEST::binding_events, 2, 'Received exactly 2 binding events'); - -=cut - -sub start_binding_capture { - # Store a copy of each binding event so that we can count the expected - # events in test cases. - $i3 = i3(get_socket_path()); - $i3->connect()->recv; - $i3->subscribe({ - binding => sub { - my ($event) = @_; - @binding_events = (@binding_events, $event); - }, - })->recv; -} - -=head2 listen_for_binding($cb) - -Helper function to evaluate whether sending KeyPress/KeyRelease events via -XTEST triggers an i3 key binding or not (with a timeout of 0.5s). Expects key -bindings to be configured in the form “bindsym nop ”, e.g. -“bindsym Mod4+Return nop Mod4+Return”. - - is(listen_for_binding( - sub { - xtest_key_press(133); # Super_L - xtest_key_press(36); # Return - xtest_key_release(36); # Return - xtest_key_release(133); # Super_L - }, - ), - 'Mod4+Return', - 'triggered the "Mod4+Return" keybinding'); - -=cut - -sub listen_for_binding { - my ($cb) = @_; - my $triggered = AnyEvent->condvar; - my $i3 = i3(get_socket_path()); - $i3->connect()->recv; - $i3->subscribe({ - binding => sub { - my ($event) = @_; - return unless $event->{change} eq 'run'; - # We look at the command (which is “nop ”) because that is - # easier than re-assembling the string representation of - # $event->{binding}. - $triggered->send($event->{binding}->{command}); - }, - })->recv; - - my $t; - $t = AnyEvent->timer( - after => 0.5, - cb => sub { - $triggered->send('timeout'); - } - ); - - $cb->(); - - my $recv = $triggered->recv; - $recv =~ s/^nop //g; - return $recv; -} - =head2 set_xkb_group($group) Changes the current XKB group from the default of 1 to C<$group>, which must be diff --git a/testcases/t/115-ipc-workspaces.t b/testcases/t/115-ipc-workspaces.t index 34ce07813..b0c4354ec 100644 --- a/testcases/t/115-ipc-workspaces.t +++ b/testcases/t/115-ipc-workspaces.t @@ -16,61 +16,25 @@ use i3test; -my $i3 = i3(get_socket_path()); -$i3->connect()->recv; - -################################ -# Workspaces requests and events -################################ - my $old_ws = get_ws(focused_ws()); -# Events - # We are switching to an empty workpspace from an empty workspace, so we expect # to receive "init", "focus", and "empty". -my $init = AnyEvent->condvar; -my $focus = AnyEvent->condvar; -my $empty = AnyEvent->condvar; -$i3->subscribe({ - workspace => sub { - my ($event) = @_; - if ($event->{change} eq 'init') { - $init->send($event); - } elsif ($event->{change} eq 'focus') { - $focus->send($event); - } elsif ($event->{change} eq 'empty') { - $empty->send($event); - } - } -})->recv; - -cmd 'workspace 2'; - -my $t; -$t = AnyEvent->timer( - after => 0.5, - cb => sub { - $init->send(0); - $focus->send(0); - $empty->send(0); - } -); - -my $init_event = $init->recv; -my $focus_event = $focus->recv; -my $empty_event = $empty->recv; +my @events = events_for( + sub { cmd 'workspace 2' }, + 'workspace'); my $current_ws = get_ws(focused_ws()); -ok($init_event, 'workspace "init" event received'); -is($init_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the initted workspace con'); +is(scalar @events, 3, 'Received 3 events'); +is($events[0]->{change}, 'init', 'First event has change = init'); +is($events[0]->{current}->{id}, $current_ws->{id}, 'the "current" property contains the initted workspace con'); -ok($focus_event, 'workspace "focus" event received'); -is($focus_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con'); -is($focus_event->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last'); +is($events[1]->{change}, 'focus', 'Second event has change = focus'); +is($events[1]->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con'); +is($events[1]->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last'); -ok($empty_event, 'workspace "empty" event received'); -is($empty_event->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con'); +is($events[2]->{change}, 'empty', 'Third event has change = empty'); +is($events[2]->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con'); done_testing; diff --git a/testcases/t/199-ipc-mode-event.t b/testcases/t/199-ipc-mode-event.t index 959ff6c47..0e4f89600 100644 --- a/testcases/t/199-ipc-mode-event.t +++ b/testcases/t/199-ipc-mode-event.t @@ -28,24 +28,11 @@ mode "with spaces" { } EOT -my $i3 = i3(get_socket_path(0)); -$i3->connect->recv; +my @events = events_for( + sub { cmd 'mode "m1"' }, + 'mode'); -my $cv = AnyEvent->condvar; - -$i3->subscribe({ - mode => sub { - my ($event) = @_; - $cv->send($event->{change} eq 'm1'); - } -})->recv; - -cmd 'mode "m1"'; - -# Timeout after 0.5s -my $t; -$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); }); - -ok($cv->recv, 'Mode event received'); +my @changes = map { $_->{change} } @events; +is_deeply(\@changes, [ 'm1' ], 'Mode event received'); done_testing; diff --git a/testcases/t/205-ipc-windows.t b/testcases/t/205-ipc-windows.t index ca7db1533..bafd155f6 100644 --- a/testcases/t/205-ipc-windows.t +++ b/testcases/t/205-ipc-windows.t @@ -16,46 +16,15 @@ use i3test; -SKIP: { - - skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15; - -my $i3 = i3(get_socket_path()); -$i3->connect()->recv; - -################################ -# Window event -################################ - -# Events - my $new = AnyEvent->condvar; my $focus = AnyEvent->condvar; -$i3->subscribe({ - window => sub { - my ($event) = @_; - if ($event->{change} eq 'new') { - $new->send($event); - } elsif ($event->{change} eq 'focus') { - $focus->send($event); - } - } -})->recv; - -open_window; - -my $t; -$t = AnyEvent->timer( - after => 0.5, - cb => sub { - $new->send(0); - $focus->send(0); - } -); -is($new->recv->{container}->{focused}, 0, 'Window "new" event received'); -is($focus->recv->{container}->{focused}, 1, 'Window "focus" event received'); +my @events = events_for( + sub { open_window }, + 'window'); -} +is(scalar @events, 2, 'Received 2 events'); +is($events[0]->{container}->{focused}, 0, 'Window "new" event received'); +is($events[1]->{container}->{focused}, 1, 'Window "focus" event received'); done_testing; diff --git a/testcases/t/219-ipc-window-focus.t b/testcases/t/219-ipc-window-focus.t index ae7781877..5baf68a80 100644 --- a/testcases/t/219-ipc-window-focus.t +++ b/testcases/t/219-ipc-window-focus.t @@ -16,13 +16,6 @@ use i3test; -SKIP: { - - skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15; - -my $i3 = i3(get_socket_path()); -$i3->connect()->recv; - ################################ # Window focus event ################################ @@ -33,62 +26,29 @@ my $win0 = open_window; my $win1 = open_window; my $win2 = open_window; -my $focus = AnyEvent->condvar; - -$i3->subscribe({ - window => sub { - my ($event) = @_; - $focus->send($event); - } -})->recv; - -my $t; -$t = AnyEvent->timer( - after => 0.5, - cb => sub { - $focus->send(0); - } -); - # ensure the rightmost window contains input focus -$i3->command('[id="' . $win2->id . '"] focus')->recv; +cmd '[id="' . $win2->id . '"] focus'; is($x->input_focus, $win2->id, "Window 2 focused"); -cmd 'focus left'; -my $event = $focus->recv; -is($event->{change}, 'focus', 'Focus event received'); -is($focus->recv->{container}->{name}, 'Window 1', 'Window 1 focused'); - -$focus = AnyEvent->condvar; -cmd 'focus left'; -$event = $focus->recv; -is($event->{change}, 'focus', 'Focus event received'); -is($event->{container}->{name}, 'Window 0', 'Window 0 focused'); - -$focus = AnyEvent->condvar; -cmd 'focus right'; -$event = $focus->recv; -is($event->{change}, 'focus', 'Focus event received'); -is($event->{container}->{name}, 'Window 1', 'Window 1 focused'); +sub focus_subtest { + my ($cmd, $name) = @_; -$focus = AnyEvent->condvar; -cmd 'focus right'; -$event = $focus->recv; -is($event->{change}, 'focus', 'Focus event received'); -is($event->{container}->{name}, 'Window 2', 'Window 2 focused'); + my $focus = AnyEvent->condvar; -$focus = AnyEvent->condvar; -cmd 'focus right'; -$event = $focus->recv; -is($event->{change}, 'focus', 'Focus event received'); -is($event->{container}->{name}, 'Window 0', 'Window 0 focused'); - -$focus = AnyEvent->condvar; -cmd 'focus left'; -$event = $focus->recv; -is($event->{change}, 'focus', 'Focus event received'); -is($event->{container}->{name}, 'Window 2', 'Window 2 focused'); + my @events = events_for( + sub { cmd $cmd }, + 'window'); + is(scalar @events, 1, 'Received 1 event'); + is($events[0]->{change}, 'focus', 'Focus event received'); + is($events[0]->{container}->{name}, $name, "$name focused"); } +subtest 'focus left (1)', \&focus_subtest, 'focus left', 'Window 1'; +subtest 'focus left (2)', \&focus_subtest, 'focus left', 'Window 0'; +subtest 'focus right (1)', \&focus_subtest, 'focus right', 'Window 1'; +subtest 'focus right (2)', \&focus_subtest, 'focus right', 'Window 2'; +subtest 'focus right (3)', \&focus_subtest, 'focus right', 'Window 0'; +subtest 'focus left', \&focus_subtest, 'focus left', 'Window 2'; + done_testing; diff --git a/testcases/t/220-ipc-window-title.t b/testcases/t/220-ipc-window-title.t index c751350a4..b5d14e237 100644 --- a/testcases/t/220-ipc-window-title.t +++ b/testcases/t/220-ipc-window-title.t @@ -16,42 +16,17 @@ use i3test; -SKIP: { - - skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15; - -my $i3 = i3(get_socket_path()); -$i3->connect()->recv; - -################################ -# Window title event -################################ - my $window = open_window(name => 'Window 0'); -my $title = AnyEvent->condvar; - -$i3->subscribe({ - window => sub { - my ($event) = @_; - $title->send($event); - } -})->recv; - -$window->name('New Window Title'); - -my $t; -$t = AnyEvent->timer( - after => 0.5, - cb => sub { - $title->send(0); - } -); - -my $event = $title->recv; -is($event->{change}, 'title', 'Window title change event received'); -is($event->{container}->{name}, 'New Window Title', 'Window title changed'); +my @events = events_for( + sub { + $window->name('New Window Title'); + sync_with_i3; + }, + 'window'); -} +is(scalar @events, 1, 'Received 1 event'); +is($events[0]->{change}, 'title', 'Window title change event received'); +is($events[0]->{container}->{name}, 'New Window Title', 'Window title changed'); done_testing; diff --git a/testcases/t/225-ipc-window-fullscreen.t b/testcases/t/225-ipc-window-fullscreen.t index aeabe9539..bc1505469 100644 --- a/testcases/t/225-ipc-window-fullscreen.t +++ b/testcases/t/225-ipc-window-fullscreen.t @@ -19,41 +19,19 @@ # Bug still in: 4.7.2-135-g7deb23c use i3test; -my $i3 = i3(get_socket_path()); -$i3->connect()->recv; +open_window; -my $cv; -my $t; +sub fullscreen_subtest { + my ($want) = @_; + my @events = events_for( + sub { cmd 'fullscreen' }, + 'window'); -sub reset_test { - $cv = AE::cv; - $t = AE::timer(0.5, 0, sub { $cv->send(0); }); + is(scalar @events, 1, 'Received 1 event'); + is($events[0]->{container}->{fullscreen_mode}, $want, "fullscreen_mode now $want"); } -reset_test; - -$i3->subscribe({ - window => sub { - my ($e) = @_; - if ($e->{change} eq 'fullscreen_mode') { - $cv->send($e->{container}); - } - }, - })->recv; - -my $window = open_window; - -cmd 'fullscreen'; -my $con = $cv->recv; - -ok($con, 'got fullscreen window event (on)'); -is($con->{fullscreen_mode}, 1, 'window is fullscreen'); - -reset_test; -cmd 'fullscreen'; -$con = $cv->recv; - -ok($con, 'got fullscreen window event (off)'); -is($con->{fullscreen_mode}, 0, 'window is not fullscreen'); +subtest 'fullscreen on', \&fullscreen_subtest, 1; +subtest 'fullscreen off', \&fullscreen_subtest, 0; done_testing; diff --git a/testcases/t/227-ipc-workspace-empty.t b/testcases/t/227-ipc-workspace-empty.t index fe8e03c02..b1f517ef5 100644 --- a/testcases/t/227-ipc-workspace-empty.t +++ b/testcases/t/227-ipc-workspace-empty.t @@ -18,12 +18,8 @@ # use i3test; -SKIP: { - - skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15; - ################################################################################ -# check that the workspace empty event is send upon workspace switch when the +# check that the workspace empty event is sent upon workspace switch when the # old workspace is empty ################################################################################ subtest 'Workspace empty event upon switch', sub { @@ -35,26 +31,17 @@ subtest 'Workspace empty event upon switch', sub { cmd '[id="' . $w1->id . '"] kill'; my $cond = AnyEvent->condvar; - my $client = i3(get_socket_path(0)); - $client->connect()->recv; - $client->subscribe({ - workspace => sub { - my ($event) = @_; - $cond->send($event); - } - })->recv; - - cmd "workspace $ws2"; - - sync_with_i3; + my @events = events_for( + sub { cmd "workspace $ws2" }, + 'workspace'); - my $event = $cond->recv; - is($event->{change}, 'empty', '"Empty" event received upon workspace switch'); - is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con'); + is(scalar @events, 2, 'Received 2 event'); + is($events[1]->{change}, 'empty', '"Empty" event received upon workspace switch'); + is($events[1]->{current}->{name}, $ws1, '"current" property should be set to the workspace con'); }; ################################################################################ -# check that no workspace empty event is send upon workspace switch if the +# check that no workspace empty event is sent upon workspace switch if the # workspace is not empty ################################################################################ subtest 'No workspace empty event', sub { @@ -63,36 +50,16 @@ subtest 'No workspace empty event', sub { my $ws1 = fresh_workspace; my $w1 = open_window(); - my @events; - my $cond = AnyEvent->condvar; - my $client = i3(get_socket_path(0)); - $client->connect()->recv; - $client->subscribe({ - workspace => sub { - my ($event) = @_; - push @events, $event; - } - })->recv; - - # Wait for the workspace event on a new connection. Events will be delivered - # to older connections earlier, so by the time it arrives here, it should be - # in @events already. - my $ws_event_block_conn = i3(get_socket_path(0)); - $ws_event_block_conn->connect()->recv; - $ws_event_block_conn->subscribe({ workspace => sub { $cond->send(1) }}); - - cmd "workspace $ws2"; + my @events = events_for( + sub { cmd "workspace $ws2" }, + 'workspace'); - sync_with_i3; - - my @expected_events = grep { $_->{change} eq 'focus' } @events; - my @empty_events = grep { $_->{change} eq 'empty' } @events; - is(@expected_events, 1, '"Focus" event received'); - is(@empty_events, 0, 'No "empty" events received'); + is(scalar @events, 1, 'Received 1 event'); + is($events[0]->{change}, 'focus', 'Event change is "focus"'); }; ################################################################################ -# check that workspace empty event is send when the last window has been closed +# check that workspace empty event is sent when the last window has been closed # on invisible workspace ################################################################################ subtest 'Workspace empty event upon window close', sub { @@ -101,25 +68,16 @@ subtest 'Workspace empty event upon window close', sub { my $ws2 = fresh_workspace; my $w2 = open_window(); - my $cond = AnyEvent->condvar; - my $client = i3(get_socket_path(0)); - $client->connect()->recv; - $client->subscribe({ - workspace => sub { - my ($event) = @_; - $cond->send($event); - } - })->recv; - - cmd '[id="' . $w1->id . '"] kill'; - - sync_with_i3; + my @events = events_for( + sub { + $w1->unmap; + sync_with_i3; + }, + 'workspace'); - my $event = $cond->recv; - is($event->{change}, 'empty', '"Empty" event received upon window close'); - is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con'); + is(scalar @events, 1, 'Received 1 event'); + is($events[0]->{change}, 'empty', '"Empty" event received upon window close'); + is($events[0]->{current}->{name}, $ws1, '"current" property should be set to the workspace con'); }; -} - done_testing; diff --git a/testcases/t/231-ipc-floating-event.t b/testcases/t/231-ipc-floating-event.t index e38a18760..96c94a497 100644 --- a/testcases/t/231-ipc-floating-event.t +++ b/testcases/t/231-ipc-floating-event.t @@ -19,41 +19,22 @@ # Bug still in: 4.8-7-gf4a8253 use i3test; -my $i3 = i3(get_socket_path()); -$i3->connect->recv; +sub floating_subtest { + my ($win, $cmd, $want) = @_; -my $cv = AnyEvent->condvar; + my @events = events_for( + sub { cmd $cmd }, + 'window'); -$i3->subscribe({ - window => sub { - my ($event) = @_; - $cv->send($event) if $event->{change} eq 'floating'; - } - })->recv; - -my $t; -$t = AnyEvent->timer( - after => 0.5, - cb => sub { - $cv->send(0); - } -); + my @floating = grep { $_->{change} eq 'floating' } @events; + is(scalar @floating, 1, 'Received 1 floating event'); + is($floating[0]->{container}->{window}, $win->{id}, "window id matches"); + is($floating[0]->{container}->{floating}, $want, "floating is $want"); +} my $win = open_window(); -cmd '[id="' . $win->{id} . '"] floating enable'; -my $e = $cv->recv; - -isnt($e, 0, 'floating a container should send an ipc window event'); -is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window'); -is($e->{container}->{floating}, 'user_on', 'the container should be floating'); - -$cv = AnyEvent->condvar; -cmd '[id="' . $win->{id} . '"] floating disable'; -$e = $cv->recv; - -isnt($e, 0, 'disabling floating on a container should send an ipc window event'); -is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window'); -is($e->{container}->{floating}, 'user_off', 'the container should not be floating'); +subtest 'floating enable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating enable', 'user_on'; +subtest 'floating disable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating disable', 'user_off'; done_testing; diff --git a/testcases/t/238-ipc-binding-event.t b/testcases/t/238-ipc-binding-event.t index af3f4d2fa..bec95a237 100644 --- a/testcases/t/238-ipc-binding-event.t +++ b/testcases/t/238-ipc-binding-event.t @@ -35,51 +35,38 @@ SKIP: { skip 'xdotool is required to test the binding event. `[apt-get install|pacman -S] xdotool`', 1 if $?; - skip "AnyEvent::I3 too old (need >= 0.16)", 1 if $AnyEvent::I3::VERSION < 0.16; - my $pid = launch_with_config($config); - my $i3 = i3(get_socket_path()); - $i3->connect->recv; - - my $cv = AE::cv; - my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; - - $i3->subscribe({ - binding => sub { - $cv->send(shift); - } - })->recv; - - qx(xdotool key $binding_symbol); - - my $e = $cv->recv; - - does_i3_live; + my $cv = AnyEvent->condvar; - diag "Event:\n", Dumper($e); + my @events = events_for( + sub { + # TODO: this is still flaky: we need to synchronize every X11 + # connection with i3. Move to XTEST and synchronize that connection. + qx(xdotool key $binding_symbol); + }, + 'binding'); - ok($e, - 'the binding event should emit when user input triggers an i3 binding event'); + is(scalar @events, 1, 'Received 1 event'); - is($e->{change}, 'run', + is($events[0]->{change}, 'run', 'the `change` field should indicate this binding has run'); - ok($e->{binding}, + ok($events[0]->{binding}, 'the `binding` field should be a hash that contains information about the binding'); - is($e->{binding}->{input_type}, 'keyboard', + is($events[0]->{binding}->{input_type}, 'keyboard', 'the input_type field should be the input type of the binding (keyboard or mouse)'); note 'the `mods` field should contain the symbols for the modifiers of the binding'; foreach (@mods) { - ok(grep(/$_/i, @{$e->{binding}->{mods}}), "`mods` contains the modifier $_"); + ok(grep(/$_/i, @{$events[0]->{binding}->{mods}}), "`mods` contains the modifier $_"); } - is($e->{binding}->{command}, $command, + is($events[0]->{binding}->{command}, $command, 'the `command` field should contain the command the binding ran'); - is($e->{binding}->{input_code}, 0, + is($events[0]->{binding}->{input_code}, 0, 'the input_code should be the specified code if the key was bound with bindcode, and otherwise zero'); exit_gracefully($pid); diff --git a/testcases/t/257-keypress-group1-fallback.t b/testcases/t/257-keypress-group1-fallback.t index e3874c92b..bc08aa2f2 100644 --- a/testcases/t/257-keypress-group1-fallback.t +++ b/testcases/t/257-keypress-group1-fallback.t @@ -36,8 +36,6 @@ SKIP: { skip "setxkbmap not found", 1 if system(q|setxkbmap -print >/dev/null|) != 0; -start_binding_capture; - system(q|setxkbmap us,ru -option grp:alt_shift_toggle|); is(listen_for_binding( @@ -87,9 +85,6 @@ is(listen_for_binding( 'Mod4+Return', 'triggered the "Mod4+Return" keybinding'); -sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events'); - # Disable the grp:alt_shift_toggle option, as we use Alt+Shift in other testcases. system(q|setxkbmap us -option|); diff --git a/testcases/t/258-keypress-release.t b/testcases/t/258-keypress-release.t index bf446003b..8bca0d869 100644 --- a/testcases/t/258-keypress-release.t +++ b/testcases/t/258-keypress-release.t @@ -37,8 +37,6 @@ SKIP: { skip "libxcb-xkb too old (need >= 1.11)", 1 unless ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11'); -start_binding_capture; - is(listen_for_binding( sub { xtest_key_press(107); # Print @@ -87,9 +85,6 @@ is(listen_for_binding( 'Mod1+Shift+b release', 'triggered the "Mod1+Shift+b" release keybinding'); -sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events'); - } done_testing; diff --git a/testcases/t/263-config-reload-reverts-bind-mode.t b/testcases/t/263-config-reload-reverts-bind-mode.t index ba95897c2..1416b6b9e 100644 --- a/testcases/t/263-config-reload-reverts-bind-mode.t +++ b/testcases/t/263-config-reload-reverts-bind-mode.t @@ -28,23 +28,11 @@ EOT cmd 'mode othermode'; -my $i3 = i3(get_socket_path(0)); -$i3->connect->recv; +my @events = events_for( + sub { cmd 'reload' }, + 'mode'); -my $cv = AnyEvent->condvar; -$i3->subscribe({ - mode => sub { - my ($event) = @_; - $cv->send($event->{change} eq 'default'); - } -})->recv; - -cmd 'reload'; - -# Timeout after 0.5s -my $t; -$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); }); - -ok($cv->recv, 'Mode event received'); +is(scalar @events, 1, 'Received 1 event'); +is($events[0]->{change}, 'default', 'change is "default"'); done_testing; diff --git a/testcases/t/265-ipc-mark.t b/testcases/t/265-ipc-mark.t index 06d8d83d1..a101944e7 100644 --- a/testcases/t/265-ipc-mark.t +++ b/testcases/t/265-ipc-mark.t @@ -18,27 +18,16 @@ # Ticket: #2501 use i3test; -my ($i3, $timer, $event, $mark); +sub mark_subtest { + my ($cmd) = @_; -$i3 = i3(get_socket_path()); -$i3->connect()->recv; + my @events = events_for( + sub { cmd $cmd }, + 'window'); -$i3->subscribe({ - window => sub { - my ($event) = @_; - return unless defined $mark; - return unless $event->{change} eq 'mark'; - - $mark->send($event); - } -})->recv; - -$timer = AnyEvent->timer( - after => 0.5, - cb => sub { - $mark->send(0); - } -); + my @mark = grep { $_->{change} eq 'mark' } @events; + is(scalar @mark, 1, 'Received 1 window::mark event'); +} ############################################################################### # Marking a container triggers a 'mark' event. @@ -46,11 +35,7 @@ $timer = AnyEvent->timer( fresh_workspace; open_window; -$mark = AnyEvent->condvar; -cmd 'mark x'; - -$event = $mark->recv; -ok($event, 'window::mark event has been received'); +subtest 'mark', \&mark_subtest, 'mark x'; ############################################################################### # Unmarking a container triggers a 'mark' event. @@ -59,11 +44,7 @@ fresh_workspace; open_window; cmd 'mark x'; -$mark = AnyEvent->condvar; -cmd 'unmark x'; - -$event = $mark->recv; -ok($event, 'window::mark event has been received'); +subtest 'unmark', \&mark_subtest, 'unmark x'; ############################################################################### diff --git a/testcases/t/275-ipc-window-close.t b/testcases/t/275-ipc-window-close.t index bf9d4f8bb..ccaf4440e 100644 --- a/testcases/t/275-ipc-window-close.t +++ b/testcases/t/275-ipc-window-close.t @@ -19,34 +19,17 @@ # Bug still in: 4.8-7-gf4a8253 use i3test; -my $i3 = i3(get_socket_path()); -$i3->connect()->recv; - -my $cv; -my $t; - -sub reset_test { - $cv = AE::cv; - $t = AE::timer(0.5, 0, sub { $cv->send(0); }); -} - -reset_test; - -$i3->subscribe({ - window => sub { - my ($e) = @_; - if ($e->{change} eq 'close') { - $cv->send($e->{container}); - } - }, - })->recv; - my $window = open_window; -cmd 'kill'; -my $con = $cv->recv; +my @events = events_for( + sub { + $window->unmap; + sync_with_i3; + }, + 'window'); -ok($con, 'closing a window should send the window::close event'); -is($con->{window}, $window->{id}, 'the event should contain information about the window'); +my @close = grep { $_->{change} eq 'close' } @events; +is(scalar @close, 1, 'Received 1 window::close event'); +is($close[0]->{container}->{window}, $window->{id}, 'the event should contain information about the window'); done_testing; diff --git a/testcases/t/276-ipc-window-move.t b/testcases/t/276-ipc-window-move.t index a3c825348..f3606b4e8 100644 --- a/testcases/t/276-ipc-window-move.t +++ b/testcases/t/276-ipc-window-move.t @@ -19,43 +19,22 @@ # Bug still in: 4.8-7-gf4a8253 use i3test; -my $i3 = i3(get_socket_path()); -$i3->connect()->recv; - -my $cv; -my $t; - -sub reset_test { - $cv = AE::cv; - $t = AE::timer(0.5, 0, sub { $cv->send(0); }); -} - -reset_test; - -$i3->subscribe({ - window => sub { - my ($e) = @_; - if ($e->{change} eq 'move') { - $cv->send($e->{container}); - } - }, - })->recv; - my $dummy_window = open_window; my $window = open_window; -cmd 'move right'; -my $con = $cv->recv; - -ok($con, 'moving a window should emit the window::move event'); -is($con->{window}, $window->{id}, 'the event should contain info about the window'); +sub move_subtest { + my ($cmd) = @_; + my $cv = AnyEvent->condvar; + my @events = events_for( + sub { cmd $cmd }, + 'window'); -reset_test; - -cmd 'move to workspace ws_new'; -$con = $cv->recv; + my @move = grep { $_->{change} eq 'move' } @events; + is(scalar @move, 1, 'Received 1 window::move event'); + is($move[0]->{container}->{window}, $window->{id}, 'window id matches'); +} -ok($con, 'moving a window to a different workspace should emit the window::move event'); -is($con->{window}, $window->{id}, 'the event should contain info about the window'); +subtest 'move right', \&move_subtest, 'move right'; +subtest 'move to workspace', \&move_subtest, 'move to workspace ws_new'; done_testing; diff --git a/testcases/t/277-ipc-window-urgent.t b/testcases/t/277-ipc-window-urgent.t index 2af29dac9..4eea2cdca 100644 --- a/testcases/t/277-ipc-window-urgent.t +++ b/testcases/t/277-ipc-window-urgent.t @@ -19,50 +19,37 @@ # use i3test; -my $config = <connect()->recv; - -my $cv; -$i3->subscribe({ - window => sub { - my ($event) = @_; - $cv->send($event) if $event->{change} eq 'urgent'; - } -})->recv; - -my $t; -$t = AnyEvent->timer( - after => 0.5, - cb => sub { - $cv->send(0); - } -); - -$cv = AnyEvent->condvar; fresh_workspace; my $win = open_window; my $dummy_win = open_window; -$win->add_hint('urgency'); -my $event = $cv->recv; - -isnt($event, 0, 'an urgent con should emit the window::urgent event'); -is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window'); -is($event->{container}->{urgent}, 1, 'the container should be urgent'); - -$cv = AnyEvent->condvar; -$win->delete_hint('urgency'); -$event = $cv->recv; - -isnt($event, 0, 'an urgent con should emit the window::urgent event'); -is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window'); -is($event->{container}->{urgent}, 0, 'the container should not be urgent'); +sub urgency_subtest { + my ($subscribecb, $win, $want) = @_; + + my @events = events_for( + $subscribecb, + 'window'); + + my @urgent = grep { $_->{change} eq 'urgent' } @events; + is(scalar @urgent, 1, 'Received 1 window::urgent event'); + is($urgent[0]->{container}->{window}, $win->{id}, "window id matches"); + is($urgent[0]->{container}->{urgent}, $want, "urgent is $want"); +} + +subtest "urgency set", \&urgency_subtest, + sub { + $win->add_hint('urgency'); + sync_with_i3; + }, + $win, + 1; + +subtest "urgency unset", \&urgency_subtest, + sub { + $win->delete_hint('urgency'); + sync_with_i3; + }, + $win, + 0; done_testing; diff --git a/testcases/t/289-ipc-shutdown-event.t b/testcases/t/289-ipc-shutdown-event.t index 0cf347aa8..f2b524601 100644 --- a/testcases/t/289-ipc-shutdown-event.t +++ b/testcases/t/289-ipc-shutdown-event.t @@ -23,13 +23,13 @@ # Bug still in: 4.12-46-g2123888 use i3test; -SKIP: { - skip "AnyEvent::I3 too old (need >= 0.17)", 1 if $AnyEvent::I3::VERSION < 0.17; +# We cannot use events_for in this test as we cannot send events after +# issuing the restart/shutdown command. my $i3 = i3(get_socket_path()); $i3->connect->recv; -my $cv = AE::cv; +my $cv = AnyEvent->condvar; my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; $i3->subscribe({ @@ -50,7 +50,7 @@ is($e->{change}, 'restart', 'the `change` field should tell the reason for the s $i3 = i3(get_socket_path()); $i3->connect->recv; -$cv = AE::cv; +$cv = AnyEvent->condvar; $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; $i3->subscribe({ @@ -66,6 +66,5 @@ $e = $cv->recv; diag "Event:\n", Dumper($e); ok($e, 'the shutdown event should emit when the ipc is exited by command'); is($e->{change}, 'exit', 'the `change` field should tell the reason for the shutdown'); -} done_testing; diff --git a/testcases/t/290-keypress-numlock.t b/testcases/t/290-keypress-numlock.t index b896e4320..94a5747d0 100644 --- a/testcases/t/290-keypress-numlock.t +++ b/testcases/t/290-keypress-numlock.t @@ -51,8 +51,6 @@ EOT my $pid = launch_with_config($config); -start_binding_capture; - is(listen_for_binding( sub { xtest_key_press(87); # KP_End @@ -213,9 +211,6 @@ is(listen_for_binding( 's', 'triggered the "s" keybinding with Num_Lock'); -sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 12, 'Received exactly 12 binding events'); - exit_gracefully($pid); ################################################################################ @@ -234,8 +229,6 @@ EOT $pid = launch_with_config($config); -start_binding_capture; - is(listen_for_binding( sub { xtest_key_press(133); # Super_L @@ -288,9 +281,6 @@ is(listen_for_binding( 'Return', 'triggered the "Return" keybinding with Num_Lock'); -sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 16, 'Received exactly 16 binding events'); - exit_gracefully($pid); ################################################################################ @@ -307,8 +297,6 @@ EOT $pid = launch_with_config($config); -start_binding_capture; - is(listen_for_binding( sub { xtest_key_press(87); # KP_End @@ -329,7 +317,7 @@ is(listen_for_binding( 'KP_Down', 'triggered the "KP_Down" keybinding'); -is(listen_for_binding( +my @unexpected = events_for( sub { xtest_key_press(77); # enable Num_Lock xtest_key_release(77); # enable Num_Lock @@ -339,11 +327,10 @@ is(listen_for_binding( xtest_key_release(77); # disable Num_Lock xtest_sync_with_i3; }, - ), - 'timeout', - 'Did not trigger the KP_End keybinding with KP_1'); + 'binding'); +is(scalar @unexpected, 0, 'Did not trigger the KP_End keybinding with KP_1'); -is(listen_for_binding( +my @unexpected2 = events_for( sub { xtest_key_press(77); # enable Num_Lock xtest_key_release(77); # enable Num_Lock @@ -353,14 +340,11 @@ is(listen_for_binding( xtest_key_release(77); # disable Num_Lock xtest_sync_with_i3; }, - ), - 'timeout', - 'Did not trigger the KP_Down keybinding with KP_2'); + 'binding'); -# TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2. +is(scalar @unexpected2, 0, 'Did not trigger the KP_Down keybinding with KP_2'); -sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 18, 'Received exactly 18 binding events'); +# TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2. exit_gracefully($pid); @@ -379,8 +363,6 @@ $pid = launch_with_config($config); my $win = open_window; -start_binding_capture; - is(listen_for_binding( sub { xtest_key_press(77); # enable Num_Lock diff --git a/testcases/t/514-ipc-workspace-multi-monitor.t b/testcases/t/514-ipc-workspace-multi-monitor.t index e3753bec6..ac918fe37 100644 --- a/testcases/t/514-ipc-workspace-multi-monitor.t +++ b/testcases/t/514-ipc-workspace-multi-monitor.t @@ -13,7 +13,7 @@ # # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf # (unless you are already familiar with Perl) -# +# # Ticket: #990 # Bug still in: 4.5.1-23-g82b5978 @@ -23,46 +23,17 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 fake-outputs 1024x768+0+0,1024x768+1024+0 EOT -my $i3 = i3(get_socket_path()); - -$i3->connect()->recv; - -################################ -# Workspaces requests and events -################################ - my $old_ws = get_ws(focused_ws); -# Events - -# We are switching to an empty workpspace on the output to the right from an empty workspace on the output on the left, so we expect -# to receive "init", "focus", and "empty". my $focus = AnyEvent->condvar; -$i3->subscribe({ - workspace => sub { - my ($event) = @_; - if ($event->{change} eq 'focus') { - $focus->send($event); - } - } -})->recv; - -my $t; -$t = AnyEvent->timer( - after => 0.5, - cb => sub { - $focus->send(0); - } -); - -cmd 'focus output right'; - -my $event = $focus->recv; +my @events = events_for( + sub { cmd 'focus output right' }, + 'workspace'); my $current_ws = get_ws(focused_ws); -ok($event, 'Workspace "focus" event received'); -is($event->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace'); -is($event->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace'); +is(scalar @events, 1, 'Received 1 event'); +is($events[0]->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace'); +is($events[0]->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace'); done_testing; diff --git a/testcases/t/517-regress-move-direction-ipc.t b/testcases/t/517-regress-move-direction-ipc.t index 217d898ca..2f7f2b27f 100644 --- a/testcases/t/517-regress-move-direction-ipc.t +++ b/testcases/t/517-regress-move-direction-ipc.t @@ -27,51 +27,29 @@ workspace ws-left output fake-0 workspace ws-right output fake-1 EOT -my $i3 = i3(get_socket_path()); -$i3->connect()->recv; - -# subscribe to the 'focus' ipc event -my $focus = AnyEvent->condvar; -$i3->subscribe({ - workspace => sub { - my ($event) = @_; - if ($event->{change} eq 'focus') { - $focus->send($event); - } - } -})->recv; - -# give up after 0.5 seconds -my $timer = AnyEvent->timer( - after => 0.5, - cb => sub { - $focus->send(0); - } -); - # open two windows on the left output cmd 'workspace ws-left'; open_window; open_window; -# move a window over to the right output -cmd 'move right'; -my $event = $focus->recv; +sub focus_subtest { + my ($cmd, $want) = @_; -ok($event, 'moving from workspace with two windows triggered focus ipc event'); -is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace'); -is(@{$event->{current}->{nodes}}, 1, 'focus event gave the right number of windows on the workspace'); + my @events = events_for( + sub { cmd $cmd }, + 'workspace'); -# reset and try again -$focus = AnyEvent->condvar; -cmd 'workspace ws-left'; -$focus->recv; + my @focus = grep { $_->{change} eq 'focus' } @events; + is(scalar @focus, 1, 'Received 1 workspace::focus event'); + is($focus[0]->{current}->{name}, 'ws-right', 'focus event gave the right workspace'); + is(@{$focus[0]->{current}->{nodes}}, $want, 'focus event gave the right number of windows on the workspace'); +} + +# move a window over to the right output +subtest 'move right (1)', \&focus_subtest, 'move right', 1; -$focus = AnyEvent->condvar; -cmd 'move right'; -$event = $focus->recv; -ok($event, 'moving from workspace with one window triggered focus ipc event'); -is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace'); -is(@{$event->{current}->{nodes}}, 2, 'focus event gave the right number of windows on the workspace'); +# move another window +cmd 'workspace ws-left'; +subtest 'move right (2)', \&focus_subtest, 'move right', 2; done_testing; diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t index ff34328c6..1dfa6aa71 100644 --- a/testcases/t/525-i3bar-mouse-bindings.t +++ b/testcases/t/525-i3bar-mouse-bindings.t @@ -34,17 +34,12 @@ bar { EOT use i3test::XTEST; -my ($cv, $timer); -sub reset_test { - $cv = AE::cv; - $timer = AE::timer(1, 0, sub { $cv->send(0); }); -} - my $i3 = i3(get_socket_path()); $i3->connect()->recv; my $ws = fresh_workspace; -reset_test; +my $cv = AnyEvent->condvar; +my $timer = AnyEvent->timer(1, 0, sub { $cv->send(0) }); $i3->subscribe({ window => sub { my ($event) = @_; @@ -60,8 +55,6 @@ $i3->subscribe({ }, })->recv; -my $con; - sub i3bar_present { my ($nodes) = @_; @@ -83,50 +76,68 @@ sub i3bar_present { if (i3bar_present($i3->get_tree->recv->{nodes})) { ok(1, 'i3bar present'); } else { - $con = $cv->recv; + my $con = $cv->recv; ok($con, 'i3bar appeared'); } my $left = open_window; my $right = open_window; sync_with_i3; -$con = $cv->recv; +my $con = $cv->recv; is($con->{window}, $right->{id}, 'focus is initially on the right container'); -reset_test; - -xtest_button_press(1, 3, 3); -xtest_button_release(1, 3, 3); -sync_with_i3; -$con = $cv->recv; -is($con->{window}, $left->{id}, 'button 1 moves focus left'); -reset_test; - -xtest_button_press(2, 3, 3); -xtest_button_release(2, 3, 3); -sync_with_i3; -$con = $cv->recv; -is($con->{window}, $right->{id}, 'button 2 moves focus right'); -reset_test; - -xtest_button_press(3, 3, 3); -xtest_button_release(3, 3, 3); -sync_with_i3; -$con = $cv->recv; -is($con->{window}, $left->{id}, 'button 3 moves focus left'); -reset_test; -xtest_button_press(4, 3, 3); -xtest_button_release(4, 3, 3); -sync_with_i3; -$con = $cv->recv; -is($con->{window}, $right->{id}, 'button 4 moves focus right'); -reset_test; +sub focus_subtest { + my ($subscribecb, $want, $msg) = @_; + my @events = events_for( + $subscribecb, + 'window'); + my @focus = map { $_->{container}->{window} } grep { $_->{change} eq 'focus' } @events; + is_deeply(\@focus, $want, $msg); +} -xtest_button_press(5, 3, 3); -xtest_button_release(5, 3, 3); -sync_with_i3; -$con = $cv->recv; -is($con->{window}, $left->{id}, 'button 5 moves focus left'); -reset_test; +subtest 'button 1 moves focus left', \&focus_subtest, + sub { + xtest_button_press(1, 3, 3); + xtest_button_release(1, 3, 3); + xtest_sync_with_i3; + }, + [ $left->{id} ], + 'button 1 moves focus left'; + +subtest 'button 2 moves focus right', \&focus_subtest, + sub { + xtest_button_press(2, 3, 3); + xtest_button_release(2, 3, 3); + xtest_sync_with_i3; + }, + [ $right->{id} ], + 'button 2 moves focus right'; + +subtest 'button 3 moves focus left', \&focus_subtest, + sub { + xtest_button_press(3, 3, 3); + xtest_button_release(3, 3, 3); + xtest_sync_with_i3; + }, + [ $left->{id} ], + 'button 3 moves focus left'; + +subtest 'button 4 moves focus right', \&focus_subtest, + sub { + xtest_button_press(4, 3, 3); + xtest_button_release(4, 3, 3); + xtest_sync_with_i3; + }, + [ $right->{id} ], + 'button 4 moves focus right'; + +subtest 'button 5 moves focus left', \&focus_subtest, + sub { + xtest_button_press(5, 3, 3); + xtest_button_release(5, 3, 3); + xtest_sync_with_i3; + }, + [ $left->{id} ], + 'button 5 moves focus left'; done_testing; From 2bff4f1dbaf0e8187fefe6defd02b03bba0c2a26 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 30 Sep 2017 13:04:20 +0200 Subject: [PATCH 14/99] Synchronize with i3bar+i3, not just i3. --- i3bar/include/xcb_atoms.def | 1 + i3bar/src/xcb.c | 22 ++++++++++++++++++++-- testcases/lib/i3test/XTEST.pm | 14 ++++++++++++-- testcases/t/525-i3bar-mouse-bindings.t | 18 +++++++++++------- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/i3bar/include/xcb_atoms.def b/i3bar/include/xcb_atoms.def index 65a147c47..453dd0ceb 100644 --- a/i3bar/include/xcb_atoms.def +++ b/i3bar/include/xcb_atoms.def @@ -9,4 +9,5 @@ ATOM_DO(_NET_SYSTEM_TRAY_OPCODE) ATOM_DO(_NET_SYSTEM_TRAY_COLORS) ATOM_DO(_XEMBED_INFO) ATOM_DO(_XEMBED) +ATOM_DO(I3_SYNC) #undef ATOM_DO diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index fed969df7..dfa66ee2d 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -678,8 +678,26 @@ static void configure_trayclients(void) { * */ static void handle_client_message(xcb_client_message_event_t *event) { - if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] && - event->format == 32) { + if (event->type == atoms[I3_SYNC]) { + xcb_window_t window = event->data.data32[0]; + uint32_t rnd = event->data.data32[1]; + DLOG("[i3 sync protocol] Forwarding random value %d, X11 window 0x%08x to i3\n", rnd, window); + + void *reply = scalloc(32, 1); + xcb_client_message_event_t *ev = reply; + + ev->response_type = XCB_CLIENT_MESSAGE; + ev->window = window; + ev->type = atoms[I3_SYNC]; + ev->format = 32; + ev->data.data32[0] = window; + ev->data.data32[1] = rnd; + + xcb_send_event(conn, false, xcb_root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)ev); + xcb_flush(conn); + free(reply); + } else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] && + event->format == 32) { DLOG("_NET_SYSTEM_TRAY_OPCODE received\n"); /* event->data.data32[0] is the timestamp */ uint32_t op = event->data.data32[1]; diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm index 5c1dfe13a..4c464c5e3 100644 --- a/testcases/lib/i3test/XTEST.pm +++ b/testcases/lib/i3test/XTEST.pm @@ -14,6 +14,7 @@ use ExtUtils::PkgConfig; use Exporter (); our @EXPORT = qw( inlinec_connect + xtest_sync_with xtest_sync_with_i3 set_xkb_group xtest_key_press @@ -118,7 +119,7 @@ bool inlinec_connect() { return true; } -void xtest_sync_with_i3() { +void xtest_sync_with(int window) { xcb_client_message_event_t ev; memset(&ev, '\0', sizeof(xcb_client_message_event_t)); @@ -131,7 +132,7 @@ void xtest_sync_with_i3() { ev.data.data32[0] = sync_window; ev.data.data32[1] = nonce; - xcb_send_event(conn, false, root_window, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev); + xcb_send_event(conn, false, (xcb_window_t)window, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev); xcb_flush(conn); xcb_generic_event_t *event = NULL; @@ -176,6 +177,10 @@ void xtest_sync_with_i3() { free(event); } +void xtest_sync_with_i3() { + xtest_sync_with((int)root_window); +} + // NOTE: while |group| should be a uint8_t, Inline::C will not define the // function unless we use an int. bool set_xkb_group(int group) { @@ -287,6 +292,11 @@ Sends a ButtonRelease event via XTEST, with the specified C<$button>. Returns false when there was an X11 error, true otherwise. +=head2 xtest_sync_with($window) + +Ensures the specified window has processed all X11 events which were triggered +by this module, provided the window response to the i3 sync protocol. + =head2 xtest_sync_with_i3() Ensures i3 has processed all X11 events which were triggered by this module. diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t index 1dfa6aa71..d3216d974 100644 --- a/testcases/t/525-i3bar-mouse-bindings.t +++ b/testcases/t/525-i3bar-mouse-bindings.t @@ -61,7 +61,7 @@ sub i3bar_present { for my $node (@{$nodes}) { my $props = $node->{window_properties}; if (defined($props) && $props->{class} eq 'i3bar') { - return 1; + return $node->{window}; } } @@ -73,13 +73,17 @@ sub i3bar_present { return i3bar_present(\@children); } -if (i3bar_present($i3->get_tree->recv->{nodes})) { +my $i3bar_window = i3bar_present($i3->get_tree->recv->{nodes}); +if ($i3bar_window) { ok(1, 'i3bar present'); } else { my $con = $cv->recv; ok($con, 'i3bar appeared'); + $i3bar_window = $con->{window}; } +diag('i3bar window = ' . $i3bar_window); + my $left = open_window; my $right = open_window; sync_with_i3; @@ -99,7 +103,7 @@ subtest 'button 1 moves focus left', \&focus_subtest, sub { xtest_button_press(1, 3, 3); xtest_button_release(1, 3, 3); - xtest_sync_with_i3; + xtest_sync_with($i3bar_window); }, [ $left->{id} ], 'button 1 moves focus left'; @@ -108,7 +112,7 @@ subtest 'button 2 moves focus right', \&focus_subtest, sub { xtest_button_press(2, 3, 3); xtest_button_release(2, 3, 3); - xtest_sync_with_i3; + xtest_sync_with($i3bar_window); }, [ $right->{id} ], 'button 2 moves focus right'; @@ -117,7 +121,7 @@ subtest 'button 3 moves focus left', \&focus_subtest, sub { xtest_button_press(3, 3, 3); xtest_button_release(3, 3, 3); - xtest_sync_with_i3; + xtest_sync_with($i3bar_window); }, [ $left->{id} ], 'button 3 moves focus left'; @@ -126,7 +130,7 @@ subtest 'button 4 moves focus right', \&focus_subtest, sub { xtest_button_press(4, 3, 3); xtest_button_release(4, 3, 3); - xtest_sync_with_i3; + xtest_sync_with($i3bar_window); }, [ $right->{id} ], 'button 4 moves focus right'; @@ -135,7 +139,7 @@ subtest 'button 5 moves focus left', \&focus_subtest, sub { xtest_button_press(5, 3, 3); xtest_button_release(5, 3, 3); - xtest_sync_with_i3; + xtest_sync_with($i3bar_window); }, [ $left->{id} ], 'button 5 moves focus left'; From 962750eb64c7f968a03c4f57541d864f2418fcaf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 30 Sep 2017 10:15:58 -0700 Subject: [PATCH 15/99] Fix memory leak when config conversion fails (#3006) This happens on an empty config file, for example. --- src/config_parser.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config_parser.c b/src/config_parser.c index 58a5552ce..9d8369c80 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -764,6 +764,7 @@ static char *migrate_config(char *input, off_t size) { wait(&status); if (!WIFEXITED(status)) { fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n"); + FREE(converted); return NULL; } @@ -778,6 +779,7 @@ static char *migrate_config(char *input, off_t size) { fprintf(stderr, "# i3 config file (v4)\n"); /* TODO: nag the user with a message to include a hint for i3 in their config file */ } + FREE(converted); return NULL; } From 21cdcdb07c5b141471993f212809813cf9829d72 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 30 Sep 2017 10:16:21 -0700 Subject: [PATCH 16/99] Fix compilation warnings on all Debian architectures. (#3007) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit stbuf.st_size is of type off_t, which the standard defines as “extended signed integral type”¹, and for which there is no correct printf format string. Hence, we need to cast it into a hopefully-large-enough type (ugh) and use the corresponding format string. In our case, int64_t should do it, as config files really shouldn’t be anywhere close to those numbers. ① http://pubs.opengroup.org/onlinepubs/007908799/xsh/systypes.h.html --- include/all.h | 1 + src/config_parser.c | 4 +++- src/util.c | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/all.h b/include/all.h index c26835b90..ecc875d08 100644 --- a/include/all.h +++ b/include/all.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include diff --git a/src/config_parser.c b/src/config_parser.c index 9d8369c80..4e66c9110 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -902,7 +902,9 @@ bool parse_file(const char *f, bool use_nagbar) { FREE(current_config); current_config = scalloc(stbuf.st_size + 1, 1); - fread(current_config, 1, stbuf.st_size, fstr); + if ((ssize_t)fread(current_config, 1, stbuf.st_size, fstr) != stbuf.st_size) { + die("Could not fread: %s\n", strerror(errno)); + } rewind(fstr); bool invalid_sets = false; diff --git a/src/util.c b/src/util.c index ba0969c72..dc3444f7b 100644 --- a/src/util.c +++ b/src/util.c @@ -500,7 +500,7 @@ ssize_t slurp(const char *path, char **buf) { size_t n = fread(*buf, 1, stbuf.st_size, f); fclose(f); if ((ssize_t)n != stbuf.st_size) { - ELOG("File \"%s\" could not be read entirely: got %zd, want %zd\n", path, n, stbuf.st_size); + ELOG("File \"%s\" could not be read entirely: got %zd, want %" PRIi64 "\n", path, n, (int64_t)stbuf.st_size); free(*buf); *buf = NULL; return -1; From 464c158d9a189339b1a384fa41553e4bb8630d8a Mon Sep 17 00:00:00 2001 From: Orestis Date: Sat, 30 Sep 2017 20:17:20 +0300 Subject: [PATCH 17/99] dump-asy.pl: use layout instead of orientation for names (#3004) --- contrib/dump-asy.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl index 478a896a5..4fcf9b77b 100755 --- a/contrib/dump-asy.pl +++ b/contrib/dump-asy.pl @@ -39,7 +39,7 @@ sub dump_node { $na =~ s/~/\\textasciitilde{}/g; my $type = 'leaf'; if (!defined($n->{window})) { - $type = $n->{orientation} . '-split'; + $type = $n->{layout}; } my $name = qq|``$na'' ($type)|; From fbf3f7da36a7d859bf5f4113953129ae47378e66 Mon Sep 17 00:00:00 2001 From: Tamir Zahavi-Brunner Date: Tue, 3 Oct 2017 01:21:39 +0300 Subject: [PATCH 18/99] Fix gaps size for HiDPI displays. --- src/commands.c | 2 +- src/config_directives.c | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/commands.c b/src/commands.c index 39d428809..c1793c4ad 100644 --- a/src/commands.c +++ b/src/commands.c @@ -2167,7 +2167,7 @@ void cmd_debuglog(I3_CMD, const char *argument) { */ void cmd_gaps(I3_CMD, const char *type, const char *scope, const char *mode, const char *value) { #define CMD_GAPS(type, other) \ - int pixels = atoi(value); \ + int pixels = logical_px(atoi(value)); \ Con *workspace = con_get_workspace(focused); \ \ int current_value = config.gaps.type; \ diff --git a/src/config_directives.c b/src/config_directives.c index 609b6670c..43ca030bb 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -191,16 +191,17 @@ void create_gaps_assignment(const char *workspace, bool inner, const long value) } CFGFUN(gaps, const char *workspace, const char *scope, const long value) { + int pixels = logical_px(value); if (!strcmp(scope, "inner")) { if (workspace == NULL) - config.gaps.inner = value; + config.gaps.inner = pixels; else - create_gaps_assignment(workspace, true, value - config.gaps.inner); + create_gaps_assignment(workspace, true, pixels - config.gaps.inner); } else if (!strcmp(scope, "outer")) { if (workspace == NULL) - config.gaps.outer = value; + config.gaps.outer = pixels; else - create_gaps_assignment(workspace, false, value - config.gaps.outer); + create_gaps_assignment(workspace, false, pixels - config.gaps.outer); } else { ELOG("Invalid command, cannot process scope %s", scope); } From 28ca1e85299aa99e243d27f62d18be3fbfc47d64 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 5 Oct 2017 13:03:33 -0700 Subject: [PATCH 19/99] Fix AnyEvent->timer call (#3008) And make the remaining AnyEvent->condvar and AnyEvent->timer calls consistent. --- testcases/t/268-ipc-config.t | 4 ++-- testcases/t/289-ipc-shutdown-event.t | 4 ++-- testcases/t/525-i3bar-mouse-bindings.t | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/testcases/t/268-ipc-config.t b/testcases/t/268-ipc-config.t index b21ff1b2a..9ac749b63 100644 --- a/testcases/t/268-ipc-config.t +++ b/testcases/t/268-ipc-config.t @@ -42,8 +42,8 @@ get_socket_path(0); my $i3 = i3(get_socket_path()); $i3->connect->recv; -my $cv = AE::cv; -my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; +my $cv = AnyEvent->condvar; +my $timer = AnyEvent->timer(after => 0.5, interval => 0, cb => sub { $cv->send(0); }); my $last_config = $i3->get_config()->recv; chomp($last_config->{config}); diff --git a/testcases/t/289-ipc-shutdown-event.t b/testcases/t/289-ipc-shutdown-event.t index f2b524601..606474e24 100644 --- a/testcases/t/289-ipc-shutdown-event.t +++ b/testcases/t/289-ipc-shutdown-event.t @@ -30,7 +30,7 @@ my $i3 = i3(get_socket_path()); $i3->connect->recv; my $cv = AnyEvent->condvar; -my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; +my $timer = AnyEvent->timer(after => 0.5, interval => 0, cb => sub { $cv->send(0); }); $i3->subscribe({ shutdown => sub { @@ -51,7 +51,7 @@ $i3 = i3(get_socket_path()); $i3->connect->recv; $cv = AnyEvent->condvar; -$timer = AE::timer 0.5, 0, sub { $cv->send(0); }; +$timer = AnyEvent->timer(after => 0.5, interval => 0, cb => sub { $cv->send(0); }); $i3->subscribe({ shutdown => sub { diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t index d3216d974..e67f0df99 100644 --- a/testcases/t/525-i3bar-mouse-bindings.t +++ b/testcases/t/525-i3bar-mouse-bindings.t @@ -39,7 +39,7 @@ $i3->connect()->recv; my $ws = fresh_workspace; my $cv = AnyEvent->condvar; -my $timer = AnyEvent->timer(1, 0, sub { $cv->send(0) }); +my $timer = AnyEvent->timer(after => 1, interval => 0, cb => sub { $cv->send(0) }); $i3->subscribe({ window => sub { my ($event) = @_; From 44a6efb5b0160718f66fe39172299a523d55cedb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 Oct 2017 00:18:49 -0700 Subject: [PATCH 20/99] tests: run under Xvfb by default (if available) (#2951) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This shaves off two seconds of wall-clock time (10s → 8s). --- docs/testsuite | 6 ++--- testcases/complete-run.pl.in | 44 +++++++++++++++++++++++++++++++++++- travis/run-tests.sh | 4 ++-- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/docs/testsuite b/docs/testsuite index bf85cb1fb..b535e7c14 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -113,10 +113,8 @@ containing the appropriate i3 logfile for each testcase. The latest folder can always be found under the symlink +latest/+. Unless told differently, it will run the tests on a separate X server instance (using Xephyr). -Xephyr will open a window where you can inspect the running test. You can run -the tests without an X session with Xvfb, such as with +xvfb-run -./complete-run+. This will also speed up the tests significantly especially on -machines without a powerful video card. +Xephyr will open a window where you can inspect the running test. By default, +tests are run under Xvfb. .Example invocation of +complete-run.pl+ --------------------------------------- diff --git a/testcases/complete-run.pl.in b/testcases/complete-run.pl.in index ddd6ccadf..b7b398720 100755 --- a/testcases/complete-run.pl.in +++ b/testcases/complete-run.pl.in @@ -38,6 +38,8 @@ binmode STDERR, ':utf8'; # subshell or situations like that. AnyEvent::Util::close_all_fds_except(0, 1, 2); +our @CLEANUP; + # convenience wrapper to write to the log file my $log; sub Log { say $log "@_" } @@ -55,6 +57,7 @@ my %options = ( xtrace => 0, coverage => 0, restart => 0, + xvfb => 1, ); my $keep_xserver_output = 0; @@ -64,6 +67,7 @@ my $result = GetOptions( "valgrind" => \$options{valgrind}, "strace" => \$options{strace}, "xtrace" => \$options{xtrace}, + "xvfb" => \$options{xvfb}, "display=s" => \@displays, "parallel=i" => \$parallel, "help|?" => \$help, @@ -112,6 +116,44 @@ $ENV{PATH} = join(':', qx(Xephyr -help 2>&1); die "Xephyr was not found in your path. Please install Xephyr (xserver-xephyr on Debian)." if $?; +qx(xvfb-run --help 2>&1); +if ($? && $options{xvfb}) { + say "xvfb-run not found, not running tests under xvfb. Install the xvfb package to speed up tests"; + $options{xvfb} = 0; +} + +if ($options{xvfb}) { + for (my $n = 99; $n < 120; $n++) { + my $path = File::Temp::tmpnam($ENV{TMPDIR} // "/tmp", "i3-testsXXXXXX"); + if (!defined(POSIX::mkfifo($path, 0600))) { + die "mkfifo: $!"; + } + my $pid = fork // die "fork: $!"; + if ($pid == 0) { + # Child + + # Xvfb checks whether the parent ignores USR1 and sends USR1 to the + # parent when ready, so that the wait call will be interrupted. We + # can’t implement this in Perl, as Perl’s waitpid transparently + # handles -EINTR. + exec('/bin/sh', '-c', qq|trap "exit" INT; trap : USR1; (trap '' USR1; exec Xvfb :$n -screen 0 640x480x8 -nolisten tcp) & PID=\$!; wait; if ! kill -0 \$PID 2>/dev/null; then echo 1:\$PID > $path; else echo 0:\$PID > $path; wait \$PID; fi|); + die "exec: $!"; + } + chomp(my $kill = slurp($path)); + unlink($path); + my ($code, $xvfbpid) = ($kill =~ m,^([0-1]):(.*)$,); + next unless $code eq '0'; + + $ENV{DISPLAY} = ":$n"; + say "Running tests under Xvfb display $ENV{DISPLAY}"; + + push(@CLEANUP, sub { + kill(15, $xvfbpid); + }); + last; + } +} + @displays = split(/,/, join(',', @displays)); @displays = map { s/ //g; $_ } @displays; @@ -379,7 +421,7 @@ sub take_job { sub cleanup { my $exitcode = $?; - $_->() for our @CLEANUP; + $_->() for @CLEANUP; exit $exitcode; } diff --git a/travis/run-tests.sh b/travis/run-tests.sh index 44df81d2f..eac2ea8a3 100755 --- a/travis/run-tests.sh +++ b/travis/run-tests.sh @@ -26,7 +26,7 @@ fi # Try running the tests in parallel so that the common case (tests pass) is # quick, but fall back to running them in sequence to make debugging easier. -if ! xvfb-run make check +if ! make check then - xvfb-run ./testcases/complete-run.pl --parallel=1 || (cat latest/complete-run.log; false) + ./testcases/complete-run.pl --parallel=1 || (cat latest/complete-run.log; false) fi From c028f0cb173ebc4c7033c758c7f942adfbc224af Mon Sep 17 00:00:00 2001 From: Dan Elkouby Date: Sun, 30 Jul 2017 18:49:42 +0300 Subject: [PATCH 21/99] Create a new split container when switching a workspace container to split layout The behavior before 52ce8c8 was to do it regardless of what layout we're switching to. Fixes #2846 --- src/con.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/con.c b/src/con.c index b6b0da2c6..88d1e7448 100644 --- a/src/con.c +++ b/src/con.c @@ -1756,7 +1756,7 @@ void con_set_layout(Con *con, layout_t layout) { con->workspace_layout = ws_layout; DLOG("Setting layout to %d\n", layout); con->layout = layout; - } else if (layout == L_STACKED || layout == L_TABBED) { + } else if (layout == L_STACKED || layout == L_TABBED || layout == L_SPLITV || layout == L_SPLITH) { DLOG("Creating new split container\n"); /* 1: create a new split container */ Con *new = con_new(NULL, NULL); From a34a0048a1a9c009e23162f3039254768fc2fa8e Mon Sep 17 00:00:00 2001 From: hwangcc23 Date: Sun, 6 Aug 2017 23:08:05 +0800 Subject: [PATCH 22/99] Add regression tests for #2846 1). Add one regression test in 167-workspace_layout.t: - Get a fresh workspace - Set the layout to something - Create windows - Try to switch to another layout - Check if successful - Repeat for all 12 possible transitions 2). Add another regression test in 167-workspace_layout.t: - Check that the command 'layout toggle split' works regardless of what layout we're using --- testcases/t/167-workspace_layout.t | 67 ++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/testcases/t/167-workspace_layout.t b/testcases/t/167-workspace_layout.t index d983eb856..597d545eb 100644 --- a/testcases/t/167-workspace_layout.t +++ b/testcases/t/167-workspace_layout.t @@ -375,7 +375,74 @@ ok(@content == 2, 'two containers opened'); isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed'); isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed'); +exit_gracefully($pid); + +##################################################################### +# 16: Check that the command 'layout toggle split' works regardless +# of what layout we're using. +##################################################################### + +$config = <{layout}, 'splitv', 'layout toggles to splitv'); + } else { + is($content[0]->{layout}, 'splith', 'layout toggles to splith'); + } + + cmd '[id="' . $first->id . '"] kill'; + cmd '[id="' . $second->id . '"] kill'; + sync_with_i3; +} exit_gracefully($pid); +##################################################################### +# 17: Check about setting a new layout. +##################################################################### + +$config = <{layout}, $second_layout, 'layout changes to ' . $second_layout); + + cmd '[id="' . $first->id . '"] kill'; + cmd '[id="' . $second->id . '"] kill'; + sync_with_i3; + } +} + done_testing; From 26014ca1a22c1b862c782f4dfa3d40a5ab629627 Mon Sep 17 00:00:00 2001 From: Dan Elkouby Date: Thu, 12 Oct 2017 23:44:25 +0300 Subject: [PATCH 23/99] Default to L_SPLITH with toggle split when last_split_layout hasn't been initialized --- src/con.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/con.c b/src/con.c index 88d1e7448..1de91d008 100644 --- a/src/con.c +++ b/src/con.c @@ -1848,6 +1848,10 @@ void con_toggle_layout(Con *con, const char *toggle_mode) { * change to the opposite split layout. */ if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) { layout = parent->last_split_layout; + /* In case last_split_layout was not initialized… */ + if (layout == L_DEFAULT) { + layout = L_SPLITH; + } } else { layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH; } From 4f03e49c3a855a3a513d2ece54d55adc0f39ead0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 15 Oct 2017 12:17:22 +0200 Subject: [PATCH 24/99] userguide: explain why Mod4 is usually preferred (#3018) --- docs/userguide | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 5225c3914..697e241c3 100644 --- a/docs/userguide +++ b/docs/userguide @@ -35,7 +35,8 @@ above, just decline i3-config-wizard’s offer and base your config on Throughout this guide, the keyword +$mod+ will be used to refer to the configured modifier. This is the Alt key (+Mod1+) by default, with the Windows -key (+Mod4+) being a popular alternative. +key (+Mod4+) being a popular alternative that largely prevents conflicts with +application-defined shortcuts. === Opening terminals and moving around From 906a88e92bd36db13c1be5b368c64fa0bf3e007e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 15 Oct 2017 14:22:41 +0200 Subject: [PATCH 25/99] docs: full-size, so-called (#3019) As per Michael Siegel, who studied the English language :). --- docs/debugging | 2 +- docs/userguide | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/debugging b/docs/debugging index 07bc13a04..dd26f98d9 100644 --- a/docs/debugging +++ b/docs/debugging @@ -153,7 +153,7 @@ When sending bug reports, please attach the *whole* log file. Even if you think you found the section which clearly highlights the problem, additional information might be necessary to completely diagnose the problem. -When debugging with us in IRC, be prepared to use a so called nopaste service +When debugging with us in IRC, be prepared to use a so-called nopaste service such as https://pastebin.com because pasting large amounts of text in IRC sometimes leads to incomplete lines (servers have line length limitations) or flood kicks. diff --git a/docs/userguide b/docs/userguide index 697e241c3..54ac9c8d6 100644 --- a/docs/userguide +++ b/docs/userguide @@ -11,7 +11,7 @@ mailing list. == Default keybindings For the "too long; didn’t read" people, here is an overview of the default -keybindings (click to see the full size image): +keybindings (click to see the full-size image): *Keys to use with $mod (Alt):* @@ -197,7 +197,7 @@ out to be complicated to use (snapping), understand and implement. === The tree consists of Containers -The building blocks of our tree are so called +Containers+. A +Container+ can +The building blocks of our tree are so-called +Containers+. A +Container+ can host a window (meaning an X11 window, one that you can actually see and use, like a browser). Alternatively, it could contain one or more +Containers+. A simple example is the workspace: When you start i3 with a single monitor, a @@ -510,7 +510,7 @@ mode "$mode_launcher" { === The floating modifier To move floating windows with your mouse, you can either grab their titlebar -or configure the so called floating modifier which you can then press and +or configure the so-called floating modifier which you can then press and click anywhere in the window itself to move it. The most common setup is to use the same key you use for managing windows (Mod1 for example). Then you can press Mod1, click into a window using your left mouse button, and drag From a7bae0341d48cc5032e59f2c5660c01a9c0dac91 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 26 Sep 2017 12:30:03 +0300 Subject: [PATCH 26/99] i3bar: free fontname --- i3bar/src/config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/i3bar/src/config.c b/i3bar/src/config.c index cbe84d507..edccd3c66 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -192,6 +192,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len if (!strcmp(cur_key, "font")) { DLOG("font = %.*s\n", len, val); + FREE(config.fontname); sasprintf(&config.fontname, "%.*s", len, val); return 1; } From a05663c59eb44112044725a416cd457efcbabd94 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 26 Sep 2017 12:29:40 +0300 Subject: [PATCH 27/99] i3bar: avoid possible va_args leak --- i3bar/src/child.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i3bar/src/child.c b/i3bar/src/child.c index fe989c44c..170fcdefc 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -106,7 +106,7 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha va_list args; va_start(args, format); if (vasprintf(&message, format, args) == -1) { - return; + goto finish; } struct status_block *err_block = scalloc(1, sizeof(struct status_block)); @@ -124,6 +124,7 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks); TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks); +finish: FREE(message); va_end(args); } From b17e7b82c602b946594006bb3019f47bd8262f14 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 18 Oct 2017 02:04:42 +0300 Subject: [PATCH 28/99] Add support to resize floating container in percentage resize set is modified to accept both 'px' and 'ppt' height and width. Fixes #2816. --- docs/userguide | 2 +- include/commands.h | 2 +- parser-specs/commands.spec | 8 +++-- src/commands.c | 15 ++++++--- testcases/t/252-floating-size.t | 58 ++++++++++++++++++++++++++++++++- 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/docs/userguide b/docs/userguide index 54ac9c8d6..4fec6cdaf 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2302,7 +2302,7 @@ If you want to resize containers/windows using your keyboard, you can use the *Syntax*: ------------------------------------------------------- resize grow|shrink [ px [or ppt]] -resize set [px] [px] +resize set [px | ppt] [px | ppt] ------------------------------------------------------- Direction can either be one of +up+, +down+, +left+ or +right+. Or you can be diff --git a/include/commands.h b/include/commands.h index 9780f788b..85d5fe78a 100644 --- a/include/commands.h +++ b/include/commands.h @@ -66,7 +66,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_ * Implementation of 'resize set [px] [px]'. * */ -void cmd_resize_set(I3_CMD, long cwidth, long cheight); +void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height); /** * Implementation of 'resize grow|shrink [ px] [or ppt]'. diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index a58733281..0289fa1ab 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -258,14 +258,16 @@ state RESIZE_SET: -> RESIZE_WIDTH state RESIZE_WIDTH: - 'px' + mode_width = 'px', 'ppt' -> height = number -> RESIZE_HEIGHT state RESIZE_HEIGHT: - 'px', end - -> call cmd_resize_set(&width, &height) + mode_height = 'px', 'ppt' + -> + end + -> call cmd_resize_set(&width, $mode_width, &height, $mode_height) # rename workspace to # rename workspace to diff --git a/src/commands.c b/src/commands.c index d7cdf2198..e68fcd802 100644 --- a/src/commands.c +++ b/src/commands.c @@ -676,13 +676,13 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px, } /* - * Implementation of 'resize set [px] [px]'. + * Implementation of 'resize set [px | ppt] [px | ppt]'. * */ -void cmd_resize_set(I3_CMD, long cwidth, long cheight) { - DLOG("resizing to %ldx%ld px\n", cwidth, cheight); +void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height) { + DLOG("resizing to %ld %s x %ld %s\n", cwidth, mode_width, cheight, mode_height); if (cwidth <= 0 || cheight <= 0) { - ELOG("Resize failed: dimensions cannot be negative (was %ldx%ld)\n", cwidth, cheight); + ELOG("Resize failed: dimensions cannot be negative (was %ld %s x %ld %s)\n", cwidth, mode_width, cheight, mode_height); return; } @@ -692,6 +692,13 @@ void cmd_resize_set(I3_CMD, long cwidth, long cheight) { TAILQ_FOREACH(current, &owindows, owindows) { Con *floating_con; if ((floating_con = con_inside_floating(current->con))) { + Con *output = con_get_output(floating_con); + if (mode_width && strcmp(mode_width, "ppt") == 0) { + cwidth = output->rect.width * ((double)cwidth / 100.0); + } + if (mode_height && strcmp(mode_height, "ppt") == 0) { + cheight = output->rect.height * ((double)cheight / 100.0); + } floating_resize(floating_con, cwidth, cheight); } else { ELOG("Resize failed: %p not a floating container\n", current->con); diff --git a/testcases/t/252-floating-size.t b/testcases/t/252-floating-size.t index 8d8d41203..ac0c48d03 100644 --- a/testcases/t/252-floating-size.t +++ b/testcases/t/252-floating-size.t @@ -17,7 +17,13 @@ # Test behavior of "resize " command. # Ticket: #1727 # Bug still in: 4.10.2-1-gc0dbc5d -use i3test; +use i3test i3_config => <{rect}->{height}, '!=', $oldrect->{width}, 'height changed') cmp_ok($content[0]->{rect}->{width}, '==', 100, 'width changed to 100 px'); cmp_ok($content[0]->{rect}->{height}, '==', 250, 'height changed to 250 px'); +################################################################################ +# Same but with ppt instead of px +################################################################################ + +kill_all_windows; +$tmp = 'ws'; +cmd "workspace $tmp"; +open_floating_window; + +@content = @{get_ws($tmp)->{floating_nodes}}; +is(@content, 1, 'one floating node on this ws'); + +$oldrect = $content[0]->{rect}; + +cmd 'resize set 33 ppt 20 ppt'; +my $expected_width = int(0.33 * 1333); +my $expected_height = int(0.2 * 999); + +@content = @{get_ws($tmp)->{floating_nodes}}; +cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched'); +cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched'); +cmp_ok($content[0]->{rect}->{width}, '!=', $oldrect->{width}, 'width changed'); +cmp_ok($content[0]->{rect}->{height}, '!=', $oldrect->{width}, 'height changed'); +cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px"); +cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px"); + +################################################################################ +# Mix ppt and px in a single resize set command +################################################################################ + +cmd 'resize set 44 ppt 111 px'; +my $expected_width = int(0.44 * 1333); +my $expected_height = 111; + +@content = @{get_ws($tmp)->{floating_nodes}}; +cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched'); +cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched'); +cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px"); +cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px"); + +cmd 'resize set 222 px 100 ppt'; +my $expected_width = 222; +my $expected_height = 999; + +@content = @{get_ws($tmp)->{floating_nodes}}; +cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched'); +cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched'); +cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px"); +cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px"); + done_testing; From 4f751610c2a51fb35755db87fafee8598d6d7bd3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 Oct 2017 22:16:15 +0200 Subject: [PATCH 29/99] _NET_ACTIVE_WINDOW: invalidate focus to force SetInputFocus call (#3027) The sender of the _NET_ACTIVE_WINDOW client message might know better when to set focus than i3, as i3 does not know about unmanaged (override_redirect=1) windows. related to https://github.com/i3/i3lock/issues/35 --- src/handlers.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index 3140e4057..436fb2aba 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -774,6 +774,8 @@ static void handle_client_message(xcb_client_message_event_t *event) { scratchpad_show(con); } else { workspace_show(ws); + /* Re-set focus, even if unchanged from i3’s perspective. */ + focused_id = XCB_NONE; con_focus(con); } } else { From 5d55f93eb35d8a0626cef09b8de250b884e06eb8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 3 Oct 2017 09:58:10 +0200 Subject: [PATCH 30/99] tests: add sync_with_i3 after open_window We need to set dont_map => 1 on the sync window to prevent an endless loop. Further, t/219-ipc-window-focus.t made assumptions about windows being named incrementally, and that assumption is broken by the sync window opened by the first sync_with_i3 call from open_window, so use the more reliable ->name. --- testcases/lib/i3test.pm.in | 7 +++++++ testcases/t/219-ipc-window-focus.t | 12 ++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index 5b24ab391..1e4eea751 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -350,6 +350,12 @@ sub open_window { $window->map; wait_for_map($window); + + # MapWindow is sent before i3 even starts rendering: the window is placed at + # temporary off-screen coordinates first, and x_push_changes() sends further + # X11 requests to set focus etc. Hence, we sync with i3 before continuing. + sync_with_i3(); + return $window; } @@ -688,6 +694,7 @@ sub sync_with_i3 { $_sync_window = open_window( rect => [ -15, -15, 10, 10 ], override_redirect => 1, + dont_map => 1, ); } diff --git a/testcases/t/219-ipc-window-focus.t b/testcases/t/219-ipc-window-focus.t index 5baf68a80..b1c8ba183 100644 --- a/testcases/t/219-ipc-window-focus.t +++ b/testcases/t/219-ipc-window-focus.t @@ -44,11 +44,11 @@ sub focus_subtest { is($events[0]->{container}->{name}, $name, "$name focused"); } -subtest 'focus left (1)', \&focus_subtest, 'focus left', 'Window 1'; -subtest 'focus left (2)', \&focus_subtest, 'focus left', 'Window 0'; -subtest 'focus right (1)', \&focus_subtest, 'focus right', 'Window 1'; -subtest 'focus right (2)', \&focus_subtest, 'focus right', 'Window 2'; -subtest 'focus right (3)', \&focus_subtest, 'focus right', 'Window 0'; -subtest 'focus left', \&focus_subtest, 'focus left', 'Window 2'; +subtest 'focus left (1)', \&focus_subtest, 'focus left', $win1->name; +subtest 'focus left (2)', \&focus_subtest, 'focus left', $win0->name; +subtest 'focus right (1)', \&focus_subtest, 'focus right', $win1->name; +subtest 'focus right (2)', \&focus_subtest, 'focus right', $win2->name; +subtest 'focus right (3)', \&focus_subtest, 'focus right', $win0->name; +subtest 'focus left', \&focus_subtest, 'focus left', $win2->name; done_testing; From 7208df2d38c9102e8e94d6d5533d705b130f87e3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 3 Oct 2017 10:00:05 +0200 Subject: [PATCH 31/99] i3test: reliably warp the pointer to (0, 0) before tests start --- testcases/lib/i3test.pm.in | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index 1e4eea751..429e51d41 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -133,6 +133,22 @@ sub import { my ($class, %args) = @_; my $pkg = caller; + $x ||= i3test::X11->new; + # set the pointer to a predictable position in case a previous test has + # disturbed it + $x->warp_pointer( + 0, # src_window (None) + $x->get_root_window(), # dst_window (None) + 0, # src_x + 0, # src_y + 0, # src_width + 0, # src_height + 0, # dst_x + 0); # dst_y + # Synchronize with X11 to ensure the pointer has been warped before i3 + # starts up. + $x->get_input_focus_reply($x->get_input_focus()->{sequence}); + $i3_autostart = delete($args{i3_autostart}) // 1; my $i3_config = delete($args{i3_config}) // '-default'; @@ -155,10 +171,6 @@ __ strict->import; warnings->import; - $x ||= i3test::X11->new; - # set the pointer to a predictable position in case a previous test has - # disturbed it - $x->root->warp_pointer(0, 0); $cv->recv if $i3_autostart; @_ = ($class); From 8e528d2de8d75eb1b053ccac5b1f119292eb68dc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 3 Oct 2017 10:00:55 +0200 Subject: [PATCH 32/99] skip ConfigureNotify events with --force_xinerama This prevents an i3 crash under certain conditions when running the tests. --- include/i3.h | 1 + src/handlers.c | 3 +++ src/main.c | 9 +++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/i3.h b/include/i3.h index 4d13d448f..93a7e0a34 100644 --- a/include/i3.h +++ b/include/i3.h @@ -74,3 +74,4 @@ extern bool xcursor_supported, xkb_supported; extern xcb_window_t root; extern struct ev_loop *main_loop; extern bool only_check_config; +extern bool force_xinerama; diff --git a/src/handlers.c b/src/handlers.c index 436fb2aba..0f81afae1 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1264,6 +1264,9 @@ static void handle_configure_notify(xcb_configure_notify_event_t *event) { } DLOG("ConfigureNotify for root window 0x%08x\n", event->event); + if (force_xinerama) { + return; + } randr_query_outputs(); } diff --git a/src/main.c b/src/main.c index 0d1457fdc..3087f1114 100644 --- a/src/main.c +++ b/src/main.c @@ -92,6 +92,8 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment bool xcursor_supported = true; bool xkb_supported = true; +bool force_xinerama = false; + /* * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop @@ -197,7 +199,6 @@ int main(int argc, char *argv[]) { bool autostart = true; char *layout_path = NULL; bool delete_layout_path = false; - bool force_xinerama = false; bool disable_randr15 = false; char *fake_outputs = NULL; bool disable_signalhandler = false; @@ -550,6 +551,10 @@ int main(int argc, char *argv[]) { config.ipc_socket_path = sstrdup(config.ipc_socket_path); } + if (config.force_xinerama) { + force_xinerama = true; + } + xcb_void_cookie_t cookie; cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK}); xcb_generic_error_t *error = xcb_request_check(conn, cookie); @@ -668,7 +673,7 @@ int main(int argc, char *argv[]) { fake_outputs_init(fake_outputs); FREE(fake_outputs); config.fake_outputs = NULL; - } else if (force_xinerama || config.force_xinerama) { + } else if (force_xinerama) { /* Force Xinerama (for drivers which don't support RandR yet, esp. the * nVidia binary graphics driver), when specified either in the config * file or on command-line */ From 7a672a9d41b60201534c17bc706f9c46dd20ad66 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 3 Oct 2017 10:02:41 +0200 Subject: [PATCH 33/99] i3test: blockingly wait for events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …as polling the xcb file descriptor directly is not reliable. --- testcases/lib/i3test.pm.in | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index 429e51d41..ed239241c 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -193,29 +193,11 @@ received, etc. sub wait_for_event { my ($timeout, $cb) = @_; - my $cv = AE::cv; - $x->flush; - # unfortunately, there is no constant for this - my $ae_read = 0; - - my $guard = AE::io $x->get_file_descriptor, $ae_read, sub { - while (defined(my $event = $x->poll_for_event)) { - if ($cb->($event)) { - $cv->send(1); - last; - } - } - }; - - # Trigger timeout after $timeout seconds (can be fractional) - my $t = AE::timer $timeout, 0, sub { warn "timeout ($timeout secs)"; $cv->send(0) }; - - my $result = $cv->recv; - undef $t; - undef $guard; - return $result; + while (defined(my $event = $x->wait_for_event)) { + return 1 if $cb->($event); + } } =head2 wait_for_map($window) From 1946cc6cab72e6e8f23a3a7fb331e82397dd39f2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 3 Oct 2017 17:16:34 +0200 Subject: [PATCH 34/99] t/525: remove flaky focus check --- testcases/t/525-i3bar-mouse-bindings.t | 2 -- 1 file changed, 2 deletions(-) diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t index e67f0df99..57786deaf 100644 --- a/testcases/t/525-i3bar-mouse-bindings.t +++ b/testcases/t/525-i3bar-mouse-bindings.t @@ -87,8 +87,6 @@ diag('i3bar window = ' . $i3bar_window); my $left = open_window; my $right = open_window; sync_with_i3; -my $con = $cv->recv; -is($con->{window}, $right->{id}, 'focus is initially on the right container'); sub focus_subtest { my ($subscribecb, $want, $msg) = @_; From 0d8b6714e39af81cbd6f4fbad500872a715dea24 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 3 Oct 2017 10:03:29 +0200 Subject: [PATCH 35/99] Move XCB event handling into xcb_prepare_cb. Previously, we used ev_check watchers, which are executed at the beginning of an event loop iteration. This was problematic if one of the handlers happened to fill the XCB event queue, e.g. by reading a reply from X11 and an event happened in the meantime. In that situation, we would hand control to the event loop, entirely ignoring the pending event. This would manifest itself as a 1-minute hang, reproducible (sometimes) in the i3 testsuite. issue #2790 describes an instance of this issue in i3bar, and we fixed that by changing the watcher priority to run last. Handling events in xcb_prepare_cb has the same effect, as ev_prepare watchers are run just before the event loop goes to sleep. --- i3bar/src/xcb.c | 28 ++++------------------------ src/floating.c | 16 +++++++++------- src/main.c | 38 ++++++++++++++++---------------------- src/restore_layout.c | 13 ++----------- 4 files changed, 31 insertions(+), 64 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index dfa66ee2d..98f0dcbe0 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -83,7 +83,6 @@ int mod_pressed = 0; /* Event watchers, to interact with the user */ ev_prepare *xcb_prep; -ev_check *xcb_chk; ev_io *xcb_io; ev_io *xkb_io; @@ -1075,21 +1074,11 @@ static void handle_resize_request(xcb_resize_request_event_t *event) { } /* - * This function is called immediately before the main loop locks. We flush xcb - * then (and only then) + * This function is called immediately before the main loop locks. We check for + * events from X11, handle them, then flush our outgoing queue. * */ void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) { - xcb_flush(xcb_connection); -} - -/* - * This function is called immediately after the main loop locks, so when one - * of the watchers registered an event. - * We check whether an X-Event arrived and handle it. - * - */ -void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { xcb_generic_event_t *event; if (xcb_connection_has_error(xcb_connection)) { @@ -1210,6 +1199,8 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { } free(event); } + + xcb_flush(xcb_connection); } /* @@ -1267,21 +1258,12 @@ char *init_xcb_early() { /* The various watchers to communicate with xcb */ xcb_io = smalloc(sizeof(ev_io)); xcb_prep = smalloc(sizeof(ev_prepare)); - xcb_chk = smalloc(sizeof(ev_check)); ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ); ev_prepare_init(xcb_prep, &xcb_prep_cb); - ev_check_init(xcb_chk, &xcb_chk_cb); - - /* Within an event loop iteration, run the xcb_chk watcher last: other - * watchers might call xcb_flush(), which, unexpectedly, can also read - * events into the queue (see _xcb_conn_wait). Hence, we need to drain xcb’s - * queue last, otherwise we risk dead-locking. */ - ev_set_priority(xcb_chk, EV_MINPRI); ev_io_start(main_loop, xcb_io); ev_prepare_start(main_loop, xcb_prep); - ev_check_start(main_loop, xcb_chk); /* Now we get the atoms and save them in a nice data structure */ get_atoms(); @@ -1535,11 +1517,9 @@ void clean_xcb(void) { xcb_aux_sync(xcb_connection); xcb_disconnect(xcb_connection); - ev_check_stop(main_loop, xcb_chk); ev_prepare_stop(main_loop, xcb_prep); ev_io_stop(main_loop, xcb_io); - FREE(xcb_chk); FREE(xcb_prep); FREE(xcb_io); } diff --git a/src/floating.c b/src/floating.c index 5f46dcf97..149888184 100644 --- a/src/floating.c +++ b/src/floating.c @@ -667,7 +667,7 @@ void floating_resize_window(Con *con, const bool proportional, /* Custom data structure used to track dragging-related events. */ struct drag_x11_cb { - ev_check check; + ev_prepare prepare; /* Whether this modal event loop should be exited and with which result. */ drag_result_t result; @@ -686,7 +686,7 @@ struct drag_x11_cb { const void *extra; }; -static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) { +static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) { struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data; xcb_motion_notify_event_t *last_motion_notify = NULL; xcb_generic_event_t *event; @@ -765,6 +765,8 @@ static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) { dragloop->extra); } free(last_motion_notify); + + xcb_flush(conn); } /* @@ -831,18 +833,18 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ .callback = callback, .extra = extra, }; - ev_check *check = &loop.check; + ev_prepare *prepare = &loop.prepare; if (con) loop.old_rect = con->rect; - ev_check_init(check, xcb_drag_check_cb); - check->data = &loop; + ev_prepare_init(prepare, xcb_drag_prepare_cb); + prepare->data = &loop; main_set_x11_cb(false); - ev_check_start(main_loop, check); + ev_prepare_start(main_loop, prepare); while (loop.result == DRAGGING) ev_run(main_loop, EVRUN_ONCE); - ev_check_stop(main_loop, check); + ev_prepare_stop(main_loop, prepare); main_set_x11_cb(true); xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME); diff --git a/src/main.c b/src/main.c index 3087f1114..d87b9a29b 100644 --- a/src/main.c +++ b/src/main.c @@ -35,9 +35,9 @@ struct rlimit original_rlimit_core; /** The number of file descriptors passed via socket activation. */ int listen_fds; -/* We keep the xcb_check watcher around to be able to enable and disable it +/* We keep the xcb_prepare watcher around to be able to enable and disable it * temporarily for drag_pointer(). */ -static struct ev_check *xcb_check; +static struct ev_prepare *xcb_prepare; extern Con *focused; @@ -95,28 +95,23 @@ bool xkb_supported = true; bool force_xinerama = false; /* - * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. + * This callback is only a dummy, see xcb_prepare_cb. * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop * */ static void xcb_got_event(EV_P_ struct ev_io *w, int revents) { - /* empty, because xcb_prepare_cb and xcb_check_cb are used */ + /* empty, because xcb_prepare_cb are used */ } /* - * Flush before blocking (and waiting for new events) + * Called just before the event loop sleeps. Ensures xcb’s incoming and outgoing + * queues are empty so that any activity will trigger another event loop + * iteration, and hence another xcb_prepare_cb invocation. * */ static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) { - xcb_flush(conn); -} - -/* - * Instead of polling the X connection socket we leave this to - * xcb_poll_for_event() which knows better than we can ever know. - * - */ -static void xcb_check_cb(EV_P_ ev_check *w, int revents) { + /* Process all queued (and possibly new) events before the event loop + sleeps. */ xcb_generic_event_t *event; while ((event = xcb_poll_for_event(conn)) != NULL) { @@ -139,6 +134,9 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { free(event); } + + /* Flush all queued events to X11. */ + xcb_flush(conn); } /* @@ -150,12 +148,12 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { void main_set_x11_cb(bool enable) { DLOG("Setting main X11 callback to enabled=%d\n", enable); if (enable) { - ev_check_start(main_loop, xcb_check); + ev_prepare_start(main_loop, xcb_prepare); /* Trigger the watcher explicitly to handle all remaining X11 events. * drag_pointer()’s event handler exits in the middle of the loop. */ - ev_feed_event(main_loop, xcb_check, 0); + ev_feed_event(main_loop, xcb_prepare, 0); } else { - ev_check_stop(main_loop, xcb_check); + ev_prepare_stop(main_loop, xcb_prepare); } } @@ -781,15 +779,11 @@ int main(int argc, char *argv[]) { ewmh_update_desktop_viewport(); struct ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io)); - xcb_check = scalloc(1, sizeof(struct ev_check)); - struct ev_prepare *xcb_prepare = scalloc(1, sizeof(struct ev_prepare)); + xcb_prepare = scalloc(1, sizeof(struct ev_prepare)); ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); ev_io_start(main_loop, xcb_watcher); - ev_check_init(xcb_check, xcb_check_cb); - ev_check_start(main_loop, xcb_check); - ev_prepare_init(xcb_prepare, xcb_prepare_cb); ev_prepare_start(main_loop, xcb_prepare); diff --git a/src/restore_layout.c b/src/restore_layout.c index bf16c864c..b99a50c16 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -39,7 +39,6 @@ static TAILQ_HEAD(state_head, placeholder_state) state_head = static xcb_connection_t *restore_conn; static struct ev_io *xcb_watcher; -static struct ev_check *xcb_check; static struct ev_prepare *xcb_prepare; static void restore_handle_event(int type, xcb_generic_event_t *event); @@ -49,10 +48,6 @@ static void restore_xcb_got_event(EV_P_ struct ev_io *w, int revents) { } static void restore_xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) { - xcb_flush(restore_conn); -} - -static void restore_xcb_check_cb(EV_P_ ev_check *w, int revents) { xcb_generic_event_t *event; if (xcb_connection_has_error(restore_conn)) { @@ -77,6 +72,8 @@ static void restore_xcb_check_cb(EV_P_ ev_check *w, int revents) { free(event); } + + xcb_flush(restore_conn); } /* @@ -91,7 +88,6 @@ void restore_connect(void) { /* This is not the initial connect, but a reconnect, most likely * because our X11 connection was killed (e.g. by a user with xkill. */ ev_io_stop(main_loop, xcb_watcher); - ev_check_stop(main_loop, xcb_check); ev_prepare_stop(main_loop, xcb_prepare); placeholder_state *state; @@ -107,7 +103,6 @@ void restore_connect(void) { */ xcb_disconnect(restore_conn); free(xcb_watcher); - free(xcb_check); free(xcb_prepare); } @@ -124,15 +119,11 @@ void restore_connect(void) { } xcb_watcher = scalloc(1, sizeof(struct ev_io)); - xcb_check = scalloc(1, sizeof(struct ev_check)); xcb_prepare = scalloc(1, sizeof(struct ev_prepare)); ev_io_init(xcb_watcher, restore_xcb_got_event, xcb_get_file_descriptor(restore_conn), EV_READ); ev_io_start(main_loop, xcb_watcher); - ev_check_init(xcb_check, restore_xcb_check_cb); - ev_check_start(main_loop, xcb_check); - ev_prepare_init(xcb_prepare, restore_xcb_prepare_cb); ev_prepare_start(main_loop, xcb_prepare); } From e0287e00d973ea98c494dafdc450c8511f16a9f5 Mon Sep 17 00:00:00 2001 From: Tyler Brazier Date: Wed, 25 Oct 2017 15:26:44 -0500 Subject: [PATCH 36/99] config: use ascii single quote MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unicode quote doesn’t render very well in terminals that don't support unicode --- etc/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/config b/etc/config index 483694c1c..65e36b808 100644 --- a/etc/config +++ b/etc/config @@ -22,7 +22,7 @@ font pango:monospace 8 # The font above is very space-efficient, that is, it looks good, sharp and # clear in small sizes. However, its unicode glyph coverage is limited, the old # X core fonts rendering does not support right-to-left and this being a bitmap -# font, it doesn’t scale on retina/hidpi displays. +# font, it doesn't scale on retina/hidpi displays. # use these keys for focus, movement, and resize directions when reaching for # the arrows is not convenient From 0afe714e2b43e95db8b377c62966916ebf242217 Mon Sep 17 00:00:00 2001 From: Wes Roberts Date: Mon, 30 Oct 2017 21:19:12 -0400 Subject: [PATCH 37/99] $mod+r toggles resize mode --- etc/config | 1 + etc/config.keycodes | 1 + 2 files changed, 2 insertions(+) diff --git a/etc/config b/etc/config index 483694c1c..f6fd5e7b5 100644 --- a/etc/config +++ b/etc/config @@ -157,6 +157,7 @@ mode "resize" { # back to normal: Enter or Escape bindsym Return mode "default" bindsym Escape mode "default" + bindsym Mod1+r mode "default" } bindsym Mod1+r mode "resize" diff --git a/etc/config.keycodes b/etc/config.keycodes index 6d10fad29..c07462b4e 100644 --- a/etc/config.keycodes +++ b/etc/config.keycodes @@ -144,6 +144,7 @@ mode "resize" { # back to normal: Enter or Escape bindcode 36 mode "default" bindcode 9 mode "default" + bindcode $mod+27 mode "default" } bindcode $mod+27 mode "resize" From 103e78e04a2015caad74e00a48f7faa83ae19f42 Mon Sep 17 00:00:00 2001 From: xzfc Date: Fri, 10 Nov 2017 02:18:23 +0700 Subject: [PATCH 38/99] Bugfix: avert endless loop on unexpected EOF at ipc messages (#3021) Fix freeze on invalid ipc commands like echo -n $'i3-ipc\0\0\0\xa\0\0\0\0focus left' | socat - `i3 --get-socketpath` Also, treat incomplete headers as IPC violation. Example of incomplete header: echo -n i3-ip | socat - `i3 --get-socketpath` --- libi3/ipc_recv_message.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/libi3/ipc_recv_message.c b/libi3/ipc_recv_message.c index 16dda90d8..84da5aa36 100644 --- a/libi3/ipc_recv_message.c +++ b/libi3/ipc_recv_message.c @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -41,14 +42,21 @@ int ipc_recv_message(int sockfd, uint32_t *message_type, if (n == -1) return -1; if (n == 0) { - return -2; + if (read_bytes == 0) { + return -2; + } else { + ELOG("IPC: unexpected EOF while reading header, got %" PRIu32 " bytes, want %" PRIu32 " bytes\n", + read_bytes, to_read); + return -3; + } } read_bytes += n; } if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) { - ELOG("IPC: invalid magic in reply\n"); + ELOG("IPC: invalid magic in header, got \"%.*s\", want \"%s\"\n", + (int)strlen(I3_IPC_MAGIC), walk, I3_IPC_MAGIC); return -3; } @@ -61,13 +69,18 @@ int ipc_recv_message(int sockfd, uint32_t *message_type, *reply = smalloc(*reply_length); read_bytes = 0; - int n; while (read_bytes < *reply_length) { - if ((n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes)) == -1) { + const int n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes); + if (n == -1) { if (errno == EINTR || errno == EAGAIN) continue; return -1; } + if (n == 0) { + ELOG("IPC: unexpected EOF while reading payload, got %" PRIu32 " bytes, want %" PRIu32 " bytes\n", + read_bytes, *reply_length); + return -3; + } read_bytes += n; } From 3a22c6e7655dc49ff935c8f93ed283f1de514216 Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Sat, 11 Nov 2017 17:06:43 -0800 Subject: [PATCH 39/99] docs/userguide: Correct mark/goto i3-input commands The userguide still mentions an old 'goto' command which no longer exists and will be ignored silently (when used in the i3 config) or causes an error to be reported (when invoked from the command line). This change updates the userguide to correct this problem. In addition to that it also updates the i3-input command shown to no longer use the deprecated -p flag but -F instead. --- docs/userguide | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index 4fec6cdaf..7a1621da8 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2398,10 +2398,10 @@ TODO: make i3-input replace %s *Examples*: --------------------------------------- # Read 1 character and mark the current window with this character -bindsym $mod+m exec i3-input -p 'mark ' -l 1 -P 'Mark: ' +bindsym $mod+m exec i3-input -F 'mark %s' -l 1 -P 'Mark: ' # Read 1 character and go to the window with the character -bindsym $mod+g exec i3-input -p 'goto ' -l 1 -P 'Goto: ' +bindsym $mod+g exec i3-input -F '[con_mark="%s"] focus' -l 1 -P 'Goto: ' --------------------------------------- Alternatively, if you do not want to mess with +i3-input+, you could create From 22ba46e2a3dc39e9ac0f8fc31a5ac74b13f4a254 Mon Sep 17 00:00:00 2001 From: Cast Date: Sun, 12 Nov 2017 16:48:21 +0800 Subject: [PATCH 40/99] add the kitty terminal in i3-sensible-terminal --- i3-sensible-terminal | 2 +- man/i3-sensible-terminal.man | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/i3-sensible-terminal b/i3-sensible-terminal index f92ff224a..23cb5688f 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -8,7 +8,7 @@ # We welcome patches that add distribution-specific mechanisms to find the # preferred terminal emulator. On Debian, there is the x-terminal-emulator # symlink for example. -for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole; do +for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty; do if command -v "$terminal" > /dev/null 2>&1; then exec "$terminal" "$@" fi diff --git a/man/i3-sensible-terminal.man b/man/i3-sensible-terminal.man index 20a6810c5..bccc824a8 100644 --- a/man/i3-sensible-terminal.man +++ b/man/i3-sensible-terminal.man @@ -44,6 +44,7 @@ It tries to start one of the following (in that order): * tilix * terminix * konsole +* kitty Please don’t complain about the order: If the user has any preference, they will have $TERMINAL set or modified their i3 configuration file. From 222d8210abaae69f91f8834a6d3045489d5b48ef Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Fri, 17 Nov 2017 06:58:12 -0800 Subject: [PATCH 41/99] i3bar: replace magic numbers with more meaningful constructs In i3bar/src/config.c we compare string lengths agains magic numbers. This change replaces those numbers with the lengths of the strings they represent. --- i3bar/src/config.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/i3bar/src/config.c b/i3bar/src/config.c index cbe84d507..b98d41232 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -107,34 +107,34 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len if (!strcmp(cur_key, "mode")) { DLOG("mode = %.*s, len = %d\n", len, val, len); - config.hide_on_modifier = (len == 4 && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK - : (len == 4 && !strncmp((const char *)val, "hide", strlen("hide")) ? M_HIDE - : M_INVISIBLE)); + config.hide_on_modifier = (len == strlen("dock") && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK + : (len == strlen("hide") && !strncmp((const char *)val, "hide", strlen("hide")) ? M_HIDE + : M_INVISIBLE)); return 1; } if (!strcmp(cur_key, "hidden_state")) { DLOG("hidden_state = %.*s, len = %d\n", len, val, len); - config.hidden_state = (len == 4 && !strncmp((const char *)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW); + config.hidden_state = (len == strlen("hide") && !strncmp((const char *)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW); return 1; } if (!strcmp(cur_key, "modifier")) { DLOG("modifier = %.*s\n", len, val); - if (len == 4 && !strncmp((const char *)val, "none", strlen("none"))) { + if (len == strlen("none") && !strncmp((const char *)val, "none", strlen("none"))) { config.modifier = XCB_NONE; return 1; } - if (len == 5 && !strncmp((const char *)val, "shift", strlen("shift"))) { + if (len == strlen("shift") && !strncmp((const char *)val, "shift", strlen("shift"))) { config.modifier = ShiftMask; return 1; } - if (len == 4 && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) { + if (len == strlen("ctrl") && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) { config.modifier = ControlMask; return 1; } - if (len == 4 && !strncmp((const char *)val, "Mod", strlen("Mod"))) { + if (len == strlen("Mod") + 1 && !strncmp((const char *)val, "Mod", strlen("Mod"))) { switch (val[3]) { case '1': config.modifier = Mod1Mask; @@ -179,7 +179,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len if (!strcmp(cur_key, "position")) { DLOG("position = %.*s\n", len, val); - config.position = (len == 3 && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT); + config.position = (len == strlen("top") && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT); return 1; } From 754322c71bacb15cc5b4478cd726b16dcd78b9dc Mon Sep 17 00:00:00 2001 From: paulbdavis Date: Fri, 17 Nov 2017 10:36:05 -0700 Subject: [PATCH 42/99] add `smart_no_gaps` option to `hide_edge_borders` formatting wording in test case fix 201-config-parser.t update README --- README.md | 2 + include/data.h | 3 +- parser-specs/config.spec | 4 +- src/con.c | 11 +++++- src/config_directives.c | 4 +- testcases/t/201-config-parser.t | 2 +- testcases/t/287-edge-borders.t | 65 +++++++++++++++++++++++++++++++++ 7 files changed, 84 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e2bfad6c9..abd44fb97 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,9 @@ Based on the patch from [i3-extras](https://github.com/ashinkarov/i3-extras), sm ``` smart_borders on|no_gaps ``` +### Hide Edge Borders +An additional option `smart_no_gaps` is available for `hide_edge_borders`. This will hide all borders on a container if it is the only container in the worskpace *and* the gap size to the edge is `0`. Otherwise all borders will be drawn normally. ## i3bar diff --git a/include/data.h b/include/data.h index e3d2b80e3..4b03ecd11 100644 --- a/include/data.h +++ b/include/data.h @@ -85,7 +85,8 @@ typedef enum { HEBM_NONE = ADJ_NONE, HEBM_VERTICAL = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE, HEBM_HORIZONTAL = ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE, HEBM_BOTH = HEBM_VERTICAL | HEBM_HORIZONTAL, - HEBM_SMART = (1 << 5) } hide_edge_borders_mode_t; + HEBM_SMART = (1 << 5), + HEBM_SMART_NO_GAPS = (1 << 6) } hide_edge_borders_mode_t; typedef enum { MM_REPLACE, MM_ADD } mark_mode_t; diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 22bd05ce9..fe3523a88 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -150,10 +150,10 @@ state NEW_WINDOW_PIXELS_PX: end -> call cfg_new_window($windowtype, $border, &width) -# hide_edge_borders +# hide_edge_borders # also hide_edge_borders for compatibility state HIDE_EDGE_BORDERS: - hide_borders = 'none', 'vertical', 'horizontal', 'both', 'smart' + hide_borders = 'none', 'vertical', 'horizontal', 'both', 'smart_no_gaps', 'smart' -> call cfg_hide_edge_borders($hide_borders) hide_borders = '1', 'yes', 'true', 'on', 'enable', 'active' -> call cfg_hide_edge_borders($hide_borders) diff --git a/src/con.c b/src/con.c index 019e1f9a8..124317128 100644 --- a/src/con.c +++ b/src/con.c @@ -1586,7 +1586,8 @@ Con *con_descend_direction(Con *con, direction_t direction) { Rect con_border_style_rect(Con *con) { if ((config.smart_borders == ON && con_num_visible_children(con_get_workspace(con)) <= 1) || (config.smart_borders == NO_GAPS && calculate_effective_gaps(con).outer == 0) || - (config.hide_edge_borders == HEBM_SMART && con_num_visible_children(con_get_workspace(con)) <= 1)) { + (config.hide_edge_borders == HEBM_SMART && con_num_visible_children(con_get_workspace(con)) <= 1) || + (config.hide_edge_borders == HEBM_SMART_NO_GAPS && con_num_visible_children(con_get_workspace(con)) <= 1 && calculate_effective_gaps(con).outer == 0)) { if (!con_is_floating(con)) return (Rect){0, 0, 0, 0}; } @@ -1613,7 +1614,13 @@ Rect con_border_style_rect(Con *con) { result = (Rect){border_width, border_width, -(2 * border_width), -(2 * border_width)}; } - borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; + /* If hide_edge_borders is set to no_gaps and it did not pass the no border check, show all borders */ + if (config.hide_edge_borders == HEBM_SMART_NO_GAPS) { + borders_to_hide = con_adjacent_borders(con) & HEBM_NONE; + } else { + borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; + } + if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) { result.x -= border_width; result.width += border_width; diff --git a/src/config_directives.c b/src/config_directives.c index 632173c8a..f3d3e1749 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -283,7 +283,9 @@ CFGFUN(new_window, const char *windowtype, const char *border, const long width) } CFGFUN(hide_edge_borders, const char *borders) { - if (strcmp(borders, "smart") == 0) + if (strcmp(borders, "smart_no_gaps") == 0) + config.hide_edge_borders = HEBM_SMART_NO_GAPS; + else if (strcmp(borders, "smart") == 0) config.hide_edge_borders = HEBM_SMART; else if (strcmp(borders, "vertical") == 0) config.hide_edge_borders = HEBM_VERTICAL; diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 927a89038..5929f7067 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -520,7 +520,7 @@ client.focused #4c7899 #285577 #ffffff #2e9ef4 EOT $expected = <<'EOT'; -ERROR: CONFIG: Expected one of these tokens: 'none', 'vertical', 'horizontal', 'both', 'smart', '1', 'yes', 'true', 'on', 'enable', 'active' +ERROR: CONFIG: Expected one of these tokens: 'none', 'vertical', 'horizontal', 'both', 'smart_no_gaps', 'smart', '1', 'yes', 'true', 'on', 'enable', 'active' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: hide_edge_borders FOOBAR ERROR: CONFIG: ^^^^^^ diff --git a/testcases/t/287-edge-borders.t b/testcases/t/287-edge-borders.t index 1fd697860..3dfc34077 100644 --- a/testcases/t/287-edge-borders.t +++ b/testcases/t/287-edge-borders.t @@ -160,4 +160,69 @@ is($tilewindow2->rect->width, $tiled[1]->{rect}->{width} - 2*2, 'second tiled bo exit_gracefully($pid); +##################################################################### +# 5: check that the borders are visible on a workspace with one tiled +# window and edge gaps +##################################################################### + +$config = <{nodes}}; +ok(@tiled == 1, 'one tiled container opened'); +is($tiled[0]->{current_border_width}, 2, 'tiled current border width set to 2'); +is($tilewindow->rect->width, $tiled[0]->{rect}->{width} - 2*2, 'single tiled border width 2'); + +exit_gracefully($pid); + +##################################################################### +# 5: check that the borders are hidden on a workspace with one tiled +# window with no gaps +##################################################################### + +$config = <{nodes}}; +ok(@tiled == 1, 'one tiled container opened'); +is($tiled[0]->{current_border_width}, 2, 'tiled current border width set to 2'); +is($tilewindow->rect->width, $tiled[0]->{rect}->{width} - 2*0, 'single tiled border width 0'); + +exit_gracefully($pid); + + + done_testing; From 2481301dfccd3eb4f4b4e83972b6eaff18562024 Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Sat, 18 Nov 2017 09:48:38 -0800 Subject: [PATCH 43/99] fix typo in src/main.c --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index d87b9a29b..98dba12c8 100644 --- a/src/main.c +++ b/src/main.c @@ -921,7 +921,7 @@ int main(int argc, char *argv[]) { free(command); } - /* Make sure to destroy the event loop to invoke the cleeanup callbacks + /* Make sure to destroy the event loop to invoke the cleanup callbacks * when calling exit() */ atexit(i3_exit); From c2ae8697b50ac907ffcd7d261087d94d349c053e Mon Sep 17 00:00:00 2001 From: Ryan Geary Date: Wed, 22 Nov 2017 09:34:53 -0500 Subject: [PATCH 44/99] Removed repeated phrase in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index abd44fb97..93a880158 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Please refer to the [wiki](https://github.com/Airblader/i3/wiki/Compiling-&-Inst Note: Gaps will only work correctly if you disable window titlebars entirely. Unfortunately this is necessary due to the way i3 creates such bars on windows. You can disable them via `for_window [class="^.*"] border pixel 0` in your config. You can also use any non-zero value as long as you only use pixel-style borders. -Based on the patches provided by o4dev and jeanbroid, i3 supports gaps between containers. I extended those patches further to make changing the gaps size easier during runtime and also to expose more functionality for binding it to keys. Additionally, the gaps patch was fixed such that the gaps between containers and the gaps between containers and the edge of the screen are the same. But I didn't stop there: these gaps are called "inner" gaps. This fork also allows setting "outer" gaps which inset all containers independently. +Based on the patches provided by o4dev and jeanbroid, i3 supports gaps between containers. I extended those patches further to make changing the gaps size easier during runtime and also to expose more functionality for binding it to keys. Additionally, the gaps patch was fixed such that the gaps between containers and the edge of the screen are the same. But I didn't stop there: these gaps are called "inner" gaps. This fork also allows setting "outer" gaps which inset all containers independently. In your i3 config, you can set a global gap size as shown below. This is the default value that will be used for all workspaces: From 927c2aeac8255db8746c1ba77b5a0bf5dede3564 Mon Sep 17 00:00:00 2001 From: Ryan Geary Date: Wed, 22 Nov 2017 09:58:31 -0500 Subject: [PATCH 45/99] Clarify correction --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93a880158..b905b4506 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Please refer to the [wiki](https://github.com/Airblader/i3/wiki/Compiling-&-Inst Note: Gaps will only work correctly if you disable window titlebars entirely. Unfortunately this is necessary due to the way i3 creates such bars on windows. You can disable them via `for_window [class="^.*"] border pixel 0` in your config. You can also use any non-zero value as long as you only use pixel-style borders. -Based on the patches provided by o4dev and jeanbroid, i3 supports gaps between containers. I extended those patches further to make changing the gaps size easier during runtime and also to expose more functionality for binding it to keys. Additionally, the gaps patch was fixed such that the gaps between containers and the edge of the screen are the same. But I didn't stop there: these gaps are called "inner" gaps. This fork also allows setting "outer" gaps which inset all containers independently. +Based on the patches provided by o4dev and jeanbroid, i3 supports gaps between containers. I extended those patches further to make changing the gaps size easier during runtime and also to expose more functionality for binding it to keys. Additionally, the gaps patch was fixed such that inner gaps (the gaps between adjacent containers) and outer gaps (gaps between the edge of the screen and a container) are the same. But I didn't stop there: these gaps are called "inner" gaps. This fork also allows setting "outer" gaps which inset all containers independently. In your i3 config, you can set a global gap size as shown below. This is the default value that will be used for all workspaces: From 865bd462b4d71c6e0d5edcae148a5b6a6c208ffb Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Thu, 23 Nov 2017 15:41:33 -0800 Subject: [PATCH 46/99] do not check for NULL in FREE macro free(3) is safe to invoke on a NULL pointer, in which case no action is taken. This change adjusts the FREE macros to omit this unnecessary check. --- i3-config-wizard/main.c | 10 ++++------ i3-input/i3-input.h | 10 ++++------ i3-nagbar/i3-nagbar.h | 10 ++++------ i3bar/include/util.h | 10 ++++------ include/util.h | 10 ++++------ 5 files changed, 20 insertions(+), 30 deletions(-) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index dd58fd124..cbea624e3 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -58,12 +58,10 @@ #error "SYSCONFDIR not defined" #endif -#define FREE(pointer) \ - do { \ - if (pointer != NULL) { \ - free(pointer); \ - pointer = NULL; \ - } \ +#define FREE(pointer) \ + do { \ + free(pointer); \ + pointer = NULL; \ } while (0) #include "xcb.h" diff --git a/i3-input/i3-input.h b/i3-input/i3-input.h index d347506fe..d7aae66b5 100644 --- a/i3-input/i3-input.h +++ b/i3-input/i3-input.h @@ -5,12 +5,10 @@ #include #define die(...) errx(EXIT_FAILURE, __VA_ARGS__); -#define FREE(pointer) \ - do { \ - if (pointer != NULL) { \ - free(pointer); \ - pointer = NULL; \ - } \ +#define FREE(pointer) \ + do { \ + free(pointer); \ + pointer = NULL; \ } while (0) extern xcb_window_t root; diff --git a/i3-nagbar/i3-nagbar.h b/i3-nagbar/i3-nagbar.h index c5e94cc61..cb672bead 100644 --- a/i3-nagbar/i3-nagbar.h +++ b/i3-nagbar/i3-nagbar.h @@ -5,12 +5,10 @@ #include #define die(...) errx(EXIT_FAILURE, __VA_ARGS__); -#define FREE(pointer) \ - do { \ - if (pointer != NULL) { \ - free(pointer); \ - pointer = NULL; \ - } \ +#define FREE(pointer) \ + do { \ + free(pointer); \ + pointer = NULL; \ } while (0) #define xmacro(atom) xcb_atom_t A_##atom; diff --git a/i3bar/include/util.h b/i3bar/include/util.h index 3af79ed77..1f5636110 100644 --- a/i3bar/include/util.h +++ b/i3bar/include/util.h @@ -20,12 +20,10 @@ #define STARTS_WITH(string, len, needle) (((len) >= strlen((needle))) && strncasecmp((string), (needle), strlen((needle))) == 0) /* Securely free p */ -#define FREE(p) \ - do { \ - if (p != NULL) { \ - free(p); \ - p = NULL; \ - } \ +#define FREE(p) \ + do { \ + free(p); \ + p = NULL; \ } while (0) /* Securely free single-linked list */ diff --git a/include/util.h b/include/util.h index 526ab8810..3547d8d7b 100644 --- a/include/util.h +++ b/include/util.h @@ -47,12 +47,10 @@ break; \ } -#define FREE(pointer) \ - do { \ - if (pointer != NULL) { \ - free(pointer); \ - pointer = NULL; \ - } \ +#define FREE(pointer) \ + do { \ + free(pointer); \ + pointer = NULL; \ } while (0) #define CALL(obj, member, ...) obj->member(obj, ##__VA_ARGS__) From c07936d91b38863f4e404a73aebe8a02820e4dfc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 26 Nov 2017 16:41:59 +0100 Subject: [PATCH 47/99] no-op refactoring: make ipc_connect find socket path --- i3-config-wizard/main.c | 9 +-------- i3-input/main.c | 9 +-------- i3-msg/main.c | 30 +++--------------------------- libi3/ipc_connect.c | 25 ++++++++++++++++++++++--- src/display_version.c | 18 +----------------- 5 files changed, 28 insertions(+), 63 deletions(-) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index dd58fd124..97c2a602f 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -94,7 +94,7 @@ static xcb_get_modifier_mapping_reply_t *modmap_reply; static i3Font font; static i3Font bold_font; static int char_width; -static char *socket_path; +static char *socket_path = NULL; static xcb_window_t win; static surface_t surface; static xcb_key_symbols_t *symbols; @@ -744,7 +744,6 @@ static void finish() { int main(int argc, char *argv[]) { char *xdg_config_home; - socket_path = getenv("I3SOCK"); char *pattern = "pango:monospace 8"; char *patternbold = "pango:monospace bold 8"; int o, option_index = 0; @@ -824,12 +823,6 @@ int main(int argc, char *argv[]) { &xkb_base_error) != 1) errx(EXIT_FAILURE, "Could not setup XKB extension."); - if (socket_path == NULL) - socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen); - - if (socket_path == NULL) - socket_path = "/tmp/i3-ipc.sock"; - keysyms = xcb_key_symbols_alloc(conn); xcb_get_modifier_mapping_cookie_t modmap_cookie; modmap_cookie = xcb_get_modifier_mapping(conn); diff --git a/i3-input/main.c b/i3-input/main.c index 785a133fc..efb7b20c2 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -41,7 +41,6 @@ * the command will be sent to i3 */ static char *format; -static char *socket_path; static int sockfd; static xcb_key_symbols_t *symbols; static bool modeswitch_active = false; @@ -374,7 +373,7 @@ static xcb_rectangle_t get_window_position(void) { int main(int argc, char *argv[]) { format = sstrdup("%s"); - socket_path = getenv("I3SOCK"); + char *socket_path = NULL; char *pattern = sstrdup("pango:monospace 8"); int o, option_index = 0; @@ -438,12 +437,6 @@ int main(int argc, char *argv[]) { if (!conn || xcb_connection_has_error(conn)) die("Cannot open display\n"); - if (socket_path == NULL) - socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen); - - if (socket_path == NULL) - socket_path = "/tmp/i3-ipc.sock"; - sockfd = ipc_connect(socket_path); root_screen = xcb_aux_get_screen(conn, screen); diff --git a/i3-msg/main.c b/i3-msg/main.c index a4f948500..91a714e56 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -38,8 +38,6 @@ #include -static char *socket_path; - /* * Having verboselog() and errorlog() is necessary when using libi3. * @@ -161,11 +159,7 @@ int main(int argc, char *argv[]) { if (pledge("stdio rpath unix", NULL) == -1) err(EXIT_FAILURE, "pledge"); #endif - char *env_socket_path = getenv("I3SOCK"); - if (env_socket_path) - socket_path = sstrdup(env_socket_path); - else - socket_path = NULL; + char *socket_path = NULL; int o, option_index = 0; uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND; char *payload = NULL; @@ -183,8 +177,7 @@ int main(int argc, char *argv[]) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { if (o == 's') { - if (socket_path != NULL) - free(socket_path); + free(socket_path); socket_path = sstrdup(optarg); } else if (o == 't') { if (strcasecmp(optarg, "command") == 0) { @@ -228,13 +221,6 @@ int main(int argc, char *argv[]) { } } - if (socket_path == NULL) - socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0); - - /* Fall back to the default socket path */ - if (socket_path == NULL) - socket_path = sstrdup("/tmp/i3-ipc.sock"); - /* Use all arguments, separated by whitespace, as payload. * This way, you don’t have to do i3-msg 'mark foo', you can use * i3-msg mark foo */ @@ -253,17 +239,7 @@ int main(int argc, char *argv[]) { if (!payload) payload = sstrdup(""); - int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); - if (sockfd == -1) - err(EXIT_FAILURE, "Could not create socket"); - - struct sockaddr_un addr; - memset(&addr, 0, sizeof(struct sockaddr_un)); - addr.sun_family = AF_LOCAL; - strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); - if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) - err(EXIT_FAILURE, "Could not connect to i3 on socket \"%s\"", socket_path); - + int sockfd = ipc_connect(socket_path); if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1) err(EXIT_FAILURE, "IPC: write()"); free(payload); diff --git a/libi3/ipc_connect.c b/libi3/ipc_connect.c index 2e6283425..f659a1a47 100644 --- a/libi3/ipc_connect.c +++ b/libi3/ipc_connect.c @@ -22,6 +22,25 @@ * */ int ipc_connect(const char *socket_path) { + char *path = NULL; + if (socket_path != NULL) { + path = sstrdup(socket_path); + } + + if (path == NULL) { + if ((path = getenv("I3SOCK")) != NULL) { + path = sstrdup(path); + } + } + + if (path == NULL) { + path = root_atom_contents("I3_SOCKET_PATH", NULL, 0); + } + + if (path == NULL) { + path = sstrdup("/tmp/i3-ipc.sock"); + } + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); if (sockfd == -1) err(EXIT_FAILURE, "Could not create socket"); @@ -31,9 +50,9 @@ int ipc_connect(const char *socket_path) { struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_LOCAL; - strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) - err(EXIT_FAILURE, "Could not connect to i3"); - + err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path); + free(path); return sockfd; } diff --git a/src/display_version.c b/src/display_version.c index 764ee7531..2e05cafa8 100644 --- a/src/display_version.c +++ b/src/display_version.c @@ -55,10 +55,6 @@ static yajl_callbacks version_callbacks = { * */ void display_running_version(void) { - char *socket_path = root_atom_contents("I3_SOCKET_PATH", conn, conn_screen); - if (socket_path == NULL) - exit(EXIT_SUCCESS); - char *pid_from_atom = root_atom_contents("I3_PID", conn, conn_screen); if (pid_from_atom == NULL) { /* If I3_PID is not set, the running version is older than 4.2-200. */ @@ -71,18 +67,7 @@ void display_running_version(void) { printf("(Getting version from running i3, press ctrl-c to abort…)"); fflush(stdout); - /* TODO: refactor this with the code for sending commands */ - int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); - if (sockfd == -1) - err(EXIT_FAILURE, "Could not create socket"); - - struct sockaddr_un addr; - memset(&addr, 0, sizeof(struct sockaddr_un)); - addr.sun_family = AF_LOCAL; - strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); - if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) - err(EXIT_FAILURE, "Could not connect to i3"); - + int sockfd = ipc_connect(NULL); if (ipc_send_message(sockfd, 0, I3_IPC_MESSAGE_TYPE_GET_VERSION, (uint8_t *)"") == -1) err(EXIT_FAILURE, "IPC: write()"); @@ -184,5 +169,4 @@ void display_running_version(void) { yajl_free(handle); free(reply); free(pid_from_atom); - free(socket_path); } From 1facb450c0088d662c4c76ee97f0b0151f746288 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 26 Nov 2017 17:26:40 +0100 Subject: [PATCH 48/99] i3-dump-log: enable shmlog on demand fixes #3055 --- i3-dump-log/main.c | 92 +++++++++++++++++++++++++++++++++------- testcases/t/207-shmlog.t | 2 +- 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/i3-dump-log/main.c b/i3-dump-log/main.c index 478af310c..e9901f8ee 100644 --- a/i3-dump-log/main.c +++ b/i3-dump-log/main.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "libi3.h" #include "shmlog.h" @@ -38,6 +39,29 @@ static uint32_t wrap_count; static i3_shmlog_header *header; static char *logbuffer, *walk; +static int ipcfd = -1; + +static volatile bool interrupted = false; + +static void sighandler(int signal) { + interrupted = true; +} + +static void disable_shmlog(void) { + const char *disablecmd = "debuglog off; shmlog off"; + if (ipc_send_message(ipcfd, strlen(disablecmd), + I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)disablecmd) != 0) + err(EXIT_FAILURE, "IPC send"); + + /* Ensure the command was sent by waiting for the reply: */ + uint32_t reply_length = 0; + uint8_t *reply = NULL; + if (ipc_recv_message(ipcfd, I3_IPC_REPLY_TYPE_COMMAND, + &reply_length, &reply) != 0) { + err(EXIT_FAILURE, "IPC recv"); + } + free(reply); +} static int check_for_wrap(void) { if (wrap_count == header->wrap_count) @@ -59,6 +83,14 @@ static void print_till_end(void) { walk += len; } +void errorlog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + int main(int argc, char *argv[]) { int o, option_index = 0; bool verbose = false; @@ -123,15 +155,35 @@ int main(int argc, char *argv[]) { exit(1); } if (root_atom_contents("I3_CONFIG_PATH", conn, screen) != NULL) { - fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled.\n\n"); - if (!is_debug_build()) { + fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled. Enabling SHM log until cancelled\n\n"); + ipcfd = ipc_connect(NULL); + const char *enablecmd = "debuglog on; shmlog 5242880"; + if (ipc_send_message(ipcfd, strlen(enablecmd), + I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)enablecmd) != 0) + err(EXIT_FAILURE, "IPC send"); + /* By the time we receive a reply, I3_SHMLOG_PATH is set: */ + uint32_t reply_length = 0; + uint8_t *reply = NULL; + if (ipc_recv_message(ipcfd, I3_IPC_REPLY_TYPE_COMMAND, + &reply_length, &reply) != 0) { + err(EXIT_FAILURE, "IPC recv"); + } + free(reply); + + atexit(disable_shmlog); + + /* Retry: */ + shmname = root_atom_contents("I3_SHMLOG_PATH", NULL, 0); + if (shmname == NULL && !is_debug_build()) { fprintf(stderr, "You seem to be using a release version of i3:\n %s\n\n", I3_VERSION); fprintf(stderr, "Release versions do not use SHM logging by default,\ntherefore i3-dump-log does not work.\n\n"); fprintf(stderr, "Please follow this guide instead:\nhttps://i3wm.org/docs/debugging-release-version.html\n"); exit(1); } } - errx(EXIT_FAILURE, "Cannot get I3_SHMLOG_PATH atom contents. Is i3 running on this display?"); + if (shmname == NULL) { + errx(EXIT_FAILURE, "Cannot get I3_SHMLOG_PATH atom contents. Is i3 running on this display?"); + } } if (*shmname == '\0') @@ -182,22 +234,32 @@ int main(int argc, char *argv[]) { print_till_end(); #if !defined(__OpenBSD__) - if (follow) { - /* Since pthread_cond_wait() expects a mutex, we need to provide one. + if (!follow) { + return 0; + } + + /* Handle SIGINT gracefully to invoke atexit handlers, if any. */ + struct sigaction action; + action.sa_handler = sighandler; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + sigaction(SIGINT, &action, NULL); + + /* Since pthread_cond_wait() expects a mutex, we need to provide one. * To not lock i3 (that’s bad, mhkay?) we just define one outside of * the shared memory. */ - pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER; - pthread_mutex_lock(&dummy_mutex); - while (1) { - pthread_cond_wait(&(header->condvar), &dummy_mutex); - /* If this was not a spurious wakeup, print the new lines. */ - if (header->offset_next_write != offset_next_write) { - offset_next_write = header->offset_next_write; - print_till_end(); - } + pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER; + pthread_mutex_lock(&dummy_mutex); + while (!interrupted) { + pthread_cond_wait(&(header->condvar), &dummy_mutex); + /* If this was not a spurious wakeup, print the new lines. */ + if (header->offset_next_write != offset_next_write) { + offset_next_write = header->offset_next_write; + print_till_end(); } } -#endif +#endif + exit(0); return 0; } diff --git a/testcases/t/207-shmlog.t b/testcases/t/207-shmlog.t index 1351d568d..c2b2ebaa3 100644 --- a/testcases/t/207-shmlog.t +++ b/testcases/t/207-shmlog.t @@ -54,7 +54,7 @@ like($stderr, qr#^$#, 'stderr empty'); # 3: change size of the shared memory log buffer and verify old content is gone ################################################################################ -cmd 'shmlog ' . (23 * 1024 * 1024); +cmd 'shmlog ' . (1 * 1024 * 1024); run [ 'i3-dump-log' ], '>', \$stdout, From 02b237b14dffd42bbafca34a5acaff8ebff88141 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 26 Nov 2017 18:07:13 +0100 Subject: [PATCH 49/99] shmlog: remote atom when disabled --- src/x.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/x.c b/src/x.c index 09a604931..7829079bb 100644 --- a/src/x.c +++ b/src/x.c @@ -1227,9 +1227,13 @@ void x_set_name(Con *con, const char *name) { * */ void update_shmlog_atom() { - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, - A_I3_SHMLOG_PATH, A_UTF8_STRING, 8, - strlen(shmlogname), shmlogname); + if (*shmlogname == '\0') { + xcb_delete_property(conn, root, A_I3_SHMLOG_PATH); + } else { + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, + A_I3_SHMLOG_PATH, A_UTF8_STRING, 8, + strlen(shmlogname), shmlogname); + } } /* From 3e34122de4037b9ddef128e1ed999f0eff71edf6 Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Tue, 28 Nov 2017 23:29:47 -0800 Subject: [PATCH 50/99] Perform proper cleanup for signals with 'Term' action (#3057) Issue #3049 describes a case where terminating i3 by means of SIGTERM causes it to leak the runtime directory and all its contents. There are multiple issues at play: first, any cleanup handlers registered via atexit are never invoked when a signal terminates the program (see atexit(3)). Hence, the log SHM log cleanup performed in i3_exit is not invoked in that case. Second, compared to the shutdown path for the 'exit' command, we do not unlink the UNIX domain socket we create, causing it to be leaked as well. Third, a handler for SIGTERM is not registered at all despite handle_signal claiming to be the handler for all 'Term' signals. This change addresses all three problems and results in a graceful exit including cleanup to happen when we receive a signal with the default action 'Term'. It addresses issue #3049. --- src/main.c | 68 +++++++++++++++++++++++-------- testcases/lib/i3test.pm.in | 47 ++++++++++++++++++++- testcases/t/540-sigterm-cleanup.t | 35 ++++++++++++++++ 3 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 testcases/t/540-sigterm-cleanup.t diff --git a/src/main.c b/src/main.c index 98dba12c8..b634b139f 100644 --- a/src/main.c +++ b/src/main.c @@ -174,21 +174,64 @@ static void i3_exit(void) { fflush(stderr); shm_unlink(shmlogname); } + ipc_shutdown(SHUTDOWN_REASON_EXIT); + unlink(config.ipc_socket_path); } /* - * (One-shot) Handler for all signals with default action "Term", see signal(7) + * (One-shot) Handler for all signals with default action "Core", see signal(7) * * Unlinks the SHM log and re-raises the signal. * */ -static void handle_signal(int sig, siginfo_t *info, void *data) { +static void handle_core_signal(int sig, siginfo_t *info, void *data) { if (*shmlogname != '\0') { shm_unlink(shmlogname); } raise(sig); } +/* + * (One-shot) Handler for all signals with default action "Term", see signal(7) + * + * Exits the program gracefully. + * + */ +static void handle_term_signal(struct ev_loop *loop, ev_signal *signal, int revents) { + /* We exit gracefully here in the sense that cleanup handlers + * installed via atexit are invoked. */ + exit(128 + signal->signum); +} + +/* + * Set up handlers for all signals with default action "Term", see signal(7) + * + */ +static void setup_term_handlers(void) { + static struct ev_signal signal_watchers[6]; + size_t num_watchers = sizeof(signal_watchers) / sizeof(signal_watchers[0]); + + /* We have to rely on libev functionality here and should not use + * sigaction handlers because we need to invoke the exit handlers + * and cannot do so from an asynchronous signal handling context as + * not all code triggered during exit is signal safe (and exiting + * the main loop from said handler is not easily possible). libev's + * signal handlers does not impose such a constraint on us. */ + ev_signal_init(&signal_watchers[0], handle_term_signal, SIGHUP); + ev_signal_init(&signal_watchers[1], handle_term_signal, SIGINT); + ev_signal_init(&signal_watchers[2], handle_term_signal, SIGALRM); + ev_signal_init(&signal_watchers[3], handle_term_signal, SIGTERM); + ev_signal_init(&signal_watchers[4], handle_term_signal, SIGUSR1); + ev_signal_init(&signal_watchers[5], handle_term_signal, SIGUSR1); + for (size_t i = 0; i < num_watchers; i++) { + ev_signal_start(main_loop, &signal_watchers[i]); + /* The signal handlers should not block ev_run from returning + * and so none of the signal handlers should hold a reference to + * the main loop. */ + ev_unref(main_loop); + } +} + int main(int argc, char *argv[]) { /* Keep a symbol pointing to the I3_VERSION string constant so that we have * it in gdb backtraces. */ @@ -853,15 +896,15 @@ int main(int argc, char *argv[]) { err(EXIT_FAILURE, "pledge"); #endif - struct sigaction action; - - action.sa_sigaction = handle_signal; - action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; - sigemptyset(&action.sa_mask); - if (!disable_signalhandler) setup_signal_handler(); else { + struct sigaction action; + + action.sa_sigaction = handle_core_signal; + action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; + sigemptyset(&action.sa_mask); + /* Catch all signals with default action "Core", see signal(7) */ if (sigaction(SIGQUIT, &action, NULL) == -1 || sigaction(SIGILL, &action, NULL) == -1 || @@ -871,14 +914,7 @@ int main(int argc, char *argv[]) { ELOG("Could not setup signal handler.\n"); } - /* Catch all signals with default action "Term", see signal(7) */ - if (sigaction(SIGHUP, &action, NULL) == -1 || - sigaction(SIGINT, &action, NULL) == -1 || - sigaction(SIGALRM, &action, NULL) == -1 || - sigaction(SIGUSR1, &action, NULL) == -1 || - sigaction(SIGUSR2, &action, NULL) == -1) - ELOG("Could not setup signal handler.\n"); - + setup_term_handlers(); /* Ignore SIGPIPE to survive errors when an IPC client disconnects * while we are sending them a message */ signal(SIGPIPE, SIG_IGN); diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index ed239241c..e754c0c17 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -12,6 +12,7 @@ use AnyEvent::I3; use List::Util qw(first); use Time::HiRes qw(sleep); use Cwd qw(abs_path); +use POSIX ':sys_wait_h'; use Scalar::Util qw(blessed); use SocketActivation; use i3test::Util qw(slurp); @@ -37,6 +38,7 @@ our @EXPORT = qw( cmd sync_with_i3 exit_gracefully + exit_forcefully workspace_exists focused_ws get_socket_path @@ -123,7 +125,7 @@ END { } else { kill(-9, $i3_pid) - or $tester->BAIL_OUT("could not kill i3"); + or $tester->BAIL_OUT("could not kill i3: $!"); waitpid $i3_pid, 0; } @@ -759,7 +761,7 @@ sub exit_gracefully { if (!$exited) { kill(9, $pid) - or $tester->BAIL_OUT("could not kill i3"); + or $tester->BAIL_OUT("could not kill i3: $!"); } if ($socketpath =~ m,^/tmp/i3-test-socket-,) { @@ -770,6 +772,47 @@ sub exit_gracefully { undef $i3_pid; } +=head2 exit_forcefully($pid, [ $signal ]) + +Tries to exit i3 forcefully by sending a signal (defaults to SIGTERM). + +You only need to use this function if you want to test signal handling +(in which case you must have launched i3 on your own with +C). + + use i3test i3_autostart => 0; + my $pid = launch_with_config($config); + # … + exit_forcefully($pid); + +=cut +sub exit_forcefully { + my ($pid, $signal) = @_; + $signal ||= 'TERM'; + + # Send the given signal to the i3 instance and wait for up to 10s + # for it to terminate. + kill($signal, $pid) + or $tester->BAIL_OUT("could not kill i3: $!"); + my $status; + my $timeout = 10; + do { + $status = waitpid $pid, WNOHANG; + + if ($status <= 0) { + sleep(1); + $timeout--; + } + } while ($status <= 0 && $timeout > 0); + + if ($status <= 0) { + kill('KILL', $pid) + or $tester->BAIL_OUT("could not kill i3: $!"); + waitpid $pid, 0; + } + undef $i3_pid; +} + =head2 get_socket_path([ $cache ]) Gets the socket path from the C atom stored on the X11 root diff --git a/testcases/t/540-sigterm-cleanup.t b/testcases/t/540-sigterm-cleanup.t new file mode 100644 index 000000000..5e5b9bf35 --- /dev/null +++ b/testcases/t/540-sigterm-cleanup.t @@ -0,0 +1,35 @@ +#!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) +# +# Tests that the socket file is cleaned up properly after gracefully +# shutting down i3 via SIGTERM. +# Ticket: #3049 +use i3test i3_autostart => 0; + +my $config = < 1); +my $socket = get_socket_path(); +ok(-S $socket, "socket $socket exists"); + +exit_forcefully($pid, 'TERM'); + +ok(!-e $socket, "socket $socket no longer exists"); + +done_testing; From e3f8939a58d75d58ccfac8cd1309994cb02832e7 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 1 Dec 2017 16:12:43 +0200 Subject: [PATCH 51/99] Skip internal workspaces with 'move workspace to output' Fixes #3064. --- src/commands.c | 4 +++ testcases/t/504-move-workspace-to-output.t | 30 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/commands.c b/src/commands.c index e68fcd802..a9187866c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1121,6 +1121,10 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) { owindow *current; TAILQ_FOREACH(current, &owindows, owindows) { Con *ws = con_get_workspace(current->con); + if (con_is_internal(ws)) { + continue; + } + bool success = workspace_move_to_output(ws, name); if (!success) { ELOG("Failed to move workspace to output.\n"); diff --git a/testcases/t/504-move-workspace-to-output.t b/testcases/t/504-move-workspace-to-output.t index 8e48bafe3..86bad5e50 100644 --- a/testcases/t/504-move-workspace-to-output.t +++ b/testcases/t/504-move-workspace-to-output.t @@ -179,6 +179,36 @@ cmd '[con_mark=marked] move workspace to output current'; ($x0, $x1) = workspaces_per_screen(); ok($ws1 ~~ @$x0, 'ws1 on fake-0'); +################################################################################ +# Verify that '[class=".*"] move workspace to output' doesn't fail when +# containers in the scratchpad are matched. +# See issue: #3064. +################################################################################ +my $__i3_scratch = get_ws('__i3_scratch'); +is(scalar @{$__i3_scratch->{floating_nodes}}, 0, 'scratchpad is empty'); + +my $ws0 = fresh_workspace(output => 0); +open_window(wm_class => 'a'); + +my $ws1 = fresh_workspace(output => 1); +open_window(wm_class => 'b'); +my $scratchpad_window = open_window(wm_class => 'c'); +cmd 'move to scratchpad'; + +($x0, $x1) = workspaces_per_screen(); +ok($ws0 ~~ @$x0, 'ws0 on fake-0'); +ok($ws1 ~~ @$x1, 'ws1 on fake-1'); + +my $reply = cmd '[class=".*"] move workspace to output fake-1'; +ok($reply->[0]->{success}, 'move successful'); + +($x0, $x1) = workspaces_per_screen(); +ok($ws0 ~~ @$x1, 'ws0 on fake-1'); +ok($ws1 ~~ @$x1, 'ws1 on fake-1'); + +$__i3_scratch = get_ws('__i3_scratch'); +is(scalar @{$__i3_scratch->{floating_nodes}}, 1, 'window still in scratchpad'); + ################################################################################ done_testing; From 54c79e4b2f17a4fb5b88d96e732022cf22a00ef3 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 26 Sep 2017 14:50:26 +0300 Subject: [PATCH 52/99] i3bar: free output data structures --- i3bar/include/outputs.h | 6 ++++++ i3bar/src/ipc.c | 7 ++----- i3bar/src/main.c | 2 -- i3bar/src/outputs.c | 35 +++++++++++++++++++++++++++++++---- i3bar/src/xcb.c | 11 +---------- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index de960270e..29a7bcd38 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -33,6 +33,12 @@ void parse_outputs_json(char* json); */ void init_outputs(void); +/* + * free() all outputs data structures. + * + */ +void free_outputs(void); + /* * Returns the output with the given name * diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 49c729ae9..cc3563ec0 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -64,17 +64,14 @@ void got_subscribe_reply(char *reply) { */ void got_output_reply(char *reply) { DLOG("Clearing old output configuration...\n"); - i3_output *o_walk; - SLIST_FOREACH(o_walk, outputs, slist) { - destroy_window(o_walk); - } - FREE_SLIST(outputs, i3_output); + free_outputs(); DLOG("Parsing outputs JSON...\n"); parse_outputs_json(reply); DLOG("Reconfiguring windows...\n"); reconfig_windows(false); + i3_output *o_walk; SLIST_FOREACH(o_walk, outputs, slist) { kick_tray_clients(o_walk); } diff --git a/i3bar/src/main.c b/i3bar/src/main.c index 910e95248..069803d4f 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -182,7 +182,5 @@ int main(int argc, char **argv) { clean_xcb(); ev_default_destroy(); - free_workspaces(); - return 0; } diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index bd056a700..4c9ce6513 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -173,6 +173,12 @@ static int outputs_start_map_cb(void *params_) { return 1; } +static void clear_output(i3_output *output) { + FREE(output->name); + FREE(output->workspaces); + FREE(output->trayclients); +} + /* * We hit the end of a map (rect or a new output) * @@ -199,9 +205,7 @@ static int outputs_end_map_cb(void *params_) { if (!handle_output) { DLOG("Ignoring output \"%s\", not configured to handle it.\n", params->outputs_walk->name); - FREE(params->outputs_walk->name); - FREE(params->outputs_walk->workspaces); - FREE(params->outputs_walk->trayclients); + clear_output(params->outputs_walk); FREE(params->outputs_walk); FREE(params->cur_key); return 1; @@ -217,6 +221,9 @@ static int outputs_end_map_cb(void *params_) { target->primary = params->outputs_walk->primary; target->ws = params->outputs_walk->ws; target->rect = params->outputs_walk->rect; + + clear_output(params->outputs_walk); + FREE(params->outputs_walk); } return 1; } @@ -260,7 +267,6 @@ void init_outputs(void) { */ void parse_outputs_json(char *json) { struct outputs_json_params params; - params.outputs_walk = NULL; params.cur_key = NULL; params.json = json; @@ -286,6 +292,27 @@ void parse_outputs_json(char *json) { yajl_free(handle); } +/* + * free() all outputs data structures. + * + */ +void free_outputs(void) { + free_workspaces(); + + i3_output *outputs_walk; + if (outputs == NULL) { + return; + } + SLIST_FOREACH(outputs_walk, outputs, slist) { + destroy_window(outputs_walk); + if (outputs_walk->trayclients != NULL && !TAILQ_EMPTY(outputs_walk->trayclients)) { + FREE_TAILQ(outputs_walk->trayclients, trayclient); + } + clear_output(outputs_walk); + } + FREE_SLIST(outputs, i3_output); +} + /* * Returns the output with the given name * diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index dfa66ee2d..815f41fdf 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1517,16 +1517,7 @@ void init_tray_colors(void) { * */ void clean_xcb(void) { - i3_output *o_walk; - free_workspaces(); - SLIST_FOREACH(o_walk, outputs, slist) { - destroy_window(o_walk); - FREE(o_walk->trayclients); - FREE(o_walk->workspaces); - FREE(o_walk->name); - } - FREE_SLIST(outputs, i3_output); - FREE(outputs); + free_outputs(); free_font(); From b6bbb91479a5e51274d8ac1e5b743ccd8a88de6e Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 5 Dec 2017 00:22:14 +0200 Subject: [PATCH 53/99] complete-run.pl: accept the xvfb option from command line --- testcases/complete-run.pl.in | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/testcases/complete-run.pl.in b/testcases/complete-run.pl.in index b7b398720..96b93bed0 100755 --- a/testcases/complete-run.pl.in +++ b/testcases/complete-run.pl.in @@ -67,7 +67,7 @@ my $result = GetOptions( "valgrind" => \$options{valgrind}, "strace" => \$options{strace}, "xtrace" => \$options{xtrace}, - "xvfb" => \$options{xvfb}, + "xvfb!" => \$options{xvfb}, "display=s" => \@displays, "parallel=i" => \$parallel, "help|?" => \$help, @@ -485,6 +485,12 @@ C. Runs i3 under xtrace to trace X11 requests/replies. The output will be available in C. +=item B<--xvfb> + +=item B<--no-xvfb> + +Enable or disable running tests under Xvfb. Enabled by default. + =item B<--coverage-testing> Generates a test coverage report at C. Exits i3 cleanly From 315ff17563fd703b2f5117b2ec4d46e89389d323 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 8 Dec 2017 02:23:15 +0200 Subject: [PATCH 54/99] Add '--release' flag for bindsym in the bar block i3bar's handle_button is modified to also handle XCB_BUTTON_RELEASE events. During these button release events, only custom commands are checked to avoid sending multiple workspace ipc messages. The way this patch is implemented will allow to assign a custom command for both the press and release of the same button: bar { ... bindsym buttonX exec command1 bindsym --release buttonX exec command2 } Fixes #3068. --- docs/userguide | 4 ++- i3bar/include/configuration.h | 1 + i3bar/src/config.c | 15 +++++++++ i3bar/src/xcb.c | 33 ++++++++++++++------ include/config_directives.h | 2 +- include/configuration.h | 3 ++ parser-specs/config.spec | 6 +++- src/config_directives.c | 14 +++++---- src/ipc.c | 2 ++ testcases/t/525-i3bar-mouse-bindings.t | 42 ++++++++++++++++++++++++++ 10 files changed, 104 insertions(+), 18 deletions(-) diff --git a/docs/userguide b/docs/userguide index 7a1621da8..0258e2ab7 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1377,7 +1377,7 @@ and will be removed in a future release. We strongly recommend using the more ge *Syntax*: ---------------------------- -bindsym button +bindsym [--release] button ---------------------------- *Example*: @@ -1385,6 +1385,8 @@ bindsym button bar { # disable clicking on workspace buttons bindsym button1 nop + # Take a screenshot by right clicking on the bar + bindsym --release button3 exec --no-startup-id import /tmp/latest-screenshot.png # execute custom script when scrolling downwards bindsym button5 exec ~/.i3/scripts/custom_wheel_down } diff --git a/i3bar/include/configuration.h b/i3bar/include/configuration.h index e77e891b9..61cac7f6b 100644 --- a/i3bar/include/configuration.h +++ b/i3bar/include/configuration.h @@ -27,6 +27,7 @@ typedef enum { M_DOCK = 0, typedef struct binding_t { int input_code; char *command; + bool release; TAILQ_ENTRY(binding_t) bindings; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 79e106c07..a58b9bf80 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -264,6 +264,21 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len * */ static int config_boolean_cb(void *params_, int val) { + if (parsing_bindings) { + if (strcmp(cur_key, "release") == 0) { + binding_t *binding = TAILQ_LAST(&(config.bindings), bindings_head); + if (binding == NULL) { + ELOG("There is no binding to put the current command onto. This is a bug in i3.\n"); + return 0; + } + + binding->release = val; + return 1; + } + + ELOG("Unknown key \"%s\" while parsing bar bindings.\n", cur_key); + } + if (!strcmp(cur_key, "binding_mode_indicator")) { DLOG("binding_mode_indicator = %d\n", val); config.disable_binding_mode_indicator = !val; diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 1a9240fb9..77822544e 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -439,6 +439,18 @@ void init_colors(const struct xcb_color_strings_t *new_colors) { xcb_flush(xcb_connection); } +static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_release) { + binding_t *binding; + TAILQ_FOREACH(binding, &(config.bindings), bindings) { + if ((binding->input_code != input_code) || (binding->release != event_is_release)) + continue; + + i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command); + return true; + } + return false; +} + /* * Handle a button press event (i.e. a mouse click on one of our bars). * We determine, whether the click occurred on a workspace button or if the scroll- @@ -460,10 +472,16 @@ void handle_button(xcb_button_press_event_t *event) { return; } - int32_t x = event->event_x >= 0 ? event->event_x : 0; - DLOG("Got button %d\n", event->detail); + /* During button release events, only check for custom commands. */ + const bool event_is_release = (event->response_type & ~0x80) == XCB_BUTTON_RELEASE; + if (event_is_release) { + execute_custom_command(event->detail, event_is_release); + return; + } + + int32_t x = event->event_x >= 0 ? event->event_x : 0; int workspace_width = 0; i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk; @@ -516,12 +534,7 @@ void handle_button(xcb_button_press_event_t *event) { /* If a custom command was specified for this mouse button, it overrides * the default behavior. */ - binding_t *binding; - TAILQ_FOREACH(binding, &(config.bindings), bindings) { - if (binding->input_code != event->detail) - continue; - - i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command); + if (execute_custom_command(event->detail, event_is_release)) { return; } @@ -1164,6 +1177,7 @@ void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) { } break; + case XCB_BUTTON_RELEASE: case XCB_BUTTON_PRESS: /* Button press events are mouse buttons clicked on one of our bars */ handle_button((xcb_button_press_event_t *)event); @@ -1678,7 +1692,8 @@ void reconfig_windows(bool redraw_bars) { * */ values[3] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | - XCB_EVENT_MASK_BUTTON_PRESS; + XCB_EVENT_MASK_BUTTON_PRESS | + XCB_EVENT_MASK_BUTTON_RELEASE; if (config.hide_on_modifier == M_DOCK) { /* If the bar is normally visible, catch visibility change events to suspend * the status process when the bar is obscured by full-screened windows. */ diff --git a/include/config_directives.h b/include/config_directives.h index 1191a7c6e..60c7a4b14 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -84,7 +84,7 @@ CFGFUN(bar_verbose, const char *verbose); CFGFUN(bar_modifier, const char *modifier); CFGFUN(bar_wheel_up_cmd, const char *command); CFGFUN(bar_wheel_down_cmd, const char *command); -CFGFUN(bar_bindsym, const char *button, const char *command); +CFGFUN(bar_bindsym, const char *button, const char *release, const char *command); CFGFUN(bar_position, const char *position); CFGFUN(bar_i3bar_command, const char *i3bar_command); CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text); diff --git a/include/configuration.h b/include/configuration.h index 8f1ce3320..ac8001590 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -384,6 +384,9 @@ struct Barbinding { /** The command which is to be executed for this button. */ char *command; + /** If true, the command will be executed after the button is released. */ + bool release; + TAILQ_ENTRY(Barbinding) bindings; }; diff --git a/parser-specs/config.spec b/parser-specs/config.spec index c31567a64..3d3ffb283 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -501,12 +501,16 @@ state BAR_WHEEL_DOWN_CMD: -> call cfg_bar_wheel_down_cmd($command); BAR state BAR_BINDSYM: + release = '--release' + -> button = word -> BAR_BINDSYM_COMMAND state BAR_BINDSYM_COMMAND: + release = '--release' + -> command = string - -> call cfg_bar_bindsym($button, $command); BAR + -> call cfg_bar_bindsym($button, $release, $command); BAR state BAR_POSITION: position = 'top', 'bottom' diff --git a/src/config_directives.c b/src/config_directives.c index 1cf875ef0..a3b1f776c 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -502,7 +502,7 @@ CFGFUN(bar_modifier, const char *modifier) { current_bar->modifier = M_NONE; } -static void bar_configure_binding(const char *button, const char *command) { +static void bar_configure_binding(const char *button, const char *release, const char *command) { if (strncasecmp(button, "button", strlen("button")) != 0) { ELOG("Bindings for a bar can only be mouse bindings, not \"%s\", ignoring.\n", button); return; @@ -513,16 +513,18 @@ static void bar_configure_binding(const char *button, const char *command) { ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button); return; } + const bool release_bool = release != NULL; struct Barbinding *current; TAILQ_FOREACH(current, &(current_bar->bar_bindings), bindings) { - if (current->input_code == input_code) { + if (current->input_code == input_code && current->release == release_bool) { ELOG("command for button %s was already specified, ignoring.\n", button); return; } } struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding)); + new_binding->release = release_bool; new_binding->input_code = input_code; new_binding->command = sstrdup(command); TAILQ_INSERT_TAIL(&(current_bar->bar_bindings), new_binding, bindings); @@ -530,16 +532,16 @@ static void bar_configure_binding(const char *button, const char *command) { CFGFUN(bar_wheel_up_cmd, const char *command) { ELOG("'wheel_up_cmd' is deprecated. Please us 'bindsym button4 %s' instead.\n", command); - bar_configure_binding("button4", command); + bar_configure_binding("button4", NULL, command); } CFGFUN(bar_wheel_down_cmd, const char *command) { ELOG("'wheel_down_cmd' is deprecated. Please us 'bindsym button5 %s' instead.\n", command); - bar_configure_binding("button5", command); + bar_configure_binding("button5", NULL, command); } -CFGFUN(bar_bindsym, const char *button, const char *command) { - bar_configure_binding(button, command); +CFGFUN(bar_bindsym, const char *button, const char *release, const char *command) { + bar_configure_binding(button, release, command); } CFGFUN(bar_position, const char *position) { diff --git a/src/ipc.c b/src/ipc.c index 99b9e0ec6..a1a72b1ac 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -572,6 +572,8 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) { y(integer, current->input_code); ystr("command"); ystr(current->command); + ystr("release"); + y(bool, current->release == B_UPON_KEYRELEASE); y(map_close); } diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t index 57786deaf..875527850 100644 --- a/testcases/t/525-i3bar-mouse-bindings.t +++ b/testcases/t/525-i3bar-mouse-bindings.t @@ -30,6 +30,9 @@ bar { bindsym button3 focus left bindsym button4 focus right bindsym button5 focus left + bindsym --release button6 focus right + bindsym button7 focus left + bindsym button7 --release focus right } EOT use i3test::XTEST; @@ -142,4 +145,43 @@ subtest 'button 5 moves focus left', \&focus_subtest, [ $left->{id} ], 'button 5 moves focus left'; +# Test --release flag with bar bindsym. +# See issue: #3068. + +my $old_focus = get_focused($ws); +subtest 'button 6 does not move focus while pressed', \&focus_subtest, + sub { + xtest_button_press(6, 3, 3); + xtest_sync_with($i3bar_window); + }, + [], + 'button 6 does not move focus while pressed'; +is(get_focused($ws), $old_focus, 'focus unchanged'); + +subtest 'button 6 release moves focus right', \&focus_subtest, + sub { + xtest_button_release(6, 3, 3); + xtest_sync_with($i3bar_window); + }, + [ $right->{id} ], + 'button 6 release moves focus right'; + +# Test same bindsym button with and without --release. + +subtest 'button 7 press moves focus left', \&focus_subtest, + sub { + xtest_button_press(7, 3, 3); + xtest_sync_with($i3bar_window); + }, + [ $left->{id} ], + 'button 7 press moves focus left'; + +subtest 'button 7 release moves focus right', \&focus_subtest, + sub { + xtest_button_release(7, 3, 3); + xtest_sync_with($i3bar_window); + }, + [ $right->{id} ], + 'button 7 release moves focus right'; + done_testing; From d2b35388b4a4626f3c57f18a470bf1244eaee837 Mon Sep 17 00:00:00 2001 From: "Pawel S. Veselov" Date: Sat, 9 Dec 2017 15:12:25 +0100 Subject: [PATCH 55/99] Fixes #3072, Xft.dpi can be floating point --- libi3/dpi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libi3/dpi.c b/libi3/dpi.c index 93a3c6f6a..a2c40319a 100644 --- a/libi3/dpi.c +++ b/libi3/dpi.c @@ -43,12 +43,13 @@ void init_dpi(void) { } char *endptr; - dpi = strtol(resource, &endptr, 10); - if (dpi == LONG_MAX || dpi == LONG_MIN || dpi < 0 || *endptr != '\0' || endptr == resource) { + double in_dpi = strtod(resource, &endptr); + if (in_dpi == HUGE_VAL || dpi < 0 || *endptr != '\0' || endptr == resource) { ELOG("Xft.dpi = %s is an invalid number and couldn't be parsed.\n", resource); dpi = 0; goto init_dpi_end; } + dpi = (long)round(in_dpi); DLOG("Found Xft.dpi = %ld.\n", dpi); From 87ed8df4fabc36bb4a5e30af4f638cc1febc4bdb Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 9 Dec 2017 15:35:05 +0200 Subject: [PATCH 56/99] floating_maybe_reassign_ws: show workspace before focusing With this change i3 will correctly switch to the focused workspace. This fixes bug with moving floating windows with 'move ' or by dragging like _NET_CURRENT_DESKTOP not getting updated or 'workspace back_and_forth' not working. Fixes #2921. --- src/floating.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/floating.c b/src/floating.c index 149888184..e2e9d85af 100644 --- a/src/floating.c +++ b/src/floating.c @@ -449,6 +449,7 @@ bool floating_maybe_reassign_ws(Con *con) { Con *ws = TAILQ_FIRST(&(content->focus_head)); DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name); con_move_to_workspace(con, ws, false, true, false); + workspace_show(ws); con_focus(con_descend_focused(con)); return true; } From 9ced77384bfa2b56b6d3c0d3ded01a7bfc2d8806 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 9 Dec 2017 16:25:30 +0200 Subject: [PATCH 57/99] Remove useless check in _tree_next con_descend_focused on an empty workspace should return the workspace. --- src/tree.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tree.c b/src/tree.c index 710bb655f..f87c4326e 100644 --- a/src/tree.c +++ b/src/tree.c @@ -565,10 +565,8 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) focus = con_descend_focused(workspace); } - if (focus) { - con_focus(focus); - x_set_warp_to(&(focus->rect)); - } + con_focus(focus); + x_set_warp_to(&(focus->rect)); return true; } From cf28147c5ecbd38db6524a0b1c20032ad9efe296 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 9 Dec 2017 16:28:30 +0200 Subject: [PATCH 58/99] Show workspace in _tree_next Fixes a regression introduced by #2980. --- src/tree.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tree.c b/src/tree.c index f87c4326e..d1c587d5b 100644 --- a/src/tree.c +++ b/src/tree.c @@ -565,6 +565,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) focus = con_descend_focused(workspace); } + workspace_show(workspace); con_focus(focus); x_set_warp_to(&(focus->rect)); return true; From ed22785909387809a706f56ccc871cb820bf9c03 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 9 Dec 2017 19:50:43 +0200 Subject: [PATCH 59/99] Fix v3 to v4 crash with a variable with longer name than value --- src/config_parser.c | 2 +- testcases/t/183-config-variables.t | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/config_parser.c b/src/config_parser.c index 4e66c9110..a27cfc5f2 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -1065,7 +1065,7 @@ bool parse_file(const char *f, bool use_nagbar) { int version = detect_version(buf); if (version == 3) { /* We need to convert this v3 configuration */ - char *converted = migrate_config(new, stbuf.st_size); + char *converted = migrate_config(new, strlen(new)); if (converted != NULL) { ELOG("\n"); ELOG("****************************************************************\n"); diff --git a/testcases/t/183-config-variables.t b/testcases/t/183-config-variables.t index 4b225214a..d135ed59c 100644 --- a/testcases/t/183-config-variables.t +++ b/testcases/t/183-config-variables.t @@ -95,7 +95,19 @@ EOT is(launch_get_border($config), 'none', 'no border'); +##################################################################### +# test that variables with longer name than value don't crash i3 with +# v3 to v4 conversion. +# See: #3076 +##################################################################### + +$config = <<'EOT'; +set $var a +EOT +my $pid = launch_with_config($config); +does_i3_live; +exit_gracefully($pid); done_testing; From 1890517f96da54566bfb3e7273630f5d0f0ac32e Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 9 Dec 2017 19:56:54 +0200 Subject: [PATCH 60/99] migrate_config: scalloc converted config Prevents a false-positive error eg with config file: set $mod Mod4 bindsym $mod+h split h bindsym $mod+v split v ERROR: CONFIG: Expected one of these tokens: , '#', 'set ', ... ERROR: CONFIG: Line 8: status_command i3status ERROR: CONFIG: Line 9: } ERROR: CONFIG: Line 10: --- src/config_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config_parser.c b/src/config_parser.c index a27cfc5f2..2d3f3bb91 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -743,7 +743,7 @@ static char *migrate_config(char *input, off_t size) { /* read the script’s output */ int conv_size = 65535; - char *converted = smalloc(conv_size); + char *converted = scalloc(conv_size, 1); int read_bytes = 0, ret; do { if (read_bytes == conv_size) { From d134745c4f1717d3ab6c2dc28f55e5c75b983950 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 9 Dec 2017 22:17:48 +0200 Subject: [PATCH 61/99] Prevent access of freed workspace in _workspace_show The bug triggers when _workspace_show calls tree_close_internal and old == old_focus. Ie, when the old workspace was empty and needs to be closed but then is accessed as output_push_sticky_windows's argument: Breakpoint 1, output_push_sticky_windows (to_focus=0x55555589c8a0) at ../../i3/src/output.c:102 102 con_move_to_workspace(current, visible_ws, true, false, current != to_focus->parent); (gdb) print con_exists(to_focus) $1 = false The access violation can also be prevented by checking if con_exists(old_focus) but it shouldn't be necessary: the old_focus container can only be killed when it is an empty workspace. With --enable-sanitizers this causes i3 to exit but with --disable-sanitizers the access violation doesn't reliably cause a crash and the con_move_to_workspace call continues with: (gdb) print current != to_focus->parent $2 = 1 Since current->type is CT_FLOATING_CON and to_focus->type is CT_WORKSPACE, in this specific case ignore_focus would always be true. So, in this case, passing NULL instead of old_focus to output_push_sticky_windows doesn't change the behaviour of i3. Fixes #3075. --- src/output.c | 3 +- src/workspace.c | 5 ++++ testcases/t/293-sticky-output-crash.t | 41 +++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 testcases/t/293-sticky-output-crash.t diff --git a/src/output.c b/src/output.c index e76903844..c76dfd035 100644 --- a/src/output.c +++ b/src/output.c @@ -99,7 +99,8 @@ void output_push_sticky_windows(Con *to_focus) { continue; if (con_is_sticky(current)) { - con_move_to_workspace(current, visible_ws, true, false, current != to_focus->parent); + bool ignore_focus = (to_focus == NULL) || (current != to_focus->parent); + con_move_to_workspace(current, visible_ws, true, false, ignore_focus); } } } diff --git a/src/workspace.c b/src/workspace.c index 4b350b822..153133576 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -459,6 +459,11 @@ static void _workspace_show(Con *workspace) { y(free); + /* Avoid calling output_push_sticky_windows later with a freed container. */ + if (old == old_focus) { + old_focus = NULL; + } + ewmh_update_number_of_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); diff --git a/testcases/t/293-sticky-output-crash.t b/testcases/t/293-sticky-output-crash.t new file mode 100644 index 000000000..93ebaee92 --- /dev/null +++ b/testcases/t/293-sticky-output-crash.t @@ -0,0 +1,41 @@ +#!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) +# +# Verifies that i3 does not crash when opening a floating sticky on one output +# and then switching empty workspaces on the other output. +# Ticket: #3075 +# Bug still in: 4.14-191-g9d2d602d +use i3test i3_config => < 0); +open_window; +cmd 'sticky enable, floating enable'; + +# Switch to the right output and open a new workspace. +my $ws = fresh_workspace(output => 1); +does_i3_live; + +# Verify results. +is(@{get_ws($ws)->{floating_nodes}}, 0, 'workspace in right output is empty'); +$ws = fresh_workspace(output => 0); +is(@{get_ws($ws)->{floating_nodes}}, 1, 'new workspace in left output has the sticky container'); + +done_testing; From de3c122337b6bc9ec69308047575b90d64208f3b Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 10 Dec 2017 21:29:56 +0200 Subject: [PATCH 62/99] generate-command-parser.pl: remove trailing comma --- generate-command-parser.pl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/generate-command-parser.pl b/generate-command-parser.pl index a7687c7bc..eeec7dfe7 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -116,17 +116,16 @@ sub slurp { open(my $enumfh, '>', "GENERATED_${prefix}_enums.h"); -# XXX: we might want to have a way to do this without a trailing comma, but gcc -# seems to eat it. my %statenum; say $enumfh 'typedef enum {'; my $cnt = 0; for my $state (@keys, '__CALL') { - say $enumfh " $state = $cnt,"; + say $enumfh ',' if $cnt > 0; + print $enumfh " $state = $cnt"; $statenum{$state} = $cnt; $cnt++; } -say $enumfh '} cmdp_state;'; +say $enumfh "\n} cmdp_state;"; close($enumfh); # Third step: Generate the call function. From 5225e34b9d76dd4ae114970ab80437c02ea9a2da Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 10 Dec 2017 21:30:09 +0200 Subject: [PATCH 63/99] generate-command-parser.pl: remove trailing whitespace --- generate-command-parser.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate-command-parser.pl b/generate-command-parser.pl index eeec7dfe7..4c45b6ed6 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -224,7 +224,7 @@ sub slurp { $next_state = '__CALL'; } my $identifier = $token->{identifier}; - say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } }, |; + say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } },|; } say $tokfh '};'; } From df437aa87e39abeded78aa3671df8720b9ca2741 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 18 Sep 2017 13:22:01 +0300 Subject: [PATCH 64/99] Use con_has_parent in con_fullscreen_permits_focusing --- src/con.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/con.c b/src/con.c index 1de91d008..c9e2b6cfa 100644 --- a/src/con.c +++ b/src/con.c @@ -2070,14 +2070,7 @@ bool con_fullscreen_permits_focusing(Con *con) { /* Allow it only if the container to be focused is contained within the * current fullscreen container. */ - do { - if (con->parent == fs) - return true; - con = con->parent; - } while (con); - - /* Focusing con would hide it behind a fullscreen window, disallow it. */ - return false; + return con_has_parent(con, fs); } /* From 2592c63603081dd44c7bccf6014b55a253e14003 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 19 Sep 2017 14:52:02 +0300 Subject: [PATCH 65/99] Add error reply to cmd_focus_window_mode --- src/commands.c | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/commands.c b/src/commands.c index a9187866c..264cd04f6 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1257,28 +1257,34 @@ void cmd_focus_direction(I3_CMD, const char *direction) { void cmd_focus_window_mode(I3_CMD, const char *window_mode) { DLOG("window_mode = %s\n", window_mode); + bool to_floating = false; + if (strcmp(window_mode, "mode_toggle") == 0) { + to_floating = !con_inside_floating(focused); + } else if (strcmp(window_mode, "floating") == 0) { + to_floating = true; + } else if (strcmp(window_mode, "tiling") == 0) { + to_floating = false; + } + Con *ws = con_get_workspace(focused); - if (ws != NULL) { - if (strcmp(window_mode, "mode_toggle") == 0) { - if (con_inside_floating(focused)) - window_mode = "tiling"; - else - window_mode = "floating"; - } - Con *current; - TAILQ_FOREACH(current, &(ws->focus_head), focused) { - if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) || - (strcmp(window_mode, "tiling") == 0 && current->type == CT_FLOATING_CON)) - continue; + Con *current; + bool success = false; + TAILQ_FOREACH(current, &(ws->focus_head), focused) { + if ((to_floating && current->type != CT_FLOATING_CON) || + (!to_floating && current->type == CT_FLOATING_CON)) + continue; - con_focus(con_descend_focused(current)); - break; - } + con_focus(con_descend_focused(current)); + success = true; + break; } - cmd_output->needs_tree_render = true; - // XXX: default reply for now, make this a better reply - ysuccess(true); + if (success) { + cmd_output->needs_tree_render = true; + ysuccess(true); + } else { + yerror("Failed to find a %s container in workspace.", to_floating ? "floating" : "tiling"); + } } /* From 759e05137467c6f768a3164de9fe783e1a37aece Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 19 Sep 2017 09:56:53 +0300 Subject: [PATCH 66/99] Move is_num_fullscreen to Test.pm --- testcases/lib/i3test/Test.pm | 20 ++++++++ testcases/t/100-fullscreen.t | 65 +++++++++++-------------- testcases/t/206-fullscreen-scratchpad.t | 17 ++----- testcases/t/291-swap.t | 21 +++----- 4 files changed, 60 insertions(+), 63 deletions(-) diff --git a/testcases/lib/i3test/Test.pm b/testcases/lib/i3test/Test.pm index 0253bc2d5..552ae8b48 100644 --- a/testcases/lib/i3test/Test.pm +++ b/testcases/lib/i3test/Test.pm @@ -5,6 +5,7 @@ use base 'Test::Builder::Module'; our @EXPORT = qw( is_num_children + is_num_fullscreen cmp_float does_i3_live ); @@ -59,6 +60,25 @@ sub is_num_children { $tb->is_num($got_num_children, $num_children, $name); } +=head2 is_num_fullscreen($workspace, $expected, $test_name) + +Gets the number of fullscreen containers on the given workspace and verifies that +they match the expected amount. + + is_num_fullscreen('1', 0, 'no fullscreen containers on workspace 1'); + +=cut +sub is_num_fullscreen { + my ($workspace, $num_fullscreen, $name) = @_; + my $workspace_content = i3test::get_ws($workspace); + my $tb = $CLASS->builder; + + my $nodes = scalar grep { $_->{fullscreen_mode} != 0 } @{$workspace_content->{nodes}->[0]->{nodes}}; + my $cons = scalar grep { $_->{fullscreen_mode} != 0 } @{$workspace_content->{nodes}}; + my $floating = scalar grep { $_->{fullscreen_mode} != 0 } @{$workspace_content->{floating_nodes}->[0]->{nodes}}; + $tb->is_num($nodes + $cons + $floating, $num_fullscreen, $name); +} + =head2 cmp_float($a, $b) Compares floating point numbers C<$a> and C<$b> and returns true if they differ diff --git a/testcases/t/100-fullscreen.t b/testcases/t/100-fullscreen.t index f0d0b4c69..d817bee03 100644 --- a/testcases/t/100-fullscreen.t +++ b/testcases/t/100-fullscreen.t @@ -21,13 +21,6 @@ my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; -sub fullscreen_windows { - my $ws = $tmp; - $ws = shift if @_; - - scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)} -} - # get the output of this workspace my $tree = $i3->get_tree->recv; my @outputs = @{$tree->{nodes}}; @@ -143,11 +136,11 @@ ok(!eq_hash($new_rect, $original_rect), "Window got repositioned"); $swindow->fullscreen(1); sync_with_i3; -is(fullscreen_windows(), 1, 'amount of fullscreen windows'); +is_num_fullscreen($tmp, 1, 'amount of fullscreen windows'); $window->fullscreen(0); sync_with_i3; -is(fullscreen_windows(), 1, 'amount of fullscreen windows'); +is_num_fullscreen($tmp, 1, 'amount of fullscreen windows'); ok($swindow->mapped, 'window mapped after other fullscreen ended'); @@ -160,15 +153,15 @@ ok($swindow->mapped, 'window mapped after other fullscreen ended'); $swindow->fullscreen(0); sync_with_i3; -is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling'); +is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after disabling'); cmd 'fullscreen'; -is(fullscreen_windows(), 1, 'amount of fullscreen windows after fullscreen command'); +is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after fullscreen command'); cmd 'fullscreen'; -is(fullscreen_windows(), 0, 'amount of fullscreen windows after fullscreen command'); +is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after fullscreen command'); # clean up the workspace so that it will be cleaned when switching away cmd 'kill' for (@{get_ws_content($tmp)}); @@ -221,18 +214,18 @@ $swindow = open_window; cmd 'fullscreen'; -is(fullscreen_windows($tmp2), 1, 'one fullscreen window on second ws'); +is_num_fullscreen($tmp2, 1, 'one fullscreen window on second ws'); cmd "move workspace $tmp"; -is(fullscreen_windows($tmp2), 0, 'no fullscreen windows on second ws'); -is(fullscreen_windows($tmp), 1, 'one fullscreen window on first ws'); +is_num_fullscreen($tmp2, 0, 'no fullscreen windows on second ws'); +is_num_fullscreen($tmp, 1, 'one fullscreen window on first ws'); $swindow->fullscreen(0); sync_with_i3; # Verify that $swindow was the one that initially remained fullscreen. -is(fullscreen_windows($tmp), 0, 'no fullscreen windows on first ws'); +is_num_fullscreen($tmp, 0, 'no fullscreen windows on first ws'); ################################################################################ # Verify that opening a window with _NET_WM_STATE_FULLSCREEN unfullscreens any @@ -245,14 +238,14 @@ $window = open_window(); cmd "fullscreen"; -is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws'); +is_num_fullscreen($tmp, 1, 'one fullscreen window on ws'); is($x->input_focus, $window->id, 'fullscreen window focused'); $swindow = open_window({ fullscreen => 1 }); -is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws'); +is_num_fullscreen($tmp, 1, 'one fullscreen window on ws'); is($x->input_focus, $swindow->id, 'fullscreen window focused'); ################################################################################ @@ -263,19 +256,19 @@ $tmp = fresh_workspace; $window = open_window; is($x->input_focus, $window->id, 'window focused'); -is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); +is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace'); cmd 'fullscreen enable'; is($x->input_focus, $window->id, 'window still focused'); -is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace'); +is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace'); cmd 'fullscreen enable'; is($x->input_focus, $window->id, 'window still focused'); -is(fullscreen_windows($tmp), 1, 'still one fullscreen window on workspace'); +is_num_fullscreen($tmp, 1, 'still one fullscreen window on workspace'); $window->fullscreen(0); sync_with_i3; -is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); +is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace'); ################################################################################ # Verify that command ‘fullscreen enable global’ works and is idempotent. @@ -285,19 +278,19 @@ $tmp = fresh_workspace; $window = open_window; is($x->input_focus, $window->id, 'window focused'); -is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); +is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace'); cmd 'fullscreen enable global'; is($x->input_focus, $window->id, 'window still focused'); -is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace'); +is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace'); cmd 'fullscreen enable global'; is($x->input_focus, $window->id, 'window still focused'); -is(fullscreen_windows($tmp), 1, 'still one fullscreen window on workspace'); +is_num_fullscreen($tmp, 1, 'still one fullscreen window on workspace'); $window->fullscreen(0); sync_with_i3; -is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); +is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace'); ################################################################################ # Verify that command ‘fullscreen disable’ works and is idempotent. @@ -307,19 +300,19 @@ $tmp = fresh_workspace; $window = open_window; is($x->input_focus, $window->id, 'window focused'); -is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); +is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace'); $window->fullscreen(1); sync_with_i3; -is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace'); +is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace'); cmd 'fullscreen disable'; is($x->input_focus, $window->id, 'window still focused'); -is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); +is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace'); cmd 'fullscreen disable'; is($x->input_focus, $window->id, 'window still focused'); -is(fullscreen_windows($tmp), 0, 'still no fullscreen window on workspace'); +is_num_fullscreen($tmp, 0, 'still no fullscreen window on workspace'); ################################################################################ # Verify that command ‘fullscreen toggle’ works. @@ -328,15 +321,15 @@ is(fullscreen_windows($tmp), 0, 'still no fullscreen window on workspace'); $tmp = fresh_workspace; $window = open_window; -is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); +is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace'); cmd 'fullscreen toggle'; is($x->input_focus, $window->id, 'window still focused'); -is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace'); +is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace'); cmd 'fullscreen toggle'; is($x->input_focus, $window->id, 'window still focused'); -is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); +is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace'); ################################################################################ # Verify that a window’s fullscreen is disabled when another one is enabled @@ -349,15 +342,15 @@ $window = open_window; $other = open_window; is($x->input_focus, $other->id, 'other window focused'); -is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); +is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace'); cmd 'fullscreen enable'; is($x->input_focus, $other->id, 'other window focused'); -is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace'); +is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace'); cmd '[id="' . $window->id . '"] fullscreen enable'; is($x->input_focus, $window->id, 'window focused'); -is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace'); +is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace'); ################################################################################ # Verify that when a global fullscreen is enabled the window is focused and diff --git a/testcases/t/206-fullscreen-scratchpad.t b/testcases/t/206-fullscreen-scratchpad.t index c098f23fb..952450993 100644 --- a/testcases/t/206-fullscreen-scratchpad.t +++ b/testcases/t/206-fullscreen-scratchpad.t @@ -21,15 +21,6 @@ use i3test; my $tmp = fresh_workspace; -sub fullscreen_windows { - my $ws = $tmp; - $ws = shift if @_; - - my $nodes = scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)->[0]->{nodes}}; - my $cons = scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)}; - return $nodes + $cons; -} - ########################################################################################## # map two windows in one container, fullscreen one of them and then move it to scratchpad ########################################################################################## @@ -41,7 +32,7 @@ my $second_win = open_window; cmd 'fullscreen'; # see if the window really is in fullscreen mode -is(fullscreen_windows(), 1, 'amount of fullscreen windows after enabling fullscreen'); +is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after enabling fullscreen'); # move window to scratchpad cmd 'move scratchpad'; @@ -57,7 +48,7 @@ cmd 'scratchpad show'; cmd 'floating toggle'; # see if no window is in fullscreen mode -is(fullscreen_windows(), 0, 'amount of fullscreen windows after showing previously fullscreened scratchpad window'); +is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after showing previously fullscreened scratchpad window'); ######################################################################################## # move a window to scratchpad, focus parent container, make it fullscreen, focus a child @@ -79,7 +70,7 @@ cmd 'fullscreen'; cmd 'focus child'; # see if the window really is in fullscreen mode -is(fullscreen_windows(), 1, 'amount of fullscreen windows after enabling fullscreen on parent'); +is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after enabling fullscreen on parent'); ########################################################################## # show a scratchpad window; no window should be in fullscreen mode anymore @@ -89,6 +80,6 @@ is(fullscreen_windows(), 1, 'amount of fullscreen windows after enabling fullscr cmd 'scratchpad show'; # see if no window is in fullscreen mode -is(fullscreen_windows(), 0, 'amount of fullscreen windows after showing a scratchpad window while a parent container was in fullscreen mode'); +is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after showing a scratchpad window while a parent container was in fullscreen mode'); done_testing; diff --git a/testcases/t/291-swap.t b/testcases/t/291-swap.t index d4a43bfb1..3b61fdab3 100644 --- a/testcases/t/291-swap.t +++ b/testcases/t/291-swap.t @@ -30,12 +30,6 @@ my ($result); my @fullscreen_permutations = ([], ["A"], ["B"], ["A", "B"]); my @urgent; -sub fullscreen_windows { - my $ws = shift if @_; - - scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)} -} - ############################################################################### # Invalid con_id should not crash i3 # See issue #2895. @@ -191,14 +185,14 @@ for my $fullscreen (@fullscreen_permutations){ $nodes = get_ws_content($ws1); $node = $nodes->[0]; is($node->{window}, $B->{id}, 'B is on ws1:left'); - is(fullscreen_windows($ws1), $A_fullscreen, 'amount of fullscreen windows in ws1'); + is_num_fullscreen($ws1, $A_fullscreen, 'amount of fullscreen windows in ws1'); is($node->{fullscreen_mode}, $A_fullscreen, 'B got A\'s fullscreen mode'); $nodes = get_ws_content($ws2); $node = $nodes->[1]; is($node->{window}, $A->{id}, 'A is on ws2:right'); is(get_focused($ws2), $expected_focus, 'A is focused'); - is(fullscreen_windows($ws2), $B_fullscreen, 'amount of fullscreen windows in ws2'); + is_num_fullscreen($ws2, $B_fullscreen, 'amount of fullscreen windows in ws2'); is($node->{fullscreen_mode}, $B_fullscreen, 'A got B\'s fullscreen mode'); kill_all_windows; @@ -232,7 +226,7 @@ cmd '[con_mark=B] swap container with mark A'; $nodes = get_ws_content($ws1); is($nodes->[0]->{window}, $B->{id}, 'B is on ws1:left'); -is(fullscreen_windows($ws1), 1, 'F still fullscreen in ws1'); +is_num_fullscreen($ws1, 1, 'F still fullscreen in ws1'); is(get_focused($ws1), $expected_focus, 'F is still focused'); $nodes = get_ws_content($ws2); @@ -258,7 +252,6 @@ cmd "split v"; open_window; cmd "focus parent"; cmd "fullscreen enable"; -$F = fullscreen_windows($ws1); $expected_focus = get_focused($ws1); $ws2 = fresh_workspace; @@ -274,11 +267,11 @@ does_i3_live; $nodes = get_ws_content($ws1); is($nodes->[1]->{nodes}->[0]->{window}, $B->{id}, 'B is on top right in ws1'); is(get_focused($ws1), $expected_focus, 'The container of the stacked windows remains focused in ws1'); -is(fullscreen_windows($ws1), $F, 'Same amount of fullscreen windows in ws1'); +is_num_fullscreen($ws1, 1, 'Same amount of fullscreen windows in ws1'); $nodes = get_ws_content($ws2); is($nodes->[0]->{window}, $A->{id}, 'A is on ws2'); -is(fullscreen_windows($ws2), 1, 'A is in fullscreen mode'); +is_num_fullscreen($ws2, 1, 'A is in fullscreen mode'); ############################################################################### # Swap two non-focused containers within the same workspace. @@ -351,13 +344,13 @@ for my $fullscreen (@fullscreen_permutations){ $nodes = get_ws_content($ws1); $node = $nodes->[0]; is($node->{window}, $B->{id}, 'B is on the first workspace'); - is(fullscreen_windows($ws1), $A_fullscreen, 'amount of fullscreen windows in ws1'); + is_num_fullscreen($ws1, $A_fullscreen, 'amount of fullscreen windows in ws1'); is($node->{fullscreen_mode}, $A_fullscreen, 'B got A\'s fullscreen mode'); $nodes = get_ws_content($ws2); $node = $nodes->[0]; is($node->{window}, $A->{id}, 'A is on the second workspace'); - is(fullscreen_windows($ws2), $B_fullscreen, 'amount of fullscreen windows in ws2'); + is_num_fullscreen($ws2, $B_fullscreen, 'amount of fullscreen windows in ws2'); is($node->{fullscreen_mode}, $B_fullscreen, 'A got B\'s fullscreen mode'); is(get_focused($ws3), $expected_focus, 'F is still focused'); From 994a479558a6b93e286ce03d6f00db3a3a9c6fcf Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 23 Sep 2017 13:09:08 +0300 Subject: [PATCH 67/99] userguide: mention in focus --- docs/userguide | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/userguide b/docs/userguide index 0258e2ab7..98242a9b0 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1951,6 +1951,9 @@ bindsym $mod+t floating toggle To change focus, you can use the +focus+ command. The following options are available: +:: + Sets focus to the container that matches the specified criteria. + See <>. left|right|up|down:: Sets focus to the nearest container in the given direction. parent:: @@ -1970,6 +1973,7 @@ output:: *Syntax*: ---------------------------------------------- + focus focus left|right|down|up focus parent|child|floating|tiling|mode_toggle focus output left|right|up|down|primary| @@ -1977,6 +1981,9 @@ focus output left|right|up|down|primary| *Examples*: ------------------------------------------------- +# Focus firefox +bindsym $mod+F1 [class="Firefox"] focus + # Focus container on the left, bottom, top, right bindsym $mod+j focus left bindsym $mod+k focus down From 2403c43f7b72c0a5230ff7aa0dd67c687fc9ba5e Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 17 Sep 2017 02:20:48 +0300 Subject: [PATCH 68/99] Make 'focus' disable blocking fullscreen windows The problem here is that con_fullscreen_permits_focusing() does not check if there is a blocking fullscreen container in the workspace that the container to be focused belongs. This makes it possible to focus a container behind a fullscreen window if it's in an unfocused workspace. This commit introduces a change in the 'focus' command behaviour. When focusing a container blocked by a fullscreen container, either CF_OUTPUT or CF_GLOBAL, the blocking container loses its fullscreen mode and the target container is focused like normal. This should not affect directional focus commands: left, right, up, down, parent, child. Fixes issue #1819. --- src/commands.c | 25 ++++--- testcases/t/156-fullscreen-focus.t | 110 ++++++++++++++++++++++++++--- 2 files changed, 116 insertions(+), 19 deletions(-) diff --git a/src/commands.c b/src/commands.c index 264cd04f6..162f0892f 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1250,6 +1250,20 @@ void cmd_focus_direction(I3_CMD, const char *direction) { ysuccess(true); } +/* + * Focus a container and disable any other fullscreen container not permitting the focus. + * + */ +static void cmd_focus_force_focus(Con *con) { + /* Disable fullscreen container in workspace with container to be focused. */ + Con *ws = con_get_workspace(con); + Con *fullscreen_on_ws = (focused && focused->fullscreen_mode == CF_GLOBAL) ? focused : con_get_fullscreen_con(ws, CF_OUTPUT); + if (fullscreen_on_ws && fullscreen_on_ws != con && !con_has_parent(con, fullscreen_on_ws)) { + con_disable_fullscreen(fullscreen_on_ws); + } + con_focus(con); +} + /* * Implementation of 'focus tiling|floating|mode_toggle'. * @@ -1274,7 +1288,7 @@ void cmd_focus_window_mode(I3_CMD, const char *window_mode) { (!to_floating && current->type == CT_FLOATING_CON)) continue; - con_focus(con_descend_focused(current)); + cmd_focus_force_focus(con_descend_focused(current)); success = true; break; } @@ -1341,13 +1355,6 @@ void cmd_focus(I3_CMD) { if (!ws) continue; - /* Check the fullscreen focus constraints. */ - if (!con_fullscreen_permits_focusing(current->con)) { - LOG("Cannot change focus while in fullscreen mode (fullscreen rules).\n"); - ysuccess(false); - return; - } - /* In case this is a scratchpad window, call scratchpad_show(). */ if (ws == __i3_scratch) { scratchpad_show(current->con); @@ -1371,7 +1378,7 @@ void cmd_focus(I3_CMD) { * So we focus 'current' to make it the currently focused window of * the target workspace, then revert focus. */ Con *currently_focused = focused; - con_focus(current->con); + cmd_focus_force_focus(current->con); con_focus(currently_focused); /* Now switch to the workspace, then focus */ diff --git a/testcases/t/156-fullscreen-focus.t b/testcases/t/156-fullscreen-focus.t index 7a5e38ca1..cfa2405e4 100644 --- a/testcases/t/156-fullscreen-focus.t +++ b/testcases/t/156-fullscreen-focus.t @@ -157,9 +157,6 @@ isnt($x->input_focus, $right2->id, 'bottom right window no longer focused'); cmd 'focus child'; is($x->input_focus, $right2->id, 'bottom right window focused again'); -cmd '[id="' . $left->id . '"] focus'; -is($x->input_focus, $right2->id, 'prevented focus change to left window'); - cmd 'focus up'; is($x->input_focus, $right1->id, 'allowed focus up'); @@ -178,9 +175,6 @@ is($x->input_focus, $right1->id, 'allowed focus wrap (down)'); cmd 'focus up'; is($x->input_focus, $right2->id, 'allowed focus wrap (up)'); -cmd '[id="' . $diff_ws->id . '"] focus'; -is($x->input_focus, $right2->id, 'prevented focus change to different ws'); - ################################################################################ # Same tests when we're in non-global fullscreen mode. It should now be possible # to focus a container in a different workspace. @@ -202,9 +196,6 @@ isnt($x->input_focus, $right2->id, 'bottom right window no longer focused'); cmd 'focus child'; is($x->input_focus, $right2->id, 'bottom right window focused again'); -cmd '[id="' . $left->id . '"] focus'; -is($x->input_focus, $right2->id, 'prevented focus change to left window'); - cmd 'focus up'; is($x->input_focus, $right1->id, 'allowed focus up'); @@ -323,6 +314,105 @@ verify_move(2, 'prevented move to workspace by name'); cmd "move to workspace prev"; verify_move(2, 'prevented move to workspace by position'); -# TODO: Tests for "move to output" and "move workspace to output". +################################################################################ +# Ensure it's possible to focus a window using the focus command despite +# fullscreen window blocking it. Fullscreen window should lose its fullscreen +# mode. +################################################################################ + +# first & second tiling, focus using id +kill_all_windows; + +$tmp = fresh_workspace; +my $first = open_window; +my $second = open_window; +cmd 'fullscreen'; +is($x->input_focus, $second->id, 'fullscreen window focused'); +is_num_fullscreen($tmp, 1, '1 fullscreen window'); + +cmd '[id="'. $first->id .'"] focus'; +sync_with_i3; + +is($x->input_focus, $first->id, 'correctly focused using id'); +is_num_fullscreen($tmp, 0, 'no fullscreen windows'); + +# first floating, second tiling, focus using 'focus floating' +kill_all_windows; + +$tmp = fresh_workspace; +my $first = open_floating_window; +my $second = open_window; +cmd 'fullscreen'; +is($x->input_focus, $second->id, 'fullscreen window focused'); +is_num_fullscreen($tmp, 1, '1 fullscreen window'); + +cmd 'focus floating'; +sync_with_i3; + +is($x->input_focus, $first->id, 'correctly focused using focus floating'); +is_num_fullscreen($tmp, 0, 'no fullscreen windows'); + +# first tiling, second floating, focus using 'focus tiling' +kill_all_windows; + +$tmp = fresh_workspace; +my $first = open_window; +my $second = open_floating_window; +cmd 'fullscreen'; +is($x->input_focus, $second->id, 'fullscreen window focused'); +is_num_fullscreen($tmp, 1, '1 fullscreen window'); + +cmd 'focus tiling'; +sync_with_i3; +is($x->input_focus, $first->id, 'correctly focused using focus tiling'); +is_num_fullscreen($tmp, 0, 'no fullscreen windows'); + +################################################################################ +# When the fullscreen window is in an other workspace it should maintain its +# fullscreen mode since it's not blocking the window to be focused. +################################################################################ + +kill_all_windows; + +$tmp = fresh_workspace; +my $first = open_window; + +$tmp2 = fresh_workspace; +my $second = open_window; +cmd 'fullscreen'; +is($x->input_focus, $second->id, 'fullscreen window focused'); +is_num_fullscreen($tmp2, 1, '1 fullscreen window'); + +cmd '[id="'. $first->id .'"] focus'; +sync_with_i3; + +is($x->input_focus, $first->id, 'correctly focused using focus id'); +is_num_fullscreen($tmp, 0, 'no fullscreen windows on first workspace'); +is_num_fullscreen($tmp2, 1, 'still one fullscreen window on second workspace'); + +################################################################################ +# But a global window in another workspace is blocking the window to be focused. +# Ensure that it loses its fullscreen mode. +################################################################################ + +kill_all_windows; + +$tmp = fresh_workspace; +$first = open_window; + +$tmp2 = fresh_workspace; +$second = open_window; +cmd 'fullscreen global'; +is($x->input_focus, $second->id, 'global window focused'); +is_num_fullscreen($tmp2, 1, '1 fullscreen window'); + +cmd '[id="'. $first->id .'"] focus'; +sync_with_i3; + +is($x->input_focus, $first->id, 'correctly focused using focus id'); +is_num_fullscreen($tmp2, 0, 'no fullscreen windows'); + + +# TODO: Tests for "move to output" and "move workspace to output". done_testing; From 161db6f17d734ac9deb0a20e81b78d4b2a92ce68 Mon Sep 17 00:00:00 2001 From: Pallav Agarwal Date: Wed, 20 Dec 2017 17:49:38 +0530 Subject: [PATCH 69/99] Add relative coordinates in JSON for i3bar click events (fixes #2767) Add support for relative coordinates in i3bar click events Rename {x,y}_rel to relative_{x,y} Update i3bar-protocol doc to mention the added fields in click events --- docs/i3bar-protocol | 11 ++++++++++- i3bar/include/child.h | 2 +- i3bar/src/child.c | 14 +++++++++++++- i3bar/src/xcb.c | 3 ++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol index b8c2b5ad7..1a3025878 100644 --- a/docs/i3bar-protocol +++ b/docs/i3bar-protocol @@ -236,6 +236,11 @@ x, y:: X11 root window coordinates where the click occurred button:: X11 button ID (for example 1 to 3 for left/middle/right mouse button) +relative_x, relative_y:: + Coordinates where the click occurred, with respect to the top left corner + of the block +width, height:: + Width and height (in px) of the block *Example*: ------------------------------------------ @@ -244,6 +249,10 @@ button:: "instance": "eth0", "button": 1, "x": 1320, - "y": 1400 + "y": 1400, + "relative_x": 12, + "relative_y": 8, + "width": 50, + "height": 22 } ------------------------------------------ diff --git a/i3bar/include/child.h b/i3bar/include/child.h index 0871c7f49..9479fac1d 100644 --- a/i3bar/include/child.h +++ b/i3bar/include/child.h @@ -85,4 +85,4 @@ bool child_want_click_events(void); * Generates a click event, if enabled. * */ -void send_block_clicked(int button, const char *name, const char *instance, int x, int y); +void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int width, int height); diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 170fcdefc..1cd7d512a 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -596,7 +596,7 @@ void child_click_events_key(const char *key) { * Generates a click event, if enabled. * */ -void send_block_clicked(int button, const char *name, const char *instance, int x, int y) { +void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int width, int height) { if (!child.click_events) { return; } @@ -624,6 +624,18 @@ void send_block_clicked(int button, const char *name, const char *instance, int child_click_events_key("y"); yajl_gen_integer(gen, y); + child_click_events_key("relative_x"); + yajl_gen_integer(gen, x_rel); + + child_click_events_key("relative_y"); + yajl_gen_integer(gen, y_rel); + + child_click_events_key("width"); + yajl_gen_integer(gen, width); + + child_click_events_key("height"); + yajl_gen_integer(gen, height); + yajl_gen_map_close(gen); child_write_output(); } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 77822544e..542c86c3c 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -523,7 +523,8 @@ void handle_button(xcb_button_press_event_t *event) { block_x += render->width + render->x_offset + render->x_append + get_sep_offset(block) + sep_offset_remainder; if (statusline_x <= block_x && statusline_x >= last_block_x) { - send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y); + send_block_clicked(event->detail, block->name, block->instance, + event->root_x, event->root_y, statusline_x - last_block_x, event->event_y, block_x - last_block_x, bar_height); return; } From 0600b11d2b151fa034cd6bdfa2ad4ccdcfe1f184 Mon Sep 17 00:00:00 2001 From: Thomas Praxl Date: Sat, 23 Dec 2017 12:28:03 +0100 Subject: [PATCH 70/99] Add workspace vars to support DRY when customizing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relabeling the workspaces required the user to update the labels at least in two places of the i3 config file (switch to workspace + move focused container to workspace) to keep a consistent state. This got even worse when the user started to reference the workspaces elsewhere, e.g. for assigning containers to specific workspaces. This change supports the DRY principle for users. Relabeling the workspaces is now merely a matter of changing the respective variable ($ws1, $ws2, … $ws10). --- etc/config | 63 +++++++++++++++++++++++++++++++-------------- etc/config.keycodes | 62 ++++++++++++++++++++++++++++++-------------- 2 files changed, 85 insertions(+), 40 deletions(-) diff --git a/etc/config b/etc/config index 5b751d00d..77966e8ae 100644 --- a/etc/config +++ b/etc/config @@ -104,29 +104,52 @@ bindsym Mod1+Shift+minus move scratchpad # If there are multiple scratchpad windows, this command cycles through them. bindsym Mod1+minus scratchpad show +# Define workspaces along with their labels +# +# Customize your workspace labels here. +# Example: +# +# set $ws1 "Terminals" +# +# or +# +# set $ws1 "1: Terminals" +# +set $ws1 "1" +set $ws2 "2" +set $ws3 "3" +set $ws4 "4" +set $ws5 "5" +set $ws6 "6" +set $ws7 "7" +set $ws8 "8" +set $ws9 "9" +set $ws10 "10" + + # switch to workspace -bindsym Mod1+1 workspace 1 -bindsym Mod1+2 workspace 2 -bindsym Mod1+3 workspace 3 -bindsym Mod1+4 workspace 4 -bindsym Mod1+5 workspace 5 -bindsym Mod1+6 workspace 6 -bindsym Mod1+7 workspace 7 -bindsym Mod1+8 workspace 8 -bindsym Mod1+9 workspace 9 -bindsym Mod1+0 workspace 10 +bindsym Mod1+1 workspace $ws1 +bindsym Mod1+2 workspace $ws2 +bindsym Mod1+3 workspace $ws3 +bindsym Mod1+4 workspace $ws4 +bindsym Mod1+5 workspace $ws5 +bindsym Mod1+6 workspace $ws6 +bindsym Mod1+7 workspace $ws7 +bindsym Mod1+8 workspace $ws8 +bindsym Mod1+9 workspace $ws9 +bindsym Mod1+0 workspace $ws10 # move focused container to workspace -bindsym Mod1+Shift+1 move container to workspace 1 -bindsym Mod1+Shift+2 move container to workspace 2 -bindsym Mod1+Shift+3 move container to workspace 3 -bindsym Mod1+Shift+4 move container to workspace 4 -bindsym Mod1+Shift+5 move container to workspace 5 -bindsym Mod1+Shift+6 move container to workspace 6 -bindsym Mod1+Shift+7 move container to workspace 7 -bindsym Mod1+Shift+8 move container to workspace 8 -bindsym Mod1+Shift+9 move container to workspace 9 -bindsym Mod1+Shift+0 move container to workspace 10 +bindsym Mod1+Shift+1 move container to workspace $ws1 +bindsym Mod1+Shift+2 move container to workspace $ws2 +bindsym Mod1+Shift+3 move container to workspace $ws3 +bindsym Mod1+Shift+4 move container to workspace $ws4 +bindsym Mod1+Shift+5 move container to workspace $ws5 +bindsym Mod1+Shift+6 move container to workspace $ws6 +bindsym Mod1+Shift+7 move container to workspace $ws7 +bindsym Mod1+Shift+8 move container to workspace $ws8 +bindsym Mod1+Shift+9 move container to workspace $ws9 +bindsym Mod1+Shift+0 move container to workspace $ws10 # reload the configuration file bindsym Mod1+Shift+c reload diff --git a/etc/config.keycodes b/etc/config.keycodes index c07462b4e..86eadb82c 100644 --- a/etc/config.keycodes +++ b/etc/config.keycodes @@ -91,29 +91,51 @@ bindcode $mod+38 focus parent # focus the child container #bindsym $mod+d focus child +# Define workspaces along with their labels +# +# Customize your workspace labels here. +# Example: +# +# set $ws1 "Terminals" +# +# or +# +# set $ws1 "1: Terminals" +# +set $ws1 "1" +set $ws2 "2" +set $ws3 "3" +set $ws4 "4" +set $ws5 "5" +set $ws6 "6" +set $ws7 "7" +set $ws8 "8" +set $ws9 "9" +set $ws10 "10" + # switch to workspace -bindcode $mod+10 workspace 1 -bindcode $mod+11 workspace 2 -bindcode $mod+12 workspace 3 -bindcode $mod+13 workspace 4 -bindcode $mod+14 workspace 5 -bindcode $mod+15 workspace 6 -bindcode $mod+16 workspace 7 -bindcode $mod+17 workspace 8 -bindcode $mod+18 workspace 9 -bindcode $mod+19 workspace 10 +bindcode $mod+10 workspace $ws1 +bindcode $mod+11 workspace $ws2 +bindcode $mod+12 workspace $ws3 +bindcode $mod+13 workspace $ws4 +bindcode $mod+14 workspace $ws5 +bindcode $mod+15 workspace $ws6 +bindcode $mod+16 workspace $ws7 +bindcode $mod+17 workspace $ws8 +bindcode $mod+18 workspace $ws9 +bindcode $mod+19 workspace $ws10 # move focused container to workspace -bindcode $mod+Shift+10 move container to workspace 1 -bindcode $mod+Shift+11 move container to workspace 2 -bindcode $mod+Shift+12 move container to workspace 3 -bindcode $mod+Shift+13 move container to workspace 4 -bindcode $mod+Shift+14 move container to workspace 5 -bindcode $mod+Shift+15 move container to workspace 6 -bindcode $mod+Shift+16 move container to workspace 7 -bindcode $mod+Shift+17 move container to workspace 8 -bindcode $mod+Shift+18 move container to workspace 9 -bindcode $mod+Shift+19 move container to workspace 10 +bindcode $mod+Shift+10 move container to workspace $ws1 +bindcode $mod+Shift+11 move container to workspace $ws2 +bindcode $mod+Shift+12 move container to workspace $ws3 +bindcode $mod+Shift+13 move container to workspace $ws4 +bindcode $mod+Shift+14 move container to workspace $ws5 +bindcode $mod+Shift+15 move container to workspace $ws6 +bindcode $mod+Shift+16 move container to workspace $ws7 +bindcode $mod+Shift+17 move container to workspace $ws8 +bindcode $mod+Shift+18 move container to workspace $ws9 +bindcode $mod+Shift+19 move container to workspace $ws10 # reload the configuration file bindcode $mod+Shift+54 reload From 0b25259370cf7e123876059c0aa3a7d0c785399b Mon Sep 17 00:00:00 2001 From: Johannes Lange Date: Sun, 24 Dec 2017 09:40:23 +0100 Subject: [PATCH 71/99] free last_motion_notify before returning fixes #3086 --- src/floating.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/floating.c b/src/floating.c index e2e9d85af..85bd8cc05 100644 --- a/src/floating.c +++ b/src/floating.c @@ -747,8 +747,10 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) { if (last_motion_notify != (xcb_motion_notify_event_t *)event) free(event); - if (dragloop->result != DRAGGING) + if (dragloop->result != DRAGGING) { + free(last_motion_notify); return; + } } if (last_motion_notify == NULL) From 941fe9ab4f49eb08a34b2e3e727be3b4d3ae05ae Mon Sep 17 00:00:00 2001 From: Thomas Praxl Date: Tue, 26 Dec 2017 11:29:42 +0100 Subject: [PATCH 72/99] Shorten comment for workspace variables The old comments gave the impression that you had to define workspace names upfront, which is not true. This also keeps the overall config as brief as possible. --- etc/config | 13 ++----------- etc/config.keycodes | 13 ++----------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/etc/config b/etc/config index 77966e8ae..f08dac995 100644 --- a/etc/config +++ b/etc/config @@ -104,17 +104,8 @@ bindsym Mod1+Shift+minus move scratchpad # If there are multiple scratchpad windows, this command cycles through them. bindsym Mod1+minus scratchpad show -# Define workspaces along with their labels -# -# Customize your workspace labels here. -# Example: -# -# set $ws1 "Terminals" -# -# or -# -# set $ws1 "1: Terminals" -# +# Define names for default workspaces for which we configure key bindings later on. +# We use variables to avoid repeating the names in multiple places. set $ws1 "1" set $ws2 "2" set $ws3 "3" diff --git a/etc/config.keycodes b/etc/config.keycodes index 86eadb82c..a22f16176 100644 --- a/etc/config.keycodes +++ b/etc/config.keycodes @@ -91,17 +91,8 @@ bindcode $mod+38 focus parent # focus the child container #bindsym $mod+d focus child -# Define workspaces along with their labels -# -# Customize your workspace labels here. -# Example: -# -# set $ws1 "Terminals" -# -# or -# -# set $ws1 "1: Terminals" -# +# Define names for default workspaces for which we configure key bindings later on. +# We use variables to avoid repeating the names in multiple places. set $ws1 "1" set $ws2 "2" set $ws3 "3" From 8f30a04425fbaf1bbc237a3119c607300361c9e6 Mon Sep 17 00:00:00 2001 From: Johannes Lange Date: Tue, 26 Dec 2017 09:37:22 +0100 Subject: [PATCH 73/99] tiling resize: remove minimum size (was 5%) fixes #3071 --- src/commands.c | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/commands.c b/src/commands.c index 162f0892f..1938b250d 100644 --- a/src/commands.c +++ b/src/commands.c @@ -78,14 +78,6 @@ } \ } while (0) -/* - * Returns true if a is definitely greater than b (using the given epsilon) - * - */ -static bool definitelyGreaterThan(float a, float b, float epsilon) { - return (a - b) > ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); -} - /* * Checks whether we switched to a new workspace and returns false in that case, * signaling that further workspace switching should be done by the calling function @@ -525,8 +517,8 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c LOG("default percentage = %f\n", percentage); /* resize */ - LOG("second->percent = %f\n", second->percent); LOG("first->percent before = %f\n", first->percent); + LOG("second->percent before = %f\n", second->percent); if (first->percent == 0.0) first->percent = percentage; if (second->percent == 0.0) @@ -535,12 +527,10 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c double new_second_percent = second->percent - ((double)ppt / 100.0); LOG("new_first_percent = %f\n", new_first_percent); LOG("new_second_percent = %f\n", new_second_percent); - /* Ensure that the new percentages are positive and greater than - * 0.05 to have a reasonable minimum size. */ - if (definitelyGreaterThan(new_first_percent, 0.05, DBL_EPSILON) && - definitelyGreaterThan(new_second_percent, 0.05, DBL_EPSILON)) { - first->percent += ((double)ppt / 100.0); - second->percent -= ((double)ppt / 100.0); + /* Ensure that the new percentages are positive. */ + if (new_first_percent > 0.0 && new_second_percent > 0.0) { + first->percent = new_first_percent; + second->percent = new_second_percent; LOG("first->percent after = %f\n", first->percent); LOG("second->percent after = %f\n", second->percent); } else { @@ -602,24 +592,23 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way double subtract_percent = ((double)ppt / 100.0) / (children - 1); LOG("new_current_percent = %f\n", new_current_percent); LOG("subtract_percent = %f\n", subtract_percent); - /* Ensure that the new percentages are positive and greater than - * 0.05 to have a reasonable minimum size. */ + /* Ensure that the new percentages are positive. */ TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { if (child == current) continue; - if (!definitelyGreaterThan(child->percent - subtract_percent, 0.05, DBL_EPSILON)) { + if (child->percent - subtract_percent <= 0.0) { LOG("Not resizing, already at minimum size (child %p would end up with a size of %.f\n", child, child->percent - subtract_percent); ysuccess(false); return false; } } - if (!definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON)) { + if (new_current_percent <= 0.0) { LOG("Not resizing, already at minimum size\n"); ysuccess(false); return false; } - current->percent += ((double)ppt / 100.0); + current->percent = new_current_percent; LOG("current->percent after = %f\n", current->percent); TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { From 80ea18624a06b4535ed51d935e4f7d65f696519e Mon Sep 17 00:00:00 2001 From: Johannes Lange Date: Tue, 26 Dec 2017 13:16:15 +0100 Subject: [PATCH 74/99] fixing redeclaration warnings in testcases --- testcases/t/113-urgent.t | 2 +- testcases/t/156-fullscreen-focus.t | 12 ++++++------ testcases/t/185-scratchpad.t | 2 +- testcases/t/189-floating-constraints.t | 6 +++--- testcases/t/195-net-active-window.t | 2 +- testcases/t/240-focus-on-window-activation.t | 4 ++-- testcases/t/252-floating-size.t | 8 ++++---- testcases/t/504-move-workspace-to-output.t | 4 ++-- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/testcases/t/113-urgent.t b/testcases/t/113-urgent.t index 9c1507e67..1e2644ad9 100644 --- a/testcases/t/113-urgent.t +++ b/testcases/t/113-urgent.t @@ -304,7 +304,7 @@ for ($type = 1; $type <= 2; $type++) { cmd 'move right'; cmd '[id="' . $w3->id . '"] focus'; sync_with_i3; - my $ws = get_ws($tmp); + $ws = get_ws($tmp); ok(!$ws->{urgent}, 'urgent flag not set on workspace'); ############################################################################## diff --git a/testcases/t/156-fullscreen-focus.t b/testcases/t/156-fullscreen-focus.t index cfa2405e4..9c396f40f 100644 --- a/testcases/t/156-fullscreen-focus.t +++ b/testcases/t/156-fullscreen-focus.t @@ -340,8 +340,8 @@ is_num_fullscreen($tmp, 0, 'no fullscreen windows'); kill_all_windows; $tmp = fresh_workspace; -my $first = open_floating_window; -my $second = open_window; +$first = open_floating_window; +$second = open_window; cmd 'fullscreen'; is($x->input_focus, $second->id, 'fullscreen window focused'); is_num_fullscreen($tmp, 1, '1 fullscreen window'); @@ -356,8 +356,8 @@ is_num_fullscreen($tmp, 0, 'no fullscreen windows'); kill_all_windows; $tmp = fresh_workspace; -my $first = open_window; -my $second = open_floating_window; +$first = open_window; +$second = open_floating_window; cmd 'fullscreen'; is($x->input_focus, $second->id, 'fullscreen window focused'); is_num_fullscreen($tmp, 1, '1 fullscreen window'); @@ -376,10 +376,10 @@ is_num_fullscreen($tmp, 0, 'no fullscreen windows'); kill_all_windows; $tmp = fresh_workspace; -my $first = open_window; +$first = open_window; $tmp2 = fresh_workspace; -my $second = open_window; +$second = open_window; cmd 'fullscreen'; is($x->input_focus, $second->id, 'fullscreen window focused'); is_num_fullscreen($tmp2, 1, '1 fullscreen window'); diff --git a/testcases/t/185-scratchpad.t b/testcases/t/185-scratchpad.t index f94bd75b6..147890e1f 100644 --- a/testcases/t/185-scratchpad.t +++ b/testcases/t/185-scratchpad.t @@ -429,7 +429,7 @@ does_i3_live; ################################################################################ clear_scratchpad; -my $ws = fresh_workspace; +$ws = fresh_workspace; open_window; my $scratch = get_focused($ws); diff --git a/testcases/t/189-floating-constraints.t b/testcases/t/189-floating-constraints.t index ea4c08de0..6b082bfdc 100644 --- a/testcases/t/189-floating-constraints.t +++ b/testcases/t/189-floating-constraints.t @@ -190,7 +190,7 @@ exit_gracefully($pid); # 7: check floating_maximum_size with cmd_size ################################################################################ -my $config = < [ 0, 0, 90, 80 ]); +$window = open_floating_window(rect => [ 0, 0, 90, 80 ]); cmd 'border none'; cmd 'resize set 101 91'; sync_with_i3; -my $rect = $window->rect; +$rect = $window->rect; is($rect->{width}, 100, 'width did not exceed maximum width'); is($rect->{height}, 90, 'height did not exceed maximum height'); diff --git a/testcases/t/195-net-active-window.t b/testcases/t/195-net-active-window.t index aee1e03a6..18245dfc2 100644 --- a/testcases/t/195-net-active-window.t +++ b/testcases/t/195-net-active-window.t @@ -137,7 +137,7 @@ is($x->input_focus, $win3->id, 'window 3 still focused'); # is received. ################################################################################ -my $scratch = open_window; +$scratch = open_window; is($x->input_focus, $scratch->id, 'to-scratchpad window has focus'); diff --git a/testcases/t/240-focus-on-window-activation.t b/testcases/t/240-focus-on-window-activation.t index cd3989c42..570607539 100644 --- a/testcases/t/240-focus-on-window-activation.t +++ b/testcases/t/240-focus-on-window-activation.t @@ -111,7 +111,7 @@ EOT $pid = launch_with_config($config); -my $ws = fresh_workspace; +$ws = fresh_workspace; $first = open_window; $second = open_window; @@ -165,7 +165,7 @@ EOT $pid = launch_with_config($config); -my $ws = fresh_workspace; +$ws = fresh_workspace; $first = open_window; $second = open_window; diff --git a/testcases/t/252-floating-size.t b/testcases/t/252-floating-size.t index ac0c48d03..2c8edf397 100644 --- a/testcases/t/252-floating-size.t +++ b/testcases/t/252-floating-size.t @@ -79,8 +79,8 @@ cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to ################################################################################ cmd 'resize set 44 ppt 111 px'; -my $expected_width = int(0.44 * 1333); -my $expected_height = 111; +$expected_width = int(0.44 * 1333); +$expected_height = 111; @content = @{get_ws($tmp)->{floating_nodes}}; cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched'); @@ -89,8 +89,8 @@ cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $e cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px"); cmd 'resize set 222 px 100 ppt'; -my $expected_width = 222; -my $expected_height = 999; +$expected_width = 222; +$expected_height = 999; @content = @{get_ws($tmp)->{floating_nodes}}; cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched'); diff --git a/testcases/t/504-move-workspace-to-output.t b/testcases/t/504-move-workspace-to-output.t index 86bad5e50..4ec33f663 100644 --- a/testcases/t/504-move-workspace-to-output.t +++ b/testcases/t/504-move-workspace-to-output.t @@ -187,10 +187,10 @@ ok($ws1 ~~ @$x0, 'ws1 on fake-0'); my $__i3_scratch = get_ws('__i3_scratch'); is(scalar @{$__i3_scratch->{floating_nodes}}, 0, 'scratchpad is empty'); -my $ws0 = fresh_workspace(output => 0); +$ws0 = fresh_workspace(output => 0); open_window(wm_class => 'a'); -my $ws1 = fresh_workspace(output => 1); +$ws1 = fresh_workspace(output => 1); open_window(wm_class => 'b'); my $scratchpad_window = open_window(wm_class => 'c'); cmd 'move to scratchpad'; From dc6a099bed22aef099bd7265c944f6dd1c7908d6 Mon Sep 17 00:00:00 2001 From: Johannes Lange Date: Tue, 26 Dec 2017 13:39:19 +0100 Subject: [PATCH 75/99] fixing uninitialized warnings in testcases --- testcases/t/195-net-active-window.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/t/195-net-active-window.t b/testcases/t/195-net-active-window.t index 18245dfc2..f9f883cbd 100644 --- a/testcases/t/195-net-active-window.t +++ b/testcases/t/195-net-active-window.t @@ -24,7 +24,7 @@ use i3test; sub send_net_active_window { my ($id, $source) = @_; - $source = ($source eq 'pager' ? 2 : 0); + $source = (((defined $source) && ($source eq 'pager')) ? 2 : 0); my $msg = pack "CCSLLLLLLL", X11::XCB::CLIENT_MESSAGE, # response_type From eb679bfabd93b4c9ea755df7349ffa0ef6f159fc Mon Sep 17 00:00:00 2001 From: clonejo Date: Wed, 27 Dec 2017 17:40:47 +0100 Subject: [PATCH 76/99] Docs: state that pango markup requires the use of a pango font. --- docs/i3bar-protocol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol index 1a3025878..cf86531cc 100644 --- a/docs/i3bar-protocol +++ b/docs/i3bar-protocol @@ -177,7 +177,8 @@ separator_block_width:: markup:: A string that indicates how the text of the block should be parsed. Set to +"pango"+ to use https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup]. - Set to +"none"+ to not use any markup (default). + Set to +"none"+ to not use any markup (default). Pango markup only works + if you use a pango font. If you want to put in your own entries into a block, prefix the key with an underscore (_). i3bar will ignore all keys it doesn’t understand, and prefixing From a2e9b1461f6573f4ed4bc41c44e9e03f4a1b4c00 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 10 Dec 2017 22:06:29 +0200 Subject: [PATCH 77/99] Reduce repetition in cmd_move_con_to_workspace* --- src/commands.c | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/src/commands.c b/src/commands.c index 162f0892f..5bfdc96a3 100644 --- a/src/commands.c +++ b/src/commands.c @@ -269,14 +269,20 @@ void cmd_criteria_add(I3_CMD, const char *ctype, const char *cvalue) { match_parse_property(current_match, ctype, cvalue); } +static void move_matches_to_workspace(Con *ws) { + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + con_move_to_workspace(current->con, ws, true, false, false); + } +} + /* * Implementation of 'move [window|container] [to] workspace * next|prev|next_on_output|prev_on_output|current'. * */ void cmd_move_con_to_workspace(I3_CMD, const char *which) { - owindow *current; - DLOG("which=%s\n", which); /* We have nothing to move: @@ -309,10 +315,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) { return; } - TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false, false); - } + move_matches_to_workspace(ws); cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply @@ -324,11 +327,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) { * */ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) { - owindow *current; - Con *ws; - - ws = workspace_back_and_forth_get(); - + Con *ws = workspace_back_and_forth_get(); if (ws == NULL) { yerror("No workspace was previously active."); return; @@ -336,10 +335,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) { HANDLE_EMPTY_MATCH; - TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false, false); - } + move_matches_to_workspace(ws); cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply @@ -358,7 +354,6 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_au } const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); - owindow *current; /* We have nothing to move: * when criteria was specified but didn't match any window or @@ -382,10 +377,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_au HANDLE_EMPTY_MATCH; - TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false, false); - } + move_matches_to_workspace(ws); cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply @@ -398,7 +390,6 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_au */ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) { const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); - owindow *current; /* We have nothing to move: * when criteria was specified but didn't match any window or @@ -435,10 +426,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no HANDLE_EMPTY_MATCH; - TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, workspace, true, false, false); - } + move_matches_to_workspace(workspace); cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply From e0f15796692d5d76c7046d83ebc2786a965ea4f0 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 10 Dec 2017 22:08:46 +0200 Subject: [PATCH 78/99] cmd_move_con_to_workspace_number: rename workspace->ws For consistency with other cmd_move_con_to_workspace* functions. --- src/commands.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/commands.c b/src/commands.c index 5bfdc96a3..02d14daac 100644 --- a/src/commands.c +++ b/src/commands.c @@ -403,7 +403,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no LOG("should move window to workspace %s\n", which); /* get the workspace */ - Con *output, *workspace = NULL; + Con *output, *ws = NULL; long parsed_num = ws_name_to_number(which); @@ -414,19 +414,19 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no } TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), + GREP_FIRST(ws, output_get_content(output), child->num == parsed_num); - if (!workspace) { - workspace = workspace_get(which, NULL); + if (!ws) { + ws = workspace_get(which, NULL); } if (!no_auto_back_and_forth) - workspace = maybe_auto_back_and_forth_workspace(workspace); + ws = maybe_auto_back_and_forth_workspace(ws); HANDLE_EMPTY_MATCH; - move_matches_to_workspace(workspace); + move_matches_to_workspace(ws); cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply From 2b5b6330dc40fe8398a2c0e3d90a3531b225bc4c Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 29 Sep 2017 01:35:11 +0300 Subject: [PATCH 79/99] Add testcases for focus_follows_mouse --- testcases/t/293-focus-follows-mouse.t | 60 +++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 testcases/t/293-focus-follows-mouse.t diff --git a/testcases/t/293-focus-follows-mouse.t b/testcases/t/293-focus-follows-mouse.t new file mode 100644 index 000000000..9fb1004e6 --- /dev/null +++ b/testcases/t/293-focus-follows-mouse.t @@ -0,0 +1,60 @@ +#!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) +# +# Tests the focus_follows_mouse setting. +use i3test i3_config => <root->warp_pointer($x_px, $y_px); + sync_with_i3; +} + +################################################################### +# Test a simple case with 2 windows. +################################################################### + +synced_warp_pointer(600, 600); +$first = open_window; +$second = open_window; +is($x->input_focus, $second->id, 'second window focused'); + +synced_warp_pointer(0, 0); +is($x->input_focus, $first->id, 'first window focused'); + +################################################################### +# Test that focus isn't changed with tabbed windows. +################################################################### + +fresh_workspace; +synced_warp_pointer(600, 600); +$first = open_window; +cmd 'layout tabbed'; +$second = open_window; +is($x->input_focus, $second->id, 'second (tabbed) window focused'); + +synced_warp_pointer(0, 0); +is($x->input_focus, $second->id, 'second window still focused'); + +done_testing; From c2483e415cfe8b95a7f26551cb1a1c66b8f60ea4 Mon Sep 17 00:00:00 2001 From: Diki Ananta Date: Fri, 5 Jan 2018 07:14:57 +0700 Subject: [PATCH 80/99] config: add comment $mod+r in back to normal when resize window --- etc/config | 2 +- etc/config.keycodes | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/config b/etc/config index f08dac995..3be9831dd 100644 --- a/etc/config +++ b/etc/config @@ -168,7 +168,7 @@ mode "resize" { bindsym Up resize shrink height 10 px or 10 ppt bindsym Right resize grow width 10 px or 10 ppt - # back to normal: Enter or Escape + # back to normal: Enter or Escape or Mod1+r bindsym Return mode "default" bindsym Escape mode "default" bindsym Mod1+r mode "default" diff --git a/etc/config.keycodes b/etc/config.keycodes index a22f16176..2d56876c2 100644 --- a/etc/config.keycodes +++ b/etc/config.keycodes @@ -154,7 +154,7 @@ mode "resize" { bindcode 111 resize shrink height 10 px or 10 ppt bindcode 114 resize grow width 10 px or 10 ppt - # back to normal: Enter or Escape + # back to normal: Enter or Escape or $mod+r bindcode 36 mode "default" bindcode 9 mode "default" bindcode $mod+27 mode "default" From c0378f737ba3248f7fcaf668bd44ed2c16da4b4c Mon Sep 17 00:00:00 2001 From: livanh Date: Sat, 6 Jan 2018 17:59:27 +0100 Subject: [PATCH 81/99] Improve resize_find_tiling_participants() and simplify cmd_resize_tiling_width_height() (#3111) --- include/resize.h | 2 +- src/click.c | 2 +- src/commands.c | 40 +++++++++------------------------------- src/resize.c | 8 +++++++- 4 files changed, 18 insertions(+), 34 deletions(-) diff --git a/include/resize.h b/include/resize.h index 7b33de907..386341561 100644 --- a/include/resize.h +++ b/include/resize.h @@ -11,6 +11,6 @@ #include -bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction); +bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides); int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event); diff --git a/src/click.c b/src/click.c index 78af8a035..b061d0c39 100644 --- a/src/click.c +++ b/src/click.c @@ -49,7 +49,7 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press break; } - bool res = resize_find_tiling_participants(&first, &second, search_direction); + bool res = resize_find_tiling_participants(&first, &second, search_direction, false); if (!res) { LOG("No second container in this direction found.\n"); return false; diff --git a/src/commands.c b/src/commands.c index 162f0892f..f9b803f48 100644 --- a/src/commands.c +++ b/src/commands.c @@ -511,7 +511,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c else search_direction = D_DOWN; - bool res = resize_find_tiling_participants(&first, &second, search_direction); + bool res = resize_find_tiling_participants(&first, &second, search_direction, false); if (!res) { LOG("No second container in this direction found.\n"); ysuccess(false); @@ -552,19 +552,15 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int ppt) { LOG("width/height resize\n"); - /* get the appropriate current container (skip stacked/tabbed cons) */ - while (current->parent->layout == L_STACKED || - current->parent->layout == L_TABBED) - current = current->parent; - - /* Then further go up until we find one with the matching orientation. */ - orientation_t search_orientation = - (strcmp(direction, "width") == 0 ? HORIZ : VERT); - while (current->type != CT_WORKSPACE && - current->type != CT_FLOATING_CON && - (con_orientation(current->parent) != search_orientation || con_num_children(current->parent) == 1)) - current = current->parent; + /* get the appropriate current container (skip stacked/tabbed cons) */ + Con *dummy = NULL; + direction_t search_direction = (strcmp(direction, "width") == 0 ? D_LEFT : D_DOWN); + bool search_result = resize_find_tiling_participants(¤t, &dummy, search_direction, true); + if (search_result == false) { + ysuccess(false); + return false; + } /* get the default percentage */ int children = con_num_children(current->parent); @@ -572,24 +568,6 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way double percentage = 1.0 / children; LOG("default percentage = %f\n", percentage); - orientation_t orientation = con_orientation(current->parent); - - if ((orientation == HORIZ && - strcmp(direction, "height") == 0) || - (orientation == VERT && - strcmp(direction, "width") == 0)) { - LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n", - (orientation == HORIZ ? "horizontal" : "vertical")); - ysuccess(false); - return false; - } - - if (children == 1) { - LOG("This is the only container, cannot resize.\n"); - ysuccess(false); - return false; - } - /* Ensure all the other children have a percentage set. */ Con *child; TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { diff --git a/src/resize.c b/src/resize.c index f07fcec6a..ee50bfbc6 100644 --- a/src/resize.c +++ b/src/resize.c @@ -47,7 +47,7 @@ DRAGGING_CB(resize_callback) { xcb_flush(conn); } -bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction) { +bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides) { DLOG("Find two participants for resizing container=%p in direction=%i\n", other, direction); Con *first = *current; Con *second = NULL; @@ -74,8 +74,14 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir /* get the counterpart for this resizement */ if (dir_backwards) { second = TAILQ_PREV(first, nodes_head, nodes); + if (second == NULL && both_sides == true) { + second = TAILQ_NEXT(first, nodes); + } } else { second = TAILQ_NEXT(first, nodes); + if (second == NULL && both_sides == true) { + second = TAILQ_PREV(first, nodes_head, nodes); + } } if (second == NULL) { From 1a04608796ff96c103cc0335994fa7fab8dca698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sat, 6 Jan 2018 23:24:33 +0100 Subject: [PATCH 82/99] Send success response for nop. (#3113) fixes #3112 --- src/commands.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands.c b/src/commands.c index f9b803f48..136184afc 100644 --- a/src/commands.c +++ b/src/commands.c @@ -745,6 +745,7 @@ void cmd_nop(I3_CMD, const char *comment) { LOG("-------------------------------------------------\n"); LOG(" NOP: %s\n", comment); LOG("-------------------------------------------------\n"); + ysuccess(true); } /* From 84ba1d142eecd94a34b991bb77f62ccb0f49b391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 7 Jan 2018 09:47:21 +0100 Subject: [PATCH 83/99] Fix build --- src/render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render.c b/src/render.c index b64c891cc..ec9b859ba 100644 --- a/src/render.c +++ b/src/render.c @@ -535,7 +535,7 @@ bool has_adjacent_container(Con *con, direction_t direction) { Con *first = con; Con *second = NULL; - bool found_neighbor = resize_find_tiling_participants(&first, &second, direction); + bool found_neighbor = resize_find_tiling_participants(&first, &second, direction, false); if (!found_neighbor) return false; From 7b59da8a4f9e7aa93ec0cc68ee60bebf3d97a604 Mon Sep 17 00:00:00 2001 From: livanh Date: Mon, 8 Jan 2018 23:25:08 +0100 Subject: [PATCH 84/99] Implement 'resize set ppt ppt' command for tiling windows (#3036) --- docs/userguide | 7 +- include/commands.h | 2 +- src/commands.c | 71 ++++++++++++-- testcases/t/541-resize-set-tiling.t | 147 ++++++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 9 deletions(-) create mode 100644 testcases/t/541-resize-set-tiling.t diff --git a/docs/userguide b/docs/userguide index 98242a9b0..974135acb 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2320,8 +2320,11 @@ space from all the other containers. The optional pixel argument specifies by how many pixels a *floating container* should be grown or shrunk (the default is 10 pixels). The ppt argument means percentage points and specifies by how many percentage points a *tiling container* should be grown or shrunk (the -default is 10 percentage points). Note that +resize set+ will only work for -floating containers. +default is 10 percentage points). + +Notes about +resize set+: a value of 0 for or means "do +not resize in this direction", and resizing a tiling container by +px+ is not +implemented. It is recommended to define bindings for resizing in a dedicated binding mode. See <> and the example in the i3 diff --git a/include/commands.h b/include/commands.h index 85d5fe78a..1057f021d 100644 --- a/include/commands.h +++ b/include/commands.h @@ -63,7 +63,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *no_aut void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth); /** - * Implementation of 'resize set [px] [px]'. + * Implementation of 'resize set [px | ppt] [px | ppt]'. * */ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height); diff --git a/src/commands.c b/src/commands.c index 136184afc..1263a56b2 100644 --- a/src/commands.c +++ b/src/commands.c @@ -659,7 +659,7 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px, */ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height) { DLOG("resizing to %ld %s x %ld %s\n", cwidth, mode_width, cheight, mode_height); - if (cwidth <= 0 || cheight <= 0) { + if (cwidth < 0 || cheight < 0) { ELOG("Resize failed: dimensions cannot be negative (was %ld %s x %ld %s)\n", cwidth, mode_width, cheight, mode_height); return; } @@ -667,25 +667,84 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c HANDLE_EMPTY_MATCH; owindow *current; + bool success = true; TAILQ_FOREACH(current, &owindows, owindows) { Con *floating_con; if ((floating_con = con_inside_floating(current->con))) { Con *output = con_get_output(floating_con); - if (mode_width && strcmp(mode_width, "ppt") == 0) { + if (cwidth == 0) { + cwidth = output->rect.width; + } else if (mode_width && strcmp(mode_width, "ppt") == 0) { cwidth = output->rect.width * ((double)cwidth / 100.0); } - if (mode_height && strcmp(mode_height, "ppt") == 0) { + if (cheight == 0) { + cheight = output->rect.height; + } else if (mode_height && strcmp(mode_height, "ppt") == 0) { cheight = output->rect.height * ((double)cheight / 100.0); } floating_resize(floating_con, cwidth, cheight); } else { - ELOG("Resize failed: %p not a floating container\n", current->con); + if (current->con->window && current->con->window->dock) { + DLOG("This is a dock window. Not resizing (con = %p)\n)", current->con); + continue; + } + + if (cwidth > 0 && mode_width && strcmp(mode_width, "ppt") == 0) { + /* get the appropriate current container (skip stacked/tabbed cons) */ + Con *target = current->con; + Con *dummy; + resize_find_tiling_participants(&target, &dummy, D_LEFT, true); + + /* Calculate new size for the target container */ + double current_percent = target->percent; + char *action_string; + long adjustment; + + if (current_percent > cwidth) { + action_string = "shrink"; + adjustment = (int)(current_percent * 100) - cwidth; + } else { + action_string = "grow"; + adjustment = cwidth - (int)(current_percent * 100); + } + + /* perform resizing and report failure if not possible */ + if (!cmd_resize_tiling_width_height(current_match, cmd_output, + target, action_string, "width", adjustment)) { + success = false; + } + } + + if (cheight > 0 && mode_width && strcmp(mode_width, "ppt") == 0) { + /* get the appropriate current container (skip stacked/tabbed cons) */ + Con *target = current->con; + Con *dummy; + resize_find_tiling_participants(&target, &dummy, D_DOWN, true); + + /* Calculate new size for the target container */ + double current_percent = target->percent; + char *action_string; + long adjustment; + + if (current_percent > cheight) { + action_string = "shrink"; + adjustment = (int)(current_percent * 100) - cheight; + } else { + action_string = "grow"; + adjustment = cheight - (int)(current_percent * 100); + } + + /* perform resizing and report failure if not possible */ + if (!cmd_resize_tiling_width_height(current_match, cmd_output, + target, action_string, "height", adjustment)) { + success = false; + } + } } } cmd_output->needs_tree_render = true; - // XXX: default reply for now, make this a better reply - ysuccess(true); + ysuccess(success); } /* diff --git a/testcases/t/541-resize-set-tiling.t b/testcases/t/541-resize-set-tiling.t new file mode 100644 index 000000000..82267baf6 --- /dev/null +++ b/testcases/t/541-resize-set-tiling.t @@ -0,0 +1,147 @@ +#!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) +# +# Tests resizing tiling containers +use i3test; + +############################################################ +# resize horizontally +############################################################ + +my $tmp = fresh_workspace; + +cmd 'split h'; + +my $left = open_window; +my $right = open_window; + +diag("left = " . $left->id . ", right = " . $right->id); + +is($x->input_focus, $right->id, 'Right window focused'); + +cmd 'resize set 75 ppt 0 ppt'; + +my ($nodes, $focus) = get_ws_content($tmp); + +cmp_float($nodes->[0]->{percent}, 0.25, 'left window got only 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'right window got 75%'); + +############################################################ +# resize vertically +############################################################ + +my $tmp = fresh_workspace; + +cmd 'split v'; + +my $top = open_window; +my $bottom = open_window; + +diag("top = " . $top->id . ", bottom = " . $bottom->id); + +is($x->input_focus, $bottom->id, 'Bottom window focused'); + +cmd 'resize set 0 ppt 75 ppt'; + +my ($nodes, $focus) = get_ws_content($tmp); + +cmp_float($nodes->[0]->{percent}, 0.25, 'top window got only 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); + + +############################################################ +# resize horizontally and vertically +############################################################ + +my $tmp = fresh_workspace; + +cmd 'split h'; +my $left = open_window; +my $top_right = open_window; +cmd 'split v'; +my $bottom_right = open_window; + +diag("left = " . $left->id . ", top-right = " . $top_right->id . ", bottom-right = " . $bottom_right->id); + +is($x->input_focus, $bottom_right->id, 'Bottom-right window focused'); + +cmd 'resize set 75 ppt 75 ppt'; + +my ($nodes, $focus) = get_ws_content($tmp); + +cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%'); +cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.25, 'top-right window got 25%'); +cmp_float($nodes->[1]->{nodes}->[1]->{percent}, 0.75, 'bottom-right window got 75%'); + + +############################################################ +# resize from inside a tabbed container +############################################################ + +my $tmp = fresh_workspace; + +cmd 'split h'; + +my $left = open_window; +my $right1 = open_window; + +cmd 'split h'; +cmd 'layout tabbed'; + +my $right2 = open_window; + +diag("left = " . $left->id . ", right1 = " . $right1->id . ", right2 = " . $right2->id); + +is($x->input_focus, $right2->id, '2nd right window focused'); + +cmd 'resize set 75 ppt 0 ppt'; + +my ($nodes, $focus) = get_ws_content($tmp); + +cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%'); + + +############################################################ +# resize from inside a stacked container +############################################################ + +my $tmp = fresh_workspace; + +cmd 'split h'; + +my $left = open_window; +my $right1 = open_window; + +cmd 'split h'; +cmd 'layout stacked'; + +my $right2 = open_window; + +diag("left = " . $left->id . ", right1 = " . $right1->id . ", right2 = " . $right2->id); + +is($x->input_focus, $right2->id, '2nd right window focused'); + +cmd 'resize set 75 ppt 0 ppt'; + +my ($nodes, $focus) = get_ws_content($tmp); + +cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%'); + + +done_testing; From 0c2fbeedc2d8c11e210961c8c0ca9d43d4a044ce Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 6 Dec 2017 01:58:47 +0200 Subject: [PATCH 85/99] Don't raise floating windows when focused because of focus_follows_mouse Fixes #2990. --- include/con.h | 6 ++++ src/click.c | 4 +-- src/commands.c | 12 +++---- src/con.c | 46 +++++++++++++++++---------- src/floating.c | 6 ++-- src/handlers.c | 8 ++--- src/load_layout.c | 2 +- src/main.c | 2 +- src/manage.c | 2 +- src/move.c | 2 +- src/randr.c | 6 ++-- src/scratchpad.c | 4 +-- src/tree.c | 16 +++++----- src/workspace.c | 6 ++-- testcases/t/293-focus-follows-mouse.t | 28 ++++++++++++++++ 15 files changed, 99 insertions(+), 51 deletions(-) diff --git a/include/con.h b/include/con.h index b88ea354c..726fec96d 100644 --- a/include/con.h +++ b/include/con.h @@ -38,6 +38,12 @@ void con_free(Con *con); */ void con_focus(Con *con); +/** + * Sets input focus to the given container and raises it to the top. + * + */ +void con_activate(Con *con); + /** * Closes the given container. * diff --git a/src/click.c b/src/click.c index 78af8a035..5b529a383 100644 --- a/src/click.c +++ b/src/click.c @@ -241,7 +241,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod * The splitv container will be focused. */ Con *focused = con->parent; focused = TAILQ_FIRST(&(focused->focus_head)); - con_focus(focused); + con_activate(focused); /* To prevent scrolling from going outside the container (see ticket * #557), we first check if scrolling is possible at all. */ bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL); @@ -256,7 +256,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod } /* 2: focus this con. */ - con_focus(con); + con_activate(con); /* 3: For floating containers, we also want to raise them on click. * We will skip handling events on floating cons in fullscreen mode */ diff --git a/src/commands.c b/src/commands.c index 162f0892f..df2b236b1 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1261,7 +1261,7 @@ static void cmd_focus_force_focus(Con *con) { if (fullscreen_on_ws && fullscreen_on_ws != con && !con_has_parent(con, fullscreen_on_ws)) { con_disable_fullscreen(fullscreen_on_ws); } - con_focus(con); + con_activate(con); } /* @@ -1379,12 +1379,12 @@ void cmd_focus(I3_CMD) { * the target workspace, then revert focus. */ Con *currently_focused = focused; cmd_focus_force_focus(current->con); - con_focus(currently_focused); + con_activate(currently_focused); /* Now switch to the workspace, then focus */ workspace_show(ws); LOG("focusing %p / %s\n", current->con, current->con->name); - con_focus(current->con); + con_activate(current->con); count++; } @@ -1496,7 +1496,7 @@ void cmd_move_direction(I3_CMD, const char *direction, long move_px) { /* the move command should not disturb focus */ if (focused != initially_focused) - con_focus(initially_focused); + con_activate(initially_focused); // XXX: default reply for now, make this a better reply ysuccess(true); @@ -1621,7 +1621,7 @@ void cmd_open(I3_CMD) { LOG("opening new container\n"); Con *con = tree_open_con(NULL, NULL); con->layout = L_SPLITH; - con_focus(con); + con_activate(con); y(map_open); ystr("success"); @@ -2010,7 +2010,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { } /* Restore the previous focus since con_attach messes with the focus. */ - con_focus(previously_focused); + con_activate(previously_focused); cmd_output->needs_tree_render = true; ysuccess(true); diff --git a/src/con.c b/src/con.c index c9e2b6cfa..2d9b8691b 100644 --- a/src/con.c +++ b/src/con.c @@ -243,15 +243,29 @@ void con_focus(Con *con) { workspace_update_urgent_flag(con_get_workspace(con)); ipc_send_window_event("urgent", con); } +} - /* Focusing a container with a floating parent should raise it to the top. Since - * con_focus is called recursively for each parent we don't need to use - * con_inside_floating(). */ - if (con->type == CT_FLOATING_CON) { - floating_raise_con(con); +/* + * Raise container to the top if it is floating or inside some floating + * container. + * + */ +static void con_raise(Con *con) { + Con *floating = con_inside_floating(con); + if (floating) { + floating_raise_con(floating); } } +/* + * Sets input focus to the given container and raises it to the top. + * + */ +void con_activate(Con *con) { + con_focus(con); + con_raise(con); +} + /* * Closes the given container. * @@ -994,9 +1008,9 @@ void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode) { Con *old_focused = focused; if (fullscreen_mode == CF_GLOBAL && cur_ws != con_ws) workspace_show(con_ws); - con_focus(con); + con_activate(con); if (fullscreen_mode != CF_GLOBAL && cur_ws != con_ws) - con_focus(old_focused); + con_activate(old_focused); con_set_fullscreen_mode(con, fullscreen_mode); } @@ -1148,11 +1162,11 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi * new workspace is hidden and it's necessary to immediately switch * back to the originally-focused workspace. */ Con *old_focus = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head)); - con_focus(con_descend_focused(con)); + con_activate(con_descend_focused(con)); /* Restore focus if the output's focused workspace has changed. */ if (con_get_workspace(focused) != old_focus) - con_focus(old_focus); + con_activate(old_focus); } /* 7: when moving to another workspace, we leave the focus on the current @@ -1172,7 +1186,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* Set focus only if con was on current workspace before moving. * Otherwise we would give focus to some window on different workspace. */ if (!ignore_focus && source_ws == current_ws) - con_focus(con_descend_focused(focus_next)); + con_activate(con_descend_focused(focus_next)); /* 8. If anything within the container is associated with a startup sequence, * delete it so child windows won't be created on the old workspace. */ @@ -1791,7 +1805,7 @@ void con_set_layout(Con *con, layout_t layout) { con_attach(new, con, false); if (old_focused) - con_focus(old_focused); + con_activate(old_focused); tree_flatten(croot); } @@ -2358,7 +2372,7 @@ bool con_swap(Con *first, Con *second) { * We don't need to check this for the second container because we've only * moved the first one at this point.*/ if (first_ws != second_ws && focused_within_first) { - con_focus(con_descend_focused(current_ws)); + con_activate(con_descend_focused(current_ws)); } /* Move second to where first has been originally. */ @@ -2401,15 +2415,15 @@ bool con_swap(Con *first, Con *second) { */ if (focused_within_first) { if (first_ws == second_ws) { - con_focus(old_focus); + con_activate(old_focus); } else { - con_focus(con_descend_focused(second)); + con_activate(con_descend_focused(second)); } } else if (focused_within_second) { if (first_ws == second_ws) { - con_focus(old_focus); + con_activate(old_focus); } else { - con_focus(con_descend_focused(first)); + con_activate(con_descend_focused(first)); } } diff --git a/src/floating.c b/src/floating.c index 85bd8cc05..e958153d6 100644 --- a/src/floating.c +++ b/src/floating.c @@ -318,7 +318,7 @@ void floating_enable(Con *con, bool automatic) { render_con(con, false); if (set_focus) - con_focus(con); + con_activate(con); /* Check if we need to re-assign it to a different workspace because of its * coordinates and exit if that was done successfully. */ @@ -382,7 +382,7 @@ void floating_disable(Con *con, bool automatic) { con_fix_percent(con->parent); if (set_focus) - con_focus(con); + con_activate(con); floating_set_hint_atom(con, false); ipc_send_window_event("floating", con); @@ -450,7 +450,7 @@ bool floating_maybe_reassign_ws(Con *con) { DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name); con_move_to_workspace(con, ws, false, true, false); workspace_show(ws); - con_focus(con_descend_focused(con)); + con_activate(con_descend_focused(con)); return true; } diff --git a/src/handlers.c b/src/handlers.c index 0f81afae1..e1671c3b4 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -433,7 +433,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) { if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) { DLOG("Focusing con = %p\n", con); workspace_show(ws); - con_focus(con); + con_activate(con); tree_render(); } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) { DLOG("Marking con = %p urgent\n", con); @@ -776,7 +776,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { workspace_show(ws); /* Re-set focus, even if unchanged from i3’s perspective. */ focused_id = XCB_NONE; - con_focus(con); + con_activate(con); } } else { /* Request is from an application. */ @@ -788,7 +788,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) { DLOG("Focusing con = %p\n", con); workspace_show(ws); - con_focus(con); + con_activate(con); } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) { DLOG("Marking con = %p urgent\n", con); con_set_urgency(con, true); @@ -1245,7 +1245,7 @@ static void handle_focus_in(xcb_focus_in_event_t *event) { if (ws != con_get_workspace(focused)) workspace_show(ws); - con_focus(con); + con_activate(con); /* We update focused_id because we don’t need to set focus again */ focused_id = event->event; tree_render(); diff --git a/src/load_layout.c b/src/load_layout.c index 071b3ccd6..aa7ac03c4 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -654,6 +654,6 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm yajl_free(hand); if (to_focus) { - con_focus(to_focus); + con_activate(to_focus); } } diff --git a/src/main.c b/src/main.c index b634b139f..194ef05c3 100644 --- a/src/main.c +++ b/src/main.c @@ -766,7 +766,7 @@ int main(int argc, char *argv[]) { output = get_first_output(); } - con_focus(con_descend_focused(output_get_content(output->con))); + con_activate(con_descend_focused(output_get_content(output->con))); free(pointerreply); } diff --git a/src/manage.c b/src/manage.c index d12ce8d6e..8b306052c 100644 --- a/src/manage.c +++ b/src/manage.c @@ -646,7 +646,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki * proper window event sequence. */ if (set_focus && nc->mapped) { DLOG("Now setting focus.\n"); - con_focus(nc); + con_activate(nc); } tree_render(); diff --git a/src/move.c b/src/move.c index 3ecc69e4c..97ca6d40b 100644 --- a/src/move.c +++ b/src/move.c @@ -118,7 +118,7 @@ static void move_to_output_directed(Con *con, direction_t direction) { attach_to_workspace(con, ws, direction); /* fix the focus stack */ - con_focus(con); + con_activate(con); /* force re-painting the indicators */ FREE(con->deco_render_params); diff --git a/src/randr.c b/src/randr.c index 1d3330145..85add08fa 100644 --- a/src/randr.c +++ b/src/randr.c @@ -496,7 +496,7 @@ void init_ws_for_output(Output *output, Con *content) { Con *ws = create_workspace_on_output(output, content); /* TODO: Set focus in main.c */ - con_focus(ws); + con_activate(ws); } /* @@ -924,7 +924,7 @@ void randr_query_outputs(void) { continue; DLOG("Focusing primary output %s\n", output_primary_name(output)); - con_focus(con_descend_focused(output->con)); + con_activate(con_descend_focused(output->con)); } /* render_layout flushes */ @@ -987,7 +987,7 @@ void randr_disable_output(Output *output) { if (next) { DLOG("now focusing next = %p\n", next); - con_focus(next); + con_activate(next); workspace_show(con_get_workspace(next)); } diff --git a/src/scratchpad.c b/src/scratchpad.c index 9018ad3ff..95154014c 100644 --- a/src/scratchpad.c +++ b/src/scratchpad.c @@ -123,7 +123,7 @@ void scratchpad_show(Con *con) { /* use con_descend_tiling_focused to get the last focused * window inside this scratch container in order to * keep the focus the same within this container */ - con_focus(con_descend_tiling_focused(walk_con)); + con_activate(con_descend_tiling_focused(walk_con)); return; } } @@ -205,7 +205,7 @@ void scratchpad_show(Con *con) { workspace_show(active); } - con_focus(con_descend_focused(con)); + con_activate(con_descend_focused(con)); } /* diff --git a/src/tree.c b/src/tree.c index d1c587d5b..b8bc732bc 100644 --- a/src/tree.c +++ b/src/tree.c @@ -344,12 +344,12 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par DLOG("focusing %p / %s\n", next, next->name); if (next->type == CT_DOCKAREA) { /* Instead of focusing the dockarea, we need to restore focus to the workspace */ - con_focus(con_descend_focused(output_get_content(next->parent))); + con_activate(con_descend_focused(output_get_content(next->parent))); } else { if (!force_set_focus && con != focused) DLOG("not changing focus, the container was not focused before\n"); else - con_focus(next); + con_activate(next); } } else { DLOG("not focusing because we're not killing anybody\n"); @@ -433,7 +433,7 @@ bool level_up(void) { /* Skip over floating containers and go directly to the grandparent * (which should always be a workspace) */ if (focused->parent->type == CT_FLOATING_CON) { - con_focus(focused->parent->parent); + con_activate(focused->parent->parent); return true; } @@ -444,7 +444,7 @@ bool level_up(void) { ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n"); return false; } - con_focus(focused->parent); + con_activate(focused->parent); return true; } @@ -469,7 +469,7 @@ bool level_down(void) { next = TAILQ_FIRST(&(next->focus_head)); } - con_focus(next); + con_activate(next); return true; } @@ -566,7 +566,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) } workspace_show(workspace); - con_focus(focus); + con_activate(focus); x_set_warp_to(&(focus->rect)); return true; } @@ -604,7 +604,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows); } - con_focus(con_descend_focused(next)); + con_activate(con_descend_focused(next)); return true; } @@ -653,7 +653,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) /* 3: focus choice comes in here. at the moment we will go down * until we find a window */ /* TODO: check for window, atm we only go down as far as possible */ - con_focus(con_descend_focused(next)); + con_activate(con_descend_focused(next)); return true; } diff --git a/src/workspace.c b/src/workspace.c index 153133576..d200b6e43 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -412,7 +412,7 @@ static void _workspace_show(Con *workspace) { if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) { /* focus for now… */ next->urgent = false; - con_focus(next); + con_activate(next); /* … but immediately reset urgency flags; they will be set to false by * the timer callback in case the container is focused at the time of @@ -435,7 +435,7 @@ static void _workspace_show(Con *workspace) { ev_timer_again(main_loop, focused->urgency_timer); } } else - con_focus(next); + con_activate(next); ipc_send_workspace_event("focus", workspace, current); @@ -837,7 +837,7 @@ void ws_force_orientation(Con *ws, orientation_t orientation) { con_fix_percent(ws); if (old_focused) - con_focus(old_focused); + con_activate(old_focused); } /* diff --git a/testcases/t/293-focus-follows-mouse.t b/testcases/t/293-focus-follows-mouse.t index 9fb1004e6..0cd6e5c32 100644 --- a/testcases/t/293-focus-follows-mouse.t +++ b/testcases/t/293-focus-follows-mouse.t @@ -57,4 +57,32 @@ is($x->input_focus, $second->id, 'second (tabbed) window focused'); synced_warp_pointer(0, 0); is($x->input_focus, $second->id, 'second window still focused'); +################################################################### +# Test that floating windows are focused but not raised to the top. +# See issue #2990. +################################################################### + +my $ws; +my $tmp = fresh_workspace; +my ($first_floating, $second_floating); + +synced_warp_pointer(0, 0); +$first_floating = open_floating_window; +$first_floating->rect(X11::XCB::Rect->new(x => 1, y => 1, width => 100, height => 100)); +$second_floating = open_floating_window; +$second_floating->rect(X11::XCB::Rect->new(x => 50, y => 50, width => 100, height => 100)); +sync_with_i3; +$first = open_window; + +is($x->input_focus, $first->id, 'first (tiling) window focused'); +$ws = get_ws($tmp); +is($ws->{floating_nodes}->[1]->{nodes}->[0]->{window}, $second_floating->id, 'second floating on top'); +is($ws->{floating_nodes}->[0]->{nodes}->[0]->{window}, $first_floating->id, 'first floating behind'); + +synced_warp_pointer(40, 40); +is($x->input_focus, $first_floating->id, 'first floating window focused'); +$ws = get_ws($tmp); +is($ws->{floating_nodes}->[1]->{nodes}->[0]->{window}, $second_floating->id, 'second floating still on top'); +is($ws->{floating_nodes}->[0]->{nodes}->[0]->{window}, $first_floating->id, 'first floating still behind'); + done_testing; From 9ea5c238e12ad10bff1840704e96770f5654747f Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Wed, 7 Feb 2018 08:01:26 +0000 Subject: [PATCH 86/99] Correct spelling a mistake. --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index b2cab272f..3778e1c91 100644 --- a/debian/changelog +++ b/debian/changelog @@ -320,7 +320,7 @@ i3-wm (4.0.2-1) unstable; urgency=low * Bugfix: Use correct format string in load_layout (fixes crash in restart) * Bugfix: Fix border rendering (border lines were "cutting" through) * Bugfix: Raise floating windows immediately when dragging/resizing - * Bugfix: Make focus switching work accross outputs again + * Bugfix: Make focus switching work across outputs again * Bugfix: migration-script: handle resize top/bottom correctly * Bugfix: Fix focus issue when moving containers to workspaces * Bugfix: Warp cursor when changing outputs again From 147d3c354abc7e322dbb10246824f4b6f52b97bd Mon Sep 17 00:00:00 2001 From: Alex Lu Date: Sun, 11 Feb 2018 19:05:43 +0800 Subject: [PATCH 87/99] Update EWMH atoms when closing a workspace container fix #3126 --- src/tree.c | 7 ++ testcases/t/294-update-ewmh-atoms.t | 111 ++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 testcases/t/294-update-ewmh-atoms.t diff --git a/src/tree.c b/src/tree.c index b8bc732bc..6c6a614e2 100644 --- a/src/tree.c +++ b/src/tree.c @@ -330,6 +330,13 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par DLOG("parent container killed\n"); } + if (ws == con) { + DLOG("Closing a workspace container, updating EWMH atoms\n"); + ewmh_update_number_of_desktops(); + ewmh_update_desktop_names(); + ewmh_update_wm_desktop(); + } + con_free(con); /* in the case of floating windows, we already focused another container diff --git a/testcases/t/294-update-ewmh-atoms.t b/testcases/t/294-update-ewmh-atoms.t new file mode 100644 index 000000000..047cc1197 --- /dev/null +++ b/testcases/t/294-update-ewmh-atoms.t @@ -0,0 +1,111 @@ +#!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) +# +# Verifies _NET_DESKTOP_NAMES, _NET_CURRENT_DESKTOP and _NET_CURRENT_DESKTOP +# are updated properly when closing an inactive workspace container. +# See github issue #3126 + +use i3test; + +sub get_desktop_names { + sync_with_i3; + + my $cookie = $x->get_property( + 0, + $x->get_root_window(), + $x->atom(name => '_NET_DESKTOP_NAMES')->id, + $x->atom(name => 'UTF8_STRING')->id, + 0, + 4096, + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + + return 0 if $reply->{value_len} == 0; + + # the property is a null-delimited list of utf8 strings ;; + return split /\0/, $reply->{value}; +} + +sub get_num_of_desktops { + sync_with_i3; + + my $cookie = $x->get_property( + 0, + $x->get_root_window(), + $x->atom(name => '_NET_NUMBER_OF_DESKTOPS')->id, + $x->atom(name => 'CARDINAL')->id, + 0, + 4, + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + + return undef if $reply->{value_len} != 1; + return undef if $reply->{format} != 32; + return undef if $reply->{type} != $x->atom(name => 'CARDINAL')->id,; + + return unpack 'L', $reply->{value}; +} + +sub get_current_desktop { + sync_with_i3; + + my $cookie = $x->get_property( + 0, + $x->get_root_window(), + $x->atom(name => '_NET_CURRENT_DESKTOP')->id, + $x->atom(name => 'CARDINAL')->id, + 0, + 4, + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + + return undef if $reply->{value_len} != 1; + return undef if $reply->{format} != 32; + return undef if $reply->{type} != $x->atom(name => 'CARDINAL')->id,; + + return unpack 'L', $reply->{value}; +} + +cmd 'workspace 0'; +my $first = open_window; + +cmd 'workspace 1'; +my $second = open_window; + +cmd 'workspace 2'; +my $third = open_window; + +# Sanity check +is(get_current_desktop, 2); +is(get_num_of_desktops, 3); +my @actual_names = get_desktop_names; +my @expected_names = ('0', '1', '2'); +is_deeply(\@actual_names, \@expected_names); + +# Kill first window to close a workspace. +cmd '[id="' . $second->id . '"] kill'; + +is(get_current_desktop, 2, '_NET_CURRENT_DESKTOP should be updated'); +is(get_num_of_desktops, 2, '_NET_NUMBER_OF_DESKTOPS should be updated'); +my @actual_names = get_desktop_names; +my @expected_names = ('0', '2'); +is_deeply(\@actual_names, \@expected_names, '_NET_DESKTOP_NAMES should be updated'); + + +done_testing; From f8d6c10d7c34fd9510fc220ebf14d8cde5866708 Mon Sep 17 00:00:00 2001 From: Bennett Piater Date: Mon, 12 Feb 2018 15:11:19 +0100 Subject: [PATCH 88/99] Add default_{,floating_}border to config userguide: document default_{,floating_}border 201-config-parser.t: add new tokens to $expected_all_tokens 201-config-parser-t: fix whitespace closes i3/i3#2702 --- docs/userguide | 23 ++++++++++++--------- include/config_directives.h | 2 +- parser-specs/config.spec | 23 +++++++++++---------- src/config_directives.c | 5 +++-- testcases/t/201-config-parser.t | 36 ++++++++++++++++++++++++--------- 5 files changed, 55 insertions(+), 34 deletions(-) diff --git a/docs/userguide b/docs/userguide index 974135acb..9b367feee 100644 --- a/docs/userguide +++ b/docs/userguide @@ -586,23 +586,26 @@ workspace_layout default|stacking|tabbed workspace_layout tabbed --------------------- -=== Border style for new windows +=== Default border style for new windows This option determines which border style new windows will have. The default is -+normal+. Note that new_float applies only to windows which are starting out as ++normal+. Note that default_floating_border applies only to windows which are starting out as floating windows, e.g., dialog windows, but not windows that are floated later on. *Syntax*: --------------------------------------------- -new_window normal|none|pixel -new_window normal|pixel -new_float normal|none|pixel -new_float normal|pixel +default_border normal|none|pixel +default_border normal|pixel +default_floating_border normal|none|pixel +default_floating_border normal|pixel --------------------------------------------- +Please note that +new_window+ and +new_float+ have been deprecated in favor of the above options +and will be removed in a future release. We strongly recommend using the new options instead. + *Example*: --------------------- -new_window pixel +default_border pixel --------------------- The "normal" and "pixel" border styles support an optional border width in @@ -610,11 +613,11 @@ pixels: *Example*: --------------------- -# The same as new_window none -new_window pixel 0 +# The same as default_border none +default_border pixel 0 # A 3 px border -new_window pixel 3 +default_border pixel 3 --------------------- diff --git a/include/config_directives.h b/include/config_directives.h index 60c7a4b14..187b550c9 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -67,7 +67,7 @@ CFGFUN(popup_during_fullscreen, const char *value); CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border); CFGFUN(color_single, const char *colorclass, const char *color); CFGFUN(floating_modifier, const char *modifiers); -CFGFUN(new_window, const char *windowtype, const char *border, const long width); +CFGFUN(default_border, const char *windowtype, const char *border, const long width); CFGFUN(workspace, const char *workspace, const char *output); CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command); diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 3d3ffb283..60a1fc67a 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -29,7 +29,8 @@ state INITIAL: 'floating_modifier' -> FLOATING_MODIFIER 'default_orientation' -> DEFAULT_ORIENTATION 'workspace_layout' -> WORKSPACE_LAYOUT - windowtype = 'new_window', 'new_float' -> NEW_WINDOW + windowtype = 'default_border', 'new_window', 'default_floating_border', 'new_float' + -> DEFAULT_BORDER 'hide_edge_borders' -> HIDE_EDGE_BORDERS 'for_window' -> FOR_WINDOW 'assign' -> ASSIGN @@ -105,25 +106,25 @@ state WORKSPACE_LAYOUT: layout = 'default', 'stacking', 'stacked', 'tabbed' -> call cfg_workspace_layout($layout) -# new_window -# new_float -state NEW_WINDOW: +# +# +state DEFAULT_BORDER: border = 'normal', 'pixel' - -> NEW_WINDOW_PIXELS + -> DEFAULT_BORDER_PIXELS border = '1pixel', 'none' - -> call cfg_new_window($windowtype, $border, -1) + -> call cfg_default_border($windowtype, $border, -1) -state NEW_WINDOW_PIXELS: +state DEFAULT_BORDER_PIXELS: end - -> call cfg_new_window($windowtype, $border, 2) + -> call cfg_default_border($windowtype, $border, 2) width = number - -> NEW_WINDOW_PIXELS_PX + -> DEFAULT_BORDER_PIXELS_PX -state NEW_WINDOW_PIXELS_PX: +state DEFAULT_BORDER_PIXELS_PX: 'px' -> end - -> call cfg_new_window($windowtype, $border, &width) + -> call cfg_default_border($windowtype, $border, &width) # hide_edge_borders # also hide_edge_borders for compatibility diff --git a/src/config_directives.c b/src/config_directives.c index a3b1f776c..ad6d65b5f 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -197,7 +197,7 @@ CFGFUN(workspace_layout, const char *layout) { config.default_layout = L_TABBED; } -CFGFUN(new_window, const char *windowtype, const char *border, const long width) { +CFGFUN(default_border, const char *windowtype, const char *border, const long width) { int border_style; int border_width; @@ -215,7 +215,8 @@ CFGFUN(new_window, const char *windowtype, const char *border, const long width) border_width = width; } - if (strcmp(windowtype, "new_window") == 0) { + if ((strcmp(windowtype, "default_border") == 0) || + (strcmp(windowtype, "new_window") == 0)) { DLOG("default tiled border style = %d and border width = %d (%d physical px)\n", border_style, border_width, logical_px(border_width)); config.default_border = border_style; diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index ece7e9b9a..6857b6211 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -145,7 +145,7 @@ is(parser_calls($config), $config = <<'EOT'; floating_minimum_size 80x55 -floating_minimum_size 80 x 55 +floating_minimum_size 80 x 55 floating_maximum_size 73 x 10 EOT @@ -245,8 +245,8 @@ is(parser_calls($config), ################################################################################ $config = <<'EOT'; -workspace "3" output DP-1 -workspace "3" output VGA-1 +workspace "3" output DP-1 +workspace "3" output VGA-1 EOT $expected = <<'EOT'; @@ -266,19 +266,33 @@ $config = <<'EOT'; new_window 1pixel new_window normal new_window none +default_border 1pixel +default_border normal +default_border none new_float 1pixel new_float normal new_float none +default_floating_border 1pixel +default_floating_border normal +default_floating_border none EOT $expected = <<'EOT'; -cfg_new_window(new_window, 1pixel, -1) -cfg_new_window(new_window, normal, 2) -cfg_new_window(new_window, none, -1) -cfg_new_window(new_float, 1pixel, -1) -cfg_new_window(new_float, normal, 2) -cfg_new_window(new_float, none, -1) -EOT +cfg_default_border(new_window, 1pixel, -1) +cfg_default_border(new_window, normal, 2) +cfg_default_border(new_window, none, -1) +cfg_default_border(default_border, 1pixel, -1) +cfg_default_border(default_border, normal, 2) +cfg_default_border(default_border, none, -1) +cfg_default_border(new_float, 1pixel, -1) +cfg_default_border(new_float, normal, 2) +cfg_default_border(new_float, none, -1) +cfg_default_border(default_floating_border, 1pixel, -1) +cfg_default_border(default_floating_border, normal, 2) +cfg_default_border(default_floating_border, none, -1) +EOT + +# TODO: are there no tests for "border pixel 1" etc? is(parser_calls($config), $expected, @@ -462,7 +476,9 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: , ' floating_modifier default_orientation workspace_layout + default_border new_window + default_floating_border new_float hide_edge_borders for_window From 306a7e46d335fdabfbb14c20f66758be936a21cd Mon Sep 17 00:00:00 2001 From: Ben Creasy Date: Sun, 18 Feb 2018 18:44:40 -0800 Subject: [PATCH 89/99] Remove hardcoded 2013 date The front page and docs landing page emphasize documentation, but the user guide looks like it hasn't been updated since 2013. It seems from the history of the file that this number is misleading. Let me know if I'm missing anything. --- docs/userguide | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 974135acb..4a400dfc8 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1,7 +1,6 @@ i3 User’s Guide =============== Michael Stapelberg -March 2013 This document contains all the information you need to configure and use the i3 window manager. If it does not, please check https://www.reddit.com/r/i3wm/ From 49f7e6bed06cee95015dc17762db4353b92386de Mon Sep 17 00:00:00 2001 From: DebianWall Date: Mon, 19 Feb 2018 15:35:01 +0100 Subject: [PATCH 90/99] Added guake and tilda. --- i3-sensible-terminal | 2 +- man/i3-sensible-terminal.man | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/i3-sensible-terminal b/i3-sensible-terminal index 23cb5688f..f1eb256ec 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -8,7 +8,7 @@ # We welcome patches that add distribution-specific mechanisms to find the # preferred terminal emulator. On Debian, there is the x-terminal-emulator # symlink for example. -for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty; do +for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda; do if command -v "$terminal" > /dev/null 2>&1; then exec "$terminal" "$@" fi diff --git a/man/i3-sensible-terminal.man b/man/i3-sensible-terminal.man index bccc824a8..894af9120 100644 --- a/man/i3-sensible-terminal.man +++ b/man/i3-sensible-terminal.man @@ -45,6 +45,8 @@ It tries to start one of the following (in that order): * terminix * konsole * kitty +* guake +* tilda Please don’t complain about the order: If the user has any preference, they will have $TERMINAL set or modified their i3 configuration file. From acc58a6763e497608a9fe4ae9ff4034d18e8641c Mon Sep 17 00:00:00 2001 From: DebianWall Date: Mon, 19 Feb 2018 15:39:46 +0100 Subject: [PATCH 91/99] Added gVim to i3-sensible-editor --- i3-sensible-editor | 2 +- man/i3-sensible-editor.man | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/i3-sensible-editor b/i3-sensible-editor index ad3f6bddb..dc95865de 100755 --- a/i3-sensible-editor +++ b/i3-sensible-editor @@ -9,7 +9,7 @@ # mechanism to find the preferred editor # Hopefully one of these is installed (no flamewars about preference please!): -for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mcedit; do +for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mcedit gvim; do if command -v "$editor" > /dev/null 2>&1; then exec "$editor" "$@" fi diff --git a/man/i3-sensible-editor.man b/man/i3-sensible-editor.man index effae6c06..4f16d6c1e 100644 --- a/man/i3-sensible-editor.man +++ b/man/i3-sensible-editor.man @@ -30,6 +30,7 @@ It tries to start one of the following (in that order): * jed * gedit * mcedit +* gvim Please don’t complain about the order: If the user has any preference, they will have $VISUAL or $EDITOR set. From 5f453914a04c193baf5b4239d83116d33a74de62 Mon Sep 17 00:00:00 2001 From: walker0643 <> Date: Wed, 21 Feb 2018 16:55:55 -0500 Subject: [PATCH 92/99] contrib/dump-asy.pl: add prerequisites check and fix warnings about empty container names --- contrib/dump-asy.pl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl index 4fcf9b77b..9bb2db3aa 100755 --- a/contrib/dump-asy.pl +++ b/contrib/dump-asy.pl @@ -15,6 +15,12 @@ use File::Temp; use File::Basename; use v5.10; +use IPC::Cmd qw[can_run]; + +# prerequisites check so we can be specific about failures caused +# by not having these tools in the path +can_run('asy') or die 'Please install asymptote'; +can_run('gv') or die 'Please install gv'; my $i3 = i3(); @@ -31,7 +37,7 @@ sub dump_node { my $o = ($n->{orientation} eq 'none' ? "u" : ($n->{orientation} eq 'horizontal' ? "h" : "v")); my $w = (defined($n->{window}) ? $n->{window} : "N"); - my $na = $n->{name}; + my $na = ($n->{name} or "[Empty]"); $na =~ s/#/\\#/g; $na =~ s/\$/\\\$/g; $na =~ s/&/\\&/g; From 128f4dcb2e7afbccaf7d88271b9f42472292ddf2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Feb 2018 08:24:53 +0100 Subject: [PATCH 93/99] debian: install contrib/ scripts as examples (Thanks anarcat) --- debian/i3-wm.install | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/debian/i3-wm.install b/debian/i3-wm.install index bff5028cb..823d40979 100644 --- a/debian/i3-wm.install +++ b/debian/i3-wm.install @@ -1,2 +1,7 @@ debian/tmp/etc debian/tmp/usr +contrib/dump-asy.pl usr/share/doc/i3-wm/examples/ +contrib/gtk-tree-watch.pl usr/share/doc/i3-wm/examples/ +contrib/i3-wsbar usr/share/doc/i3-wm/examples/ +contrib/per-workspace-layout.pl usr/share/doc/i3-wm/examples/ +contrib/trivial-bar-script.sh usr/share/doc/i3-wm/examples/ From b280f103cf4d5690a404dd6b2c79c9760e4408f2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Feb 2018 08:55:57 +0100 Subject: [PATCH 94/99] debian: explicitly use gzip compression fixes #3146 --- debian/rules | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/debian/rules b/debian/rules index e4da3db85..d5c306867 100755 --- a/debian/rules +++ b/debian/rules @@ -17,5 +17,9 @@ override_dh_auto_configure: # The default is /usr/share/doc/i3 dh_auto_configure -- --docdir=/usr/share/doc/i3-wm +override_dh_builddeb: + # bintray does not support xz currently. + dh_builddeb -- -Zgzip + %: dh $@ --parallel --builddirectory=build --with=autoreconf From 27e60bd9d024353b8b5a2fe45e8e3c07b8e0c186 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 25 Feb 2018 13:55:30 +0100 Subject: [PATCH 95/99] Makefile: include contrib/ in dist tarballs (#3152) unbreaks the debian package build --- Makefile.am | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile.am b/Makefile.am index f1ba3f637..f37933f85 100644 --- a/Makefile.am +++ b/Makefile.am @@ -78,6 +78,11 @@ EXTRA_DIST = \ AnyEvent-I3/t/manifest.t \ AnyEvent-I3/t/pod-coverage.t \ AnyEvent-I3/t/pod.t \ + contrib/dump-asy.pl \ + contrib/gtk-tree-watch.pl \ + contrib/i3-wsbar \ + contrib/per-workspace-layout.pl \ + contrib/trivial-bar-script.sh \ docs/asciidoc-git.conf \ docs/bigpicture.png \ docs/i3-pod2html \ From 60200b1d3c4c6f50f7e8e0b19c26d8ccd77d0cea Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 26 Feb 2018 03:26:05 +0200 Subject: [PATCH 96/99] Don't raise floating windows when workspace is shown From comment: https://github.com/i3/i3/issues/2990#issuecomment-368345169 To easily reproduce: 1. Open 2 floating windows 2. Focus (with `focus_follows_mouse`) the one behind 3. Move the mouse to the other workspace 4. Move the mouse inside the previous workspace (without it even touching a window) --- src/workspace.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index d200b6e43..4c14e8c1e 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -412,7 +412,7 @@ static void _workspace_show(Con *workspace) { if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) { /* focus for now… */ next->urgent = false; - con_activate(next); + con_focus(next); /* … but immediately reset urgency flags; they will be set to false by * the timer callback in case the container is focused at the time of @@ -435,7 +435,7 @@ static void _workspace_show(Con *workspace) { ev_timer_again(main_loop, focused->urgency_timer); } } else - con_activate(next); + con_focus(next); ipc_send_workspace_event("focus", workspace, current); From dc2363a66586e785d3636cff9abe81521973f417 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 13 Dec 2017 16:29:54 +0200 Subject: [PATCH 97/99] Introduce *focus_order functions --- include/con.h | 16 +++++++++++++++ src/con.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/include/con.h b/include/con.h index 726fec96d..58123a87f 100644 --- a/include/con.h +++ b/include/con.h @@ -228,6 +228,22 @@ void con_unmark(Con *con, const char *name); */ Con *con_for_window(Con *con, i3Window *window, Match **store_match); +/** + * Iterate over the container's focus stack and return an array with the + * containers inside it, ordered from higher focus order to lowest. + * + */ +Con **get_focus_order(Con *con); + +/** + * Clear the container's focus stack and re-add it using the provided container + * array. The function doesn't check if the provided array contains the same + * containers with the previous focus stack but will not add floating containers + * in the new focus stack if container is not a workspace. + * + */ +void set_focus_order(Con *con, Con **focus_order); + /** * Returns the number of children of this container. * diff --git a/src/con.c b/src/con.c index 2d9b8691b..47e9a342a 100644 --- a/src/con.c +++ b/src/con.c @@ -809,6 +809,62 @@ Con *con_for_window(Con *con, i3Window *window, Match **store_match) { return NULL; } +static int num_focus_heads(Con *con) { + int focus_heads = 0; + + Con *current; + TAILQ_FOREACH(current, &(con->focus_head), focused) { + focus_heads++; + } + + return focus_heads; +} + +/* + * Iterate over the container's focus stack and return an array with the + * containers inside it, ordered from higher focus order to lowest. + * + */ +Con **get_focus_order(Con *con) { + const int focus_heads = num_focus_heads(con); + Con **focus_order = smalloc(focus_heads * sizeof(Con *)); + Con *current; + int idx = 0; + TAILQ_FOREACH(current, &(con->focus_head), focused) { + assert(idx < focus_heads); + focus_order[idx++] = current; + } + + return focus_order; +} + +/* + * Clear the container's focus stack and re-add it using the provided container + * array. The function doesn't check if the provided array contains the same + * containers with the previous focus stack but will not add floating containers + * in the new focus stack if container is not a workspace. + * + */ +void set_focus_order(Con *con, Con **focus_order) { + int focus_heads = 0; + while (!TAILQ_EMPTY(&(con->focus_head))) { + Con *current = TAILQ_FIRST(&(con->focus_head)); + + TAILQ_REMOVE(&(con->focus_head), current, focused); + focus_heads++; + } + + for (int idx = 0; idx < focus_heads; idx++) { + /* Useful when encapsulating a workspace. */ + if (con->type != CT_WORKSPACE && con_inside_floating(focus_order[idx])) { + focus_heads++; + continue; + } + + TAILQ_INSERT_TAIL(&(con->focus_head), focus_order[idx], focused); + } +} + /* * Returns the number of children of this container. * From cb73fd5e31eefb3243d70db4026253e7bcafb060 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 13 Dec 2017 16:15:01 +0200 Subject: [PATCH 98/99] Fix focus order issues when encapsulating workspaces See new tests for an explanation of the problem. --- src/con.c | 18 ++-- src/workspace.c | 16 ++-- testcases/t/294-focus-order.t | 109 +++++++++++++++++++++++++ testcases/t/510-focus-across-outputs.t | 1 + 4 files changed, 125 insertions(+), 19 deletions(-) create mode 100644 testcases/t/294-focus-order.t diff --git a/src/con.c b/src/con.c index 47e9a342a..985d07da2 100644 --- a/src/con.c +++ b/src/con.c @@ -1837,17 +1837,9 @@ void con_set_layout(Con *con, layout_t layout) { new->layout = layout; new->last_split_layout = con->last_split_layout; - /* Save the container that was focused before we move containers - * around, but only if the container is visible (otherwise focus - * will be restored properly automatically when switching). */ - Con *old_focused = TAILQ_FIRST(&(con->focus_head)); - if (old_focused == TAILQ_END(&(con->focus_head))) - old_focused = NULL; - if (old_focused != NULL && - !workspace_is_visible(con_get_workspace(old_focused))) - old_focused = NULL; - /* 3: move the existing cons of this workspace below the new con */ + Con **focus_order = get_focus_order(con); + DLOG("Moving cons\n"); Con *child; while (!TAILQ_EMPTY(&(con->nodes_head))) { @@ -1856,13 +1848,13 @@ void con_set_layout(Con *con, layout_t layout) { con_attach(child, new, true); } + set_focus_order(new, focus_order); + free(focus_order); + /* 4: attach the new split container to the workspace */ DLOG("Attaching new split to ws\n"); con_attach(new, con, false); - if (old_focused) - con_activate(old_focused); - tree_flatten(croot); } con_force_split_parents_redraw(con); diff --git a/src/workspace.c b/src/workspace.c index d200b6e43..cbef473b9 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -815,9 +815,9 @@ void ws_force_orientation(Con *ws, orientation_t orientation) { /* 2: copy layout from workspace */ split->layout = ws->layout; - Con *old_focused = TAILQ_FIRST(&(ws->focus_head)); - /* 3: move the existing cons of this workspace below the new con */ + Con **focus_order = get_focus_order(ws); + DLOG("Moving cons\n"); while (!TAILQ_EMPTY(&(ws->nodes_head))) { Con *child = TAILQ_FIRST(&(ws->nodes_head)); @@ -825,6 +825,9 @@ void ws_force_orientation(Con *ws, orientation_t orientation) { con_attach(child, split, true); } + set_focus_order(split, focus_order); + free(focus_order); + /* 4: switch workspace layout */ ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV; DLOG("split->layout = %d, ws->layout = %d\n", split->layout, ws->layout); @@ -835,9 +838,6 @@ void ws_force_orientation(Con *ws, orientation_t orientation) { /* 6: fix the percentages */ con_fix_percent(ws); - - if (old_focused) - con_activate(old_focused); } /* @@ -892,9 +892,10 @@ Con *workspace_encapsulate(Con *ws) { new->parent = ws; new->layout = ws->layout; + Con **focus_order = get_focus_order(ws); + DLOG("Moving children of workspace %p / %s into container %p\n", ws, ws->name, new); - Con *child; while (!TAILQ_EMPTY(&(ws->nodes_head))) { child = TAILQ_FIRST(&(ws->nodes_head)); @@ -902,6 +903,9 @@ Con *workspace_encapsulate(Con *ws) { con_attach(child, new, true); } + set_focus_order(new, focus_order); + free(focus_order); + con_attach(new, ws, true); return new; diff --git a/testcases/t/294-focus-order.t b/testcases/t/294-focus-order.t new file mode 100644 index 000000000..41c0bf070 --- /dev/null +++ b/testcases/t/294-focus-order.t @@ -0,0 +1,109 @@ +#!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) +# +# Verify that the corrent focus stack order is preserved after various +# operations. +use i3test; + +sub kill_and_confirm_focus { + my $focus = shift; + my $msg = shift; + cmd "kill"; + sync_with_i3; + is($x->input_focus, $focus, $msg); +} + +my @windows; + +sub focus_windows { + for (my $i = $#windows; $i >= 0; $i--) { + cmd '[id=' . $windows[$i]->id . '] focus'; + } +} + +sub confirm_focus { + my $msg = shift; + sync_with_i3; + is($x->input_focus, $windows[0]->id, $msg . ': window 0 focused'); + foreach my $i (1 .. $#windows) { + kill_and_confirm_focus($windows[$i]->id, "$msg: window $i focused"); + } + cmd 'kill'; + @windows = (); +} + +##################################################################### +# Open 5 windows, focus them in a custom order and then change to +# tabbed layout. The focus order should be preserved. +##################################################################### + +fresh_workspace; + +$windows[3] = open_window; +$windows[1] = open_window; +$windows[0] = open_window; +$windows[2] = open_window; +$windows[4] = open_window; +focus_windows; + +cmd 'layout tabbed'; +confirm_focus('tabbed'); + +##################################################################### +# Same as above but with stacked. +##################################################################### + +fresh_workspace; + +$windows[3] = open_window; +$windows[1] = open_window; +$windows[0] = open_window; +$windows[2] = open_window; +$windows[4] = open_window; +focus_windows; + +cmd 'layout stacked'; +confirm_focus('stacked'); + +##################################################################### +# Open 4 windows horizontally, move the last one down. The focus +# order should be preserved. +##################################################################### + +fresh_workspace; +$windows[3] = open_window; +$windows[2] = open_window; +$windows[1] = open_window; +$windows[0] = open_window; + +cmd 'move down'; +confirm_focus('split-h + move'); + +##################################################################### +# Same as above but with a vertical split. +##################################################################### + +fresh_workspace; +$windows[3] = open_window; +cmd 'split v'; +$windows[2] = open_window; +$windows[1] = open_window; +$windows[0] = open_window; + +cmd 'move left'; +confirm_focus('split-v + move'); + +done_testing; diff --git a/testcases/t/510-focus-across-outputs.t b/testcases/t/510-focus-across-outputs.t index 07a2115c0..065437f0b 100644 --- a/testcases/t/510-focus-across-outputs.t +++ b/testcases/t/510-focus-across-outputs.t @@ -119,6 +119,7 @@ is($x->input_focus, $eighth->id, 'eighth window focused'); cmd 'focus parent'; cmd 'focus parent'; cmd 'split v'; +cmd '[id=' . $sixth->id . '] focus'; reset_focus $s3_ws; cmd "workspace $s1_ws"; From 021d40f6661ae960fc639c3d0d4e355663cc83df Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 9 Mar 2018 08:25:07 +0100 Subject: [PATCH 99/99] Bugfix: ignore ConfigureNotify with width == 0 || height == 0 fixes #3132 --- i3-nagbar/main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 7d38f7314..e4628e303 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -575,7 +575,9 @@ int main(int argc, char *argv[]) { case XCB_CONFIGURE_NOTIFY: { xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event; - draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height); + if (configure_notify->width > 0 && configure_notify->height > 0) { + draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height); + } break; } }