From 8ee4a4a3e16bc3dff9be689aed93807b91d7e075 Mon Sep 17 00:00:00 2001 From: Hritik Vijay Date: Sat, 10 Mar 2018 02:38:47 +0530 Subject: [PATCH 001/218] Fixed typo --- i3bar/include/child.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3bar/include/child.h b/i3bar/include/child.h index 9479fac1..51dd5a19 100644 --- a/i3bar/include/child.h +++ b/i3bar/include/child.h @@ -31,7 +31,7 @@ typedef struct { */ int stop_signal; /** - * The signal requested by the client to inform it of theun hidden state of i3bar + * The signal requested by the client to inform it of the unhidden state of i3bar */ int cont_signal; From e6b2fefe26b99b17a695ae130a8d0bc32fcf6e16 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 10 Mar 2018 18:35:11 +0100 Subject: [PATCH 002/218] debian: update changelog --- debian/changelog | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 3778e1c9..7695bb4c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,14 @@ -i3-wm (4.14.2-1) unstable; urgency=medium +i3-wm (4.15.1-1) unstable; urgency=medium * UNRELEASED - -- Michael Stapelberg Mon, 25 Sep 2017 08:55:22 +0200 + -- Michael Stapelberg Sat, 10 Mar 2018 17:27:26 +0100 + +i3-wm (4.15-1) unstable; urgency=medium + + * New upstream release. + + -- Michael Stapelberg Sat, 10 Mar 2018 17:27:26 +0100 i3-wm (4.14.1-1) unstable; urgency=medium From 8513107f099ba4950bedfc72f4131177bc8bb2cd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 10 Mar 2018 18:52:27 +0100 Subject: [PATCH 003/218] =?UTF-8?q?release:=20disable=20git=E2=80=99s=20re?= =?UTF-8?q?name=20protection=20(prevented=20merging)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release.sh b/release.sh index cfb4beae..fb017bb1 100755 --- a/release.sh +++ b/release.sh @@ -85,12 +85,12 @@ if [ "${RELEASE_BRANCH}" = "master" ]; then git checkout master git merge --no-ff release-${RELEASE_VERSION} -m "Merge branch 'release-${RELEASE_VERSION}'" git checkout next - git merge --no-ff -X ours master -m "Merge branch 'master' into next" + git merge --no-ff -s recursive -X ours -X no-renames master -m "Merge branch 'master' into next" else git checkout next git merge --no-ff release-${RELEASE_VERSION} -m "Merge branch 'release-${RELEASE_VERSION}'" git checkout master - git merge --no-ff -X theirs next -m "Merge branch 'next' into master" + git merge --no-ff -s recursive -X theirs -X no-renames next -m "Merge branch 'next' into master" fi git remote remove origin From a07980f70b911c1899eee504df8bc2f4882b7419 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 10 Mar 2018 18:52:50 +0100 Subject: [PATCH 004/218] release: also build a Debian source-only upload --- release.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/release.sh b/release.sh index fb017bb1..8e2852a8 100755 --- a/release.sh +++ b/release.sh @@ -126,6 +126,7 @@ WORKDIR /usr/src RUN mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' i3-${RELEASE_VERSION}/debian/control WORKDIR /usr/src/i3-${RELEASE_VERSION} RUN dpkg-buildpackage -sa -j8 +RUN dpkg-buildpackage -S -sa -j8 EOT CONTAINER_NAME=$(echo "i3-${TMPDIR}" | sed 's,/,,g') @@ -139,7 +140,7 @@ echo "Content of resulting package’s .changes file:" cat ${TMPDIR}/debian/*.changes # debsign is in devscripts, which is available in fedora and debian -debsign -k4AC8EE1D ${TMPDIR}/debian/*.changes +debsign --no-re-sign -k4AC8EE1D ${TMPDIR}/debian/*.changes # TODO: docker cleanup From c74dd61f594192f8e0f392374e677ddf63e43442 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 10 Mar 2018 18:53:04 +0100 Subject: [PATCH 005/218] release: new-enough dput no longer needs an explicit file name --- release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.sh b/release.sh index 8e2852a8..54c2ea81 100755 --- a/release.sh +++ b/release.sh @@ -228,7 +228,7 @@ echo " cd ${TMPDIR}/i3.github.io" echo " git push" echo "" echo " cd ${TMPDIR}/debian" -echo " dput *.changes" +echo " dput" echo "" echo " cd ${TMPDIR}" echo " sendmail -t < email.txt" From a05ba370cdd3058645575505aa12959197705908 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 10 Mar 2018 18:53:20 +0100 Subject: [PATCH 006/218] update release.sh after release --- release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release.sh b/release.sh index 54c2ea81..0190fcfb 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.14.1" +export RELEASE_VERSION="4.15" export PREVIOUS_VERSION="4.14" -export RELEASE_BRANCH="master" +export RELEASE_BRANCH="next" if [ ! -e "../i3.github.io" ] then From 69eec7e5b2743770a502188d15e66ffc7a7067aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sat, 10 Mar 2018 19:18:44 +0100 Subject: [PATCH 007/218] Refocus focused window for FOCUS_IN events on the root window. (#3097) This deals with (admittedly somewhat misbehaving) clients which use XSetInputFocus to take focus, but then don't properly restore focus. This has been observed with TK apps, but also, e.g., Steam. fixes #2722 fixes #3096 --- include/xcb.h | 1 + src/handlers.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/include/xcb.h b/include/xcb.h index 92be7b89..53c932bf 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -53,6 +53,7 @@ ConfigureNotify */ \ XCB_EVENT_MASK_POINTER_MOTION | \ XCB_EVENT_MASK_PROPERTY_CHANGE | \ + XCB_EVENT_MASK_FOCUS_CHANGE | \ XCB_EVENT_MASK_ENTER_WINDOW) #define xmacro(atom) xcb_atom_t A_##atom; diff --git a/src/handlers.c b/src/handlers.c index e1671c3b..50fd8566 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1207,6 +1207,14 @@ static bool handle_clientleader_change(void *data, xcb_connection_t *conn, uint8 */ static void handle_focus_in(xcb_focus_in_event_t *event) { DLOG("focus change in, for window 0x%08x\n", event->event); + + if (event->event == root) { + DLOG("Received focus in for root window, refocusing the focused window.\n"); + con_focus(focused); + focused_id = XCB_NONE; + x_push_changes(croot); + } + Con *con; if ((con = con_by_window_id(event->event)) == NULL || con->window == NULL) return; From 2269b2e7950d0d984ba73eed8d506c9feaeb82e9 Mon Sep 17 00:00:00 2001 From: hwangcc23 Date: Sat, 10 Mar 2018 12:49:09 +0800 Subject: [PATCH 008/218] Add strip_workspace_name See the issue #3163 (https://github.com/i3/i3/issues/3163). Add strip_workspace_name to strip off the workspace name. --- docs/userguide | 6 +++++- i3bar/include/configuration.h | 1 + i3bar/src/config.c | 6 ++++++ i3bar/src/workspaces.c | 17 ++++++++++------- include/config_directives.h | 1 + include/configuration.h | 4 ++++ parser-specs/config.spec | 5 +++++ src/config_directives.c | 4 ++++ src/ipc.c | 3 +++ testcases/t/201-config-parser.t | 2 +- 10 files changed, 40 insertions(+), 9 deletions(-) diff --git a/docs/userguide b/docs/userguide index ba314af1..850b0351 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1594,7 +1594,7 @@ bar { } ------------------------ -=== Strip workspace numbers +=== Strip workspace numbers/name Specifies whether workspace numbers should be displayed within the workspace buttons. This is useful if you want to have a named workspace that stays in @@ -1605,11 +1605,15 @@ the form "[n]:[NAME]" will display only the name. You could use this, for instance, to display Roman numerals rather than digits by naming your workspaces to "1:I", "2:II", "3:III", "4:IV", ... +When +strip_workspace_name+ is set to +yes+, any workspace that has a name of +the form "[n]:[NAME]" will display only the number. + The default is to display the full name within the workspace button. *Syntax*: ------------------------------ strip_workspace_numbers yes|no +strip_workspace_name yes|no ------------------------------ *Example*: diff --git a/i3bar/include/configuration.h b/i3bar/include/configuration.h index 61cac7f6..e60d7483 100644 --- a/i3bar/include/configuration.h +++ b/i3bar/include/configuration.h @@ -52,6 +52,7 @@ typedef struct config_t { bool disable_binding_mode_indicator; bool disable_ws; bool strip_ws_numbers; + bool strip_ws_name; char *bar_id; char *command; char *fontname; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index a58b9bf8..59a44aee 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -297,6 +297,12 @@ static int config_boolean_cb(void *params_, int val) { return 1; } + if (!strcmp(cur_key, "strip_workspace_name")) { + DLOG("strip_workspace_name = %d\n", val); + config.strip_ws_name = val; + return 1; + } + if (!strcmp(cur_key, "verbose")) { DLOG("verbose = %d\n", val); config.verbose = val; diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c index 23324989..7285d150 100644 --- a/i3bar/src/workspaces.c +++ b/i3bar/src/workspaces.c @@ -106,8 +106,8 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t const char *ws_name = (const char *)val; params->workspaces_walk->canonical_name = sstrndup(ws_name, len); - if (config.strip_ws_numbers && params->workspaces_walk->num >= 0) { - /* Special case: strip off the workspace number */ + if ((config.strip_ws_numbers || config.strip_ws_name) && params->workspaces_walk->num >= 0) { + /* Special case: strip off the workspace number/name */ static char ws_num[10]; snprintf(ws_num, sizeof(ws_num), "%d", params->workspaces_walk->num); @@ -119,11 +119,14 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t if (offset && ws_name[offset] == ':') offset += 1; - /* Offset may be equal to length, in which case display the number */ - params->workspaces_walk->name = (offset < len - ? i3string_from_markup_with_length(ws_name + offset, len - offset) - : i3string_from_markup(ws_num)); - + if (config.strip_ws_numbers) { + /* Offset may be equal to length, in which case display the number */ + params->workspaces_walk->name = (offset < len + ? i3string_from_markup_with_length(ws_name + offset, len - offset) + : i3string_from_markup(ws_num)); + } else { + params->workspaces_walk->name = i3string_from_markup(ws_num); + } } else { /* Default case: just save the name */ params->workspaces_walk->name = i3string_from_markup_with_length(ws_name, len); diff --git a/include/config_directives.h b/include/config_directives.h index 187b550c..852325ba 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -96,5 +96,6 @@ CFGFUN(bar_status_command, const char *command); CFGFUN(bar_binding_mode_indicator, const char *value); CFGFUN(bar_workspace_buttons, const char *value); CFGFUN(bar_strip_workspace_numbers, const char *value); +CFGFUN(bar_strip_workspace_name, const char *value); CFGFUN(bar_start); CFGFUN(bar_finish); diff --git a/include/configuration.h b/include/configuration.h index ac800159..87897aaf 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -331,6 +331,10 @@ struct Barconfig { * 'strip_workspace_numbers yes'. */ bool strip_workspace_numbers; + /** Strip workspace name? Configuration option is + * 'strip_workspace_name yes'. */ + bool strip_workspace_name; + /** Hide mode button? Configuration option is 'binding_mode_indicator no' * but we invert the bool for the same reason as hide_workspace_buttons.*/ bool hide_binding_mode_indicator; diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 60a1fc67..9cbc782b 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -455,6 +455,7 @@ state BAR: 'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR 'workspace_buttons' -> BAR_WORKSPACE_BUTTONS 'strip_workspace_numbers' -> BAR_STRIP_WORKSPACE_NUMBERS + 'strip_workspace_name' -> BAR_STRIP_WORKSPACE_NAME 'verbose' -> BAR_VERBOSE 'colors' -> BAR_COLORS_BRACE '}' @@ -555,6 +556,10 @@ state BAR_STRIP_WORKSPACE_NUMBERS: value = word -> call cfg_bar_strip_workspace_numbers($value); BAR +state BAR_STRIP_WORKSPACE_NAME: + value = word + -> call cfg_bar_strip_workspace_name($value); BAR + state BAR_VERBOSE: value = word -> call cfg_bar_verbose($value); BAR diff --git a/src/config_directives.c b/src/config_directives.c index ad6d65b5..da1fb580 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -627,6 +627,10 @@ CFGFUN(bar_strip_workspace_numbers, const char *value) { current_bar->strip_workspace_numbers = eval_boolstr(value); } +CFGFUN(bar_strip_workspace_name, const char *value) { + current_bar->strip_workspace_name = eval_boolstr(value); +} + CFGFUN(bar_start) { current_bar = scalloc(1, sizeof(struct Barconfig)); TAILQ_INIT(&(current_bar->bar_bindings)); diff --git a/src/ipc.c b/src/ipc.c index a1a72b1a..6b6383ec 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -709,6 +709,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { ystr("strip_workspace_numbers"); y(bool, config->strip_workspace_numbers); + ystr("strip_workspace_name"); + y(bool, config->strip_workspace_name); + ystr("binding_mode_indicator"); y(bool, !config->hide_binding_mode_indicator); diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 6857b621..1e7ebfc4 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -725,7 +725,7 @@ EOT $expected = <<'EOT'; cfg_bar_start() cfg_bar_output(LVDS-1) -ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'verbose', 'colors', '}' +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'colors', '}' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: bar { ERROR: CONFIG: Line 2: output LVDS-1 From 9017a17e396646e98da885abfa7f3ecc7da818ef Mon Sep 17 00:00:00 2001 From: Klorax <31596164+klorax@users.noreply.github.com> Date: Sun, 11 Mar 2018 18:04:32 +0100 Subject: [PATCH 009/218] Docs [#3164]: Clarification about X resource value Clarification about X resource value: they are loaded verbatim and must therefore be in the format that i3 uses. Solves #3164. --- docs/userguide | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 850b0351..ccedc577 100644 --- a/docs/userguide +++ b/docs/userguide @@ -730,7 +730,8 @@ resource database to achieve an easily maintainable, consistent color theme across many X applications. Defining a resource will load this resource from the resource database and -assign its value to the specified variable. A fallback must be specified in +assign its value to the specified variable. This is done verbatim and the value +must therefore be in the format that i3 uses. A fallback must be specified in case the resource cannot be loaded from the database. *Syntax*: From 9521f69e11e68a5fdc855c738f4cf500f5ccad7a Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 14 Mar 2018 12:11:53 +0200 Subject: [PATCH 010/218] dump-asy.pl: Add POD usage --- contrib/dump-asy.pl | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl index 9bb2db3a..54a1e490 100755 --- a/contrib/dump-asy.pl +++ b/contrib/dump-asy.pl @@ -1,22 +1,26 @@ #!/usr/bin/env perl # vim:ts=4:sw=4:expandtab -# renders the layout tree using asymptote -# -# ./dump-asy.pl -# will render the entire tree -# ./dump-asy.pl 'name' -# will render the tree starting from the node with the specified name, -# e.g. ./dump-asy.pl 2 will render workspace 2 and below use strict; use warnings; use Data::Dumper; +use Getopt::Long; +use Pod::Usage; use AnyEvent::I3; use File::Temp; use File::Basename; use v5.10; use IPC::Cmd qw[can_run]; +my %options = ( + help => 0, +); +my $result = GetOptions( + "help|?" => \$options{help}, +); + +pod2usage(-verbose => 2, -exitcode => 0) if $options{help}; + # 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'; @@ -84,3 +88,23 @@ my $rep = "$tmp"; $rep =~ s/asy$/eps/; my $tmp_dir = dirname($rep); system("cd $tmp_dir && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep"); + +__END__ + +=head1 NAME + +dump-asy.pl - Render the layout tree using asymptote + +=head1 SYNOPSIS + +dump-asy.pl [workspace] + +=head1 EXAMPLE + +Render the entire tree, run: + + ./dump-asy.pl + +Render the tree starting from the node with the specified name, run: + + ./dump-asy.pl 'name' From 883cf4041d6fcccf8d3003ed279d7fc0fa984bce Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 14 Mar 2018 12:18:58 +0200 Subject: [PATCH 011/218] dump-asy.pl: Add --gv option --- contrib/dump-asy.pl | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl index 54a1e490..866cf8e0 100755 --- a/contrib/dump-asy.pl +++ b/contrib/dump-asy.pl @@ -8,14 +8,17 @@ use Getopt::Long; use Pod::Usage; use AnyEvent::I3; use File::Temp; +use File::Spec; use File::Basename; use v5.10; use IPC::Cmd qw[can_run]; my %options = ( + gv => 1, help => 0, ); my $result = GetOptions( + "gv!" => \$options{gv}, "help|?" => \$options{help}, ); @@ -24,7 +27,7 @@ pod2usage(-verbose => 2, -exitcode => 0) if $options{help}; # 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'; +can_run('gv') or die 'Please install gv' unless !$options{gv}; my $i3 = i3(); @@ -86,8 +89,13 @@ say $tmp "draw(n" . $root->{id} . ", (0, 0));"; close($tmp); my $rep = "$tmp"; $rep =~ s/asy$/eps/; -my $tmp_dir = dirname($rep); -system("cd $tmp_dir && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep"); +if ($options{gv}) { + my $tmp_dir = dirname($rep); + chdir($tmp_dir); +} +system("asy $tmp"); # Create the .eps file. +system("gv --scale=-1000 --noresize --widgetless $rep && rm $rep") if $options{gv}; +system("rm $tmp"); __END__ @@ -108,3 +116,16 @@ Render the entire tree, run: Render the tree starting from the node with the specified name, run: ./dump-asy.pl 'name' + +=head1 OPTIONS + +=over 8 + +=item B<--gv> + +=item B<--no-gv> + +Enable or disable showing the result through gv. If disabled, an .eps file will +be saved in the current working directory. Enabled by default. + +=back From dda340cbc6bfe38d6071b54512ea5c28ea9d5503 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 14 Mar 2018 14:43:12 +0200 Subject: [PATCH 012/218] dump-asy.pl: Add --save option --- contrib/dump-asy.pl | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl index 866cf8e0..c183db33 100755 --- a/contrib/dump-asy.pl +++ b/contrib/dump-asy.pl @@ -15,10 +15,12 @@ use IPC::Cmd qw[can_run]; my %options = ( gv => 1, + save => undef, help => 0, ); my $result = GetOptions( "gv!" => \$options{gv}, + "save=s" => \$options{save}, "help|?" => \$options{help}, ); @@ -91,10 +93,18 @@ my $rep = "$tmp"; $rep =~ s/asy$/eps/; if ($options{gv}) { my $tmp_dir = dirname($rep); + $options{save} = File::Spec->rel2abs($options{save}) if $options{save}; chdir($tmp_dir); +} else { + $rep = basename($rep); # Output in current dir. } system("asy $tmp"); # Create the .eps file. -system("gv --scale=-1000 --noresize --widgetless $rep && rm $rep") if $options{gv}; +system("gv --scale=-1000 --noresize --widgetless $rep") if $options{gv}; +if ($options{save}) { + system("mv $rep ${options{save}}"); +} elsif ($options{gv}) { + system("rm $rep"); +} system("rm $tmp"); __END__ @@ -117,6 +127,10 @@ Render the tree starting from the node with the specified name, run: ./dump-asy.pl 'name' +Render the entire tree, save in file 'file.eps' and show using gv, run: + + ./dump-asy.pl --save file.eps + =head1 OPTIONS =over 8 @@ -128,4 +142,9 @@ Render the tree starting from the node with the specified name, run: Enable or disable showing the result through gv. If disabled, an .eps file will be saved in the current working directory. Enabled by default. +=item B<--save> + +Save result using the specified file-name. If omitted, no file will be saved +when using '--gv' or a random name will be used when using '--no-gv'. + =back From 774a61b47ec0d3c312fad8d47d0e72f929f5cb7e Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 14 Mar 2018 14:59:26 +0200 Subject: [PATCH 013/218] dump-asy.pl: Add marks --- contrib/dump-asy.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl index 9bb2db3a..636b20ce 100755 --- a/contrib/dump-asy.pl +++ b/contrib/dump-asy.pl @@ -47,7 +47,8 @@ sub dump_node { if (!defined($n->{window})) { $type = $n->{layout}; } - my $name = qq|``$na'' ($type)|; + my $marks = $n->{marks} ? ' [' . join('][', @{$n->{marks}}) . ']' : ''; + my $name = qq|``$na'' ($type)$marks|; print $tmp "TreeNode n" . $n->{id} . " = makeNode("; From 6222ab108465c8f3f033acc1db5276b28af27b27 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 23 Sep 2017 19:23:49 +0300 Subject: [PATCH 014/218] Correct insert_con_into's focus handling Change from always putting con on the head of the new parent. Important for moving unfocused containers. --- src/move.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/src/move.c b/src/move.c index 97ca6d40..72cf9d61 100644 --- a/src/move.c +++ b/src/move.c @@ -12,6 +12,54 @@ typedef enum { BEFORE, AFTER } position_t; +/* + * Returns the lowest container in the tree that has both a and b as descendants. + * + */ +static Con *lowest_common_ancestor(Con *a, Con *b) { + Con *parent_a = a; + while (parent_a) { + Con *parent_b = b; + while (parent_b) { + if (parent_a == parent_b) { + return parent_a; + } + parent_b = parent_b->parent; + } + parent_a = parent_a->parent; + } + assert(false); +} + +/* + * Returns the direct child of ancestor that contains con. + * + */ +static Con *child_containing_con_recursively(Con *ancestor, Con *con) { + Con *child = con; + while (child && child->parent != ancestor) { + child = child->parent; + assert(child->parent); + } + return child; +} + +/* + * Returns true if the given container is the focused descendant of ancestor, recursively. + * + */ +static bool is_focused_descendant(Con *con, Con *ancestor) { + Con *current = con; + while (current != ancestor) { + if (TAILQ_FIRST(&(current->parent->focus_head)) != current) { + return false; + } + current = current->parent; + assert(current->parent); + } + return true; +} + /* * This function detaches 'con' from its parent and inserts it either before or * after 'target'. @@ -24,6 +72,52 @@ static void insert_con_into(Con *con, Con *target, position_t position) { * afterwards which might then close the con if it is empty. */ Con *old_parent = con->parent; + /* We compare the focus order of the children of the lowest common ancestor. If con or + * its ancestor is before target's ancestor then con should be placed before the target + * in the focus stack. */ + Con *lca = lowest_common_ancestor(con, parent); + if (lca == con) { + ELOG("Container is being inserted into one of its descendants.\n"); + return; + } + + Con *con_ancestor = child_containing_con_recursively(lca, con); + Con *target_ancestor = child_containing_con_recursively(lca, target); + bool moves_focus_from_ancestor = is_focused_descendant(con, con_ancestor); + bool focus_before; + + /* Determine if con is going to be placed before or after target in the parent's focus stack. */ + if (con_ancestor == target_ancestor) { + /* Happens when the target is con's old parent. Eg with layout V [ A H [ B C ] ], + * if we move C up. Target will be H. */ + focus_before = moves_focus_from_ancestor; + } else { + /* Look at the focus stack order of the children of the lowest common ancestor. */ + Con *current; + TAILQ_FOREACH(current, &(lca->focus_head), focused) { + if (current == con_ancestor || current == target_ancestor) { + break; + } + } + focus_before = (current == con_ancestor); + } + + /* If con is the focused container in our old ancestor we place the new ancestor + * before the old ancestor in the focus stack. Example: + * Consider the layout [ H [ V1 [ A* B ] V2 [ C ] ] ] where A is focused. We move to + * a second workspace and from there we move A to the right and switch back to the + * original workspace. Without the change focus would move to B instead of staying + * with A. */ + if (moves_focus_from_ancestor && focus_before) { + Con *place = TAILQ_PREV(con_ancestor, focus_head, focused); + TAILQ_REMOVE(&(lca->focus_head), target_ancestor, focused); + if (place) { + TAILQ_INSERT_AFTER(&(lca->focus_head), place, target_ancestor, focused); + } else { + TAILQ_INSERT_HEAD(&(lca->focus_head), target_ancestor, focused); + } + } + con_detach(con); con_fix_percent(con->parent); @@ -46,12 +140,28 @@ static void insert_con_into(Con *con, Con *target, position_t position) { con->parent = parent; + if (parent == lca) { + if (focus_before) { + /* Example layout: H [ A B* ], we move A up/down. 'target' will be H. */ + TAILQ_INSERT_BEFORE(target, con, focused); + } else { + /* Example layout: H [ A B* ], we move A up/down. 'target' will be H. */ + TAILQ_INSERT_AFTER(&(parent->focus_head), target, con, focused); + } + } else { + if (focus_before) { + /* Example layout: V [ H [ A B ] C* ], we move C up. 'target' will be A. */ + TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused); + } else { + /* Example layout: V [ H [ A* B ] C ], we move C up. 'target' will be A. */ + TAILQ_INSERT_TAIL(&(parent->focus_head), con, focused); + } + } + if (position == BEFORE) { TAILQ_INSERT_BEFORE(target, con, nodes); - TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused); } else if (position == AFTER) { TAILQ_INSERT_AFTER(&(parent->nodes_head), target, con, nodes); - TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused); } /* Pretend the con was just opened with regards to size percent values. From d66fa51f336378c55e266c8aa2ba5fbec64d6a73 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 22 Sep 2017 04:48:32 +0300 Subject: [PATCH 015/218] Don't call con_focus in tree_move Fixes: - Issue where moving an urgent (unfocused) window resets it's urgency hint. - Moving an unfocused container to a new parent should not move it to the top of the focus stack. --- src/move.c | 5 ----- testcases/t/113-urgent.t | 28 ++++++++++++++++++++++++++++ testcases/t/294-focus-order.t | 16 ++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/move.c b/src/move.c index 72cf9d61..a60a27ca 100644 --- a/src/move.c +++ b/src/move.c @@ -374,11 +374,6 @@ void tree_move(Con *con, int direction) { } end: - /* We need to call con_focus() to fix the focus stack "above" the container - * we just inserted the focused container into (otherwise, the parent - * container(s) would still point to the old container(s)). */ - con_focus(con); - /* force re-painting the indicators */ FREE(con->deco_render_params); diff --git a/testcases/t/113-urgent.t b/testcases/t/113-urgent.t index 1e2644ad..0de90193 100644 --- a/testcases/t/113-urgent.t +++ b/testcases/t/113-urgent.t @@ -332,6 +332,34 @@ for ($type = 1; $type <= 2; $type++) { ok(!$source_ws->{urgent}, 'Source workspace is no longer marked urgent'); is($target_ws->{urgent}, 1, 'Target workspace is now marked urgent'); +############################################################################## +# Test that moving an unfocused container doesn't reset its urgency hint. +############################################################################## + $tmp = fresh_workspace; + $win1 = open_window; + $win2 = open_window; + cmd 'split v'; + $win3 = open_window; + set_urgency($win1, 1, $type); + sync_with_i3; + + my $win1_info; + + @content = @{get_ws_content($tmp)}; + $win1_info = first { $_->{window} == $win1->id } @content; + ok($win1_info->{urgent}, 'win1 window is marked urgent'); + + cmd '[id="' . $win1->id . '"] move right'; + cmd '[id="' . $win1->id . '"] move right'; + @content = @{get_ws_content($tmp)}; + $win1_info = first { $_->{window} == $win1->id } @content; + ok($win1_info->{urgent}, 'win1 window is still marked urgent after moving'); + + cmd '[id="' . $win1->id . '"] focus'; + @content = @{get_ws_content($tmp)}; + $win1_info = first { $_->{window} == $win1->id } @content; + ok(!$win1_info->{urgent}, 'win1 window is not marked urgent after focusing'); + ############################################################################## exit_gracefully($pid); diff --git a/testcases/t/294-focus-order.t b/testcases/t/294-focus-order.t index 41c0bf07..0f116241 100644 --- a/testcases/t/294-focus-order.t +++ b/testcases/t/294-focus-order.t @@ -106,4 +106,20 @@ $windows[0] = open_window; cmd 'move left'; confirm_focus('split-v + move'); +###################################################################### +# Test that moving an unfocused container maintains the correct focus +# order. +# Layout: H [ A V1 [ B C D ] ] +###################################################################### + +fresh_workspace; +$windows[3] = open_window; +$windows[2] = open_window; +cmd 'split v'; +$windows[1] = open_window; +$windows[0] = open_window; + +cmd '[id=' . $windows[3]->id . '] move right'; +confirm_focus('split-v + unfocused move'); + done_testing; From 3f4268561d4482e2c770d7c6b7b32d7ce8c82568 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Thu, 15 Mar 2018 21:33:45 +0200 Subject: [PATCH 016/218] Remove trailing whitespace from Perl scripts --- generate-command-parser.pl | 2 +- testcases/t/134-invalid-command.t | 2 +- testcases/t/232-cmd-move-criteria.t | 2 +- testcases/t/240-focus-on-window-activation.t | 2 +- testcases/t/243-move-to-mark.t | 4 ++-- testcases/t/245-move-position-mouse.t | 4 ++-- testcases/t/253-multiple-net-wm-state-atoms.t | 8 ++++---- testcases/t/529-net-wm-desktop.t | 8 ++++---- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/generate-command-parser.pl b/generate-command-parser.pl index 4c45b6ed..052e4c66 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -77,7 +77,7 @@ for my $line (@lines) { ($line =~ / ^\s* # skip leading whitespace ([a-z_]+ \s* = \s*|) # optional identifier - (.*?) -> \s* # token + (.*?) -> \s* # token (.*) # optional action /x); diff --git a/testcases/t/134-invalid-command.t b/testcases/t/134-invalid-command.t index 4557bbda..c10771c8 100644 --- a/testcases/t/134-invalid-command.t +++ b/testcases/t/134-invalid-command.t @@ -14,7 +14,7 @@ # # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf # (unless you are already familiar with Perl) -# +# # use i3test; diff --git a/testcases/t/232-cmd-move-criteria.t b/testcases/t/232-cmd-move-criteria.t index 54e7ce9c..312c9660 100644 --- a/testcases/t/232-cmd-move-criteria.t +++ b/testcases/t/232-cmd-move-criteria.t @@ -45,7 +45,7 @@ is($x->input_focus, $win3->{id}, 'it should not disturb focus'); ############################################################################### # test all window types -my %window_types = ( +my %window_types = ( 'normal' => '_NET_WM_WINDOW_TYPE_NORMAL', 'dialog' => '_NET_WM_WINDOW_TYPE_DIALOG', 'utility' => '_NET_WM_WINDOW_TYPE_UTILITY', diff --git a/testcases/t/240-focus-on-window-activation.t b/testcases/t/240-focus-on-window-activation.t index 57060753..efcd5ca4 100644 --- a/testcases/t/240-focus-on-window-activation.t +++ b/testcases/t/240-focus-on-window-activation.t @@ -22,7 +22,7 @@ use List::Util qw(first); my ($config, $pid, $first, $second, $ws1, $ws2); sub send_net_active_window { - my ($id) = @_; + my ($id) = @_; my $msg = pack "CCSLLLLLLL", X11::XCB::CLIENT_MESSAGE, # response_type diff --git a/testcases/t/243-move-to-mark.t b/testcases/t/243-move-to-mark.t index abc2b4c6..25d13333 100644 --- a/testcases/t/243-move-to-mark.t +++ b/testcases/t/243-move-to-mark.t @@ -31,7 +31,7 @@ my $_NET_WM_STATE_ADD = 1; my $_NET_WM_STATE_TOGGLE = 2; sub set_urgency { - my ($win, $urgent_flag) = @_; + my ($win, $urgent_flag) = @_; my $msg = pack "CCSLLLLLL", X11::XCB::CLIENT_MESSAGE, # response_type 32, # format @@ -119,7 +119,7 @@ is($nodes->[0]->{window}, $M->{id}, 'M is left of S'); is($nodes->[1]->{window}, $S->{id}, 'S is right of M'); ############################################################################### -# Given 'S' and 'M' on different workspaces and 'S' is urgent, when 'S' is +# Given 'S' and 'M' on different workspaces and 'S' is urgent, when 'S' is # moved to 'M', then the urgency flag is transferred to the target workspace. ############################################################################### diff --git a/testcases/t/245-move-position-mouse.t b/testcases/t/245-move-position-mouse.t index 12f890f2..01e2c30e 100644 --- a/testcases/t/245-move-position-mouse.t +++ b/testcases/t/245-move-position-mouse.t @@ -58,7 +58,7 @@ exit_gracefully($pid); ########################################################################## # Given a floating container and the cursor is in the left upper edge -# of the output, when moving the container to the mouse, then the +# of the output, when moving the container to the mouse, then the # container is moved but adjusted to stay in-bounds. ########################################################################## @@ -84,7 +84,7 @@ exit_gracefully($pid); ########################################################################## # Given a floating container and the cursor is in the left right lower -# edge of the output, when moving the container to the mouse, then the +# edge of the output, when moving the container to the mouse, then the # container is moved but adjusted to stay in-bounds. ########################################################################## diff --git a/testcases/t/253-multiple-net-wm-state-atoms.t b/testcases/t/253-multiple-net-wm-state-atoms.t index 3790f92d..392beae1 100644 --- a/testcases/t/253-multiple-net-wm-state-atoms.t +++ b/testcases/t/253-multiple-net-wm-state-atoms.t @@ -21,15 +21,15 @@ use X11::XCB qw(:all); sub get_wm_state { sync_with_i3; - my ($con) = @_; + my ($con) = @_; my $cookie = $x->get_property( - 0, + 0, $con->{id}, $x->atom(name => '_NET_WM_STATE')->id, GET_PROPERTY_TYPE_ANY, - 0, + 0, 4096 - ); + ); my $reply = $x->get_property_reply($cookie->{sequence}); my $len = $reply->{length}; diff --git a/testcases/t/529-net-wm-desktop.t b/testcases/t/529-net-wm-desktop.t index ae596203..f1da2980 100644 --- a/testcases/t/529-net-wm-desktop.t +++ b/testcases/t/529-net-wm-desktop.t @@ -31,15 +31,15 @@ use X11::XCB qw(:all); sub get_net_wm_desktop { sync_with_i3; - my ($con) = @_; + my ($con) = @_; my $cookie = $x->get_property( - 0, + 0, $con->{id}, $x->atom(name => '_NET_WM_DESKTOP')->id, $x->atom(name => 'CARDINAL')->id, - 0, + 0, 1 - ); + ); my $reply = $x->get_property_reply($cookie->{sequence}); return undef if $reply->{length} != 1; From 9d22d2efcea7ed95991ce4417d0feea2e2529af5 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 16 Mar 2018 03:08:47 +0200 Subject: [PATCH 017/218] Reduce repetition in get_binding() --- src/bindings.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index c145b956..9edc04dc 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -227,9 +227,9 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas /* For keyboard bindings where a symbol was specified by the user, we * need to look in the array of translated keycodes for the event’s * keycode */ + bool found_keycode = false; if (input_type == B_KEYBOARD && bind->symbol != NULL) { xcb_keycode_t input_keycode = (xcb_keycode_t)input_code; - bool found_keycode = false; struct Binding_Keycode *binding_keycode; TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) { const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF); @@ -241,16 +241,12 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas break; } } - if (!found_keycode) { - continue; - } } else { /* This case is easier: The user specified a keycode */ if (bind->keycode != input_code) { continue; } - bool found_keycode = false; struct Binding_Keycode *binding_keycode; TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) { const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF); @@ -262,9 +258,9 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas break; } } - if (!found_keycode) { - continue; - } + } + if (!found_keycode) { + continue; } /* If this binding is a release binding, it matches the key which the From b266574c3027efc39656a3f88c1f5ae4d534874c Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 16 Mar 2018 22:53:39 +0100 Subject: [PATCH 018/218] Add alacritty to 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 f1eb256e..6638098c 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 guake tilda; 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 alacritty; 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 894af912..fc956eef 100644 --- a/man/i3-sensible-terminal.man +++ b/man/i3-sensible-terminal.man @@ -47,6 +47,7 @@ It tries to start one of the following (in that order): * kitty * guake * tilda +* alacritty 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 9cd4b5323180d97ca68e5d7ff7772ccf2789e334 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Sun, 24 Jul 2016 20:43:09 -0400 Subject: [PATCH 019/218] testcases: remove assumption from state atoms test Remove the assumption that only two atoms can possibly be set in t/253-multiple-net-wm-state-atoms.t so that the tests will pass when more atoms are supported that may be set during this test. --- testcases/t/253-multiple-net-wm-state-atoms.t | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/testcases/t/253-multiple-net-wm-state-atoms.t b/testcases/t/253-multiple-net-wm-state-atoms.t index 392beae1..3a3e7c6e 100644 --- a/testcases/t/253-multiple-net-wm-state-atoms.t +++ b/testcases/t/253-multiple-net-wm-state-atoms.t @@ -36,7 +36,7 @@ sub get_wm_state { return undef if $len == 0; my @atoms = unpack("L$len", $reply->{value}); - return \@atoms; + return @atoms; } my $wm_state_sticky = $x->atom(name => '_NET_WM_STATE_STICKY')->id; @@ -51,18 +51,24 @@ my $wm_state_fullscreen = $x->atom(name => '_NET_WM_STATE_FULLSCREEN')->id; fresh_workspace; my $window = open_window; cmd 'sticky enable'; -is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set'); +my @state = get_wm_state($window); +ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, 'sanity check: _NET_WM_STATE_STICKY is set'); cmd 'fullscreen enable'; -is_deeply(get_wm_state($window), [ $wm_state_sticky, $wm_state_fullscreen ], - 'both _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_STICKY are set'); +@state = get_wm_state($window); +ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set'); +ok((scalar grep { $_ == $wm_state_fullscreen } @state) > 0, '_NET_WM_STATE_FULLSCREEN is set'); cmd 'sticky disable'; -is_deeply(get_wm_state($window), [ $wm_state_fullscreen ], 'only _NET_WM_STATE_FULLSCREEN is set'); +@state = get_wm_state($window); +ok((scalar grep { $_ == $wm_state_sticky } @state) == 0, '_NET_WM_STATE_STICKY is not set'); +ok((scalar grep { $_ == $wm_state_fullscreen } @state) > 0, '_NET_WM_STATE_FULLSCREEN is set'); cmd 'sticky enable'; cmd 'fullscreen disable'; -is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICKY is set'); +@state = get_wm_state($window); +ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set'); +ok((scalar grep { $_ == $wm_state_fullscreen } @state) == 0, '_NET_WM_STATE_FULLSCREEN is not set'); ############################################################################### # _NET_WM_STATE is removed when the window is withdrawn. @@ -71,7 +77,8 @@ is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICK fresh_workspace; $window = open_window; cmd 'sticky enable'; -is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set'); +@state = get_wm_state($window); +ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set'); $window->unmap; wait_for_unmap($window); From 124e64767ea7ce8fc46d53b9eb3ae7d7111dde30 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 18 Mar 2018 03:38:06 +0200 Subject: [PATCH 020/218] i3-msg: only print input + errorposition if they exist Before: $ i3-msg floating disable, move window to position 100 px 100 px ERROR: Your command: (null) ERROR: (null) ERROR: Cannot change position of a window/container because it is not floating. [{"success":true},{"success":false,"error":"Cannot change position of a window/container because it is not floating."}] After: $ i3-msg floating disable, move window to position 100 px 100 px ERROR: Cannot change position of a window/container because it is not floating. [{"success":true},{"success":false,"error":"Cannot change position of a window/container because it is not floating."}] --- i3-msg/main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index 91a714e5..96edb2c3 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -95,8 +95,10 @@ static int reply_start_map_cb(void *params) { static int reply_end_map_cb(void *params) { if (!last_reply.success) { - fprintf(stderr, "ERROR: Your command: %s\n", last_reply.input); - fprintf(stderr, "ERROR: %s\n", last_reply.errorposition); + if (last_reply.input) { + fprintf(stderr, "ERROR: Your command: %s\n", last_reply.input); + fprintf(stderr, "ERROR: %s\n", last_reply.errorposition); + } fprintf(stderr, "ERROR: %s\n", last_reply.error); } return 1; From 1fe4e635b583eb30ea50ba1ab2be862e9e84ac77 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 19 Mar 2018 02:02:59 +0200 Subject: [PATCH 021/218] Fix memleak: FREE(assign->dest.output) --- include/data.h | 2 +- src/config.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/data.h b/include/data.h index 69a46e46..32fb098f 100644 --- a/include/data.h +++ b/include/data.h @@ -573,7 +573,7 @@ struct Assignment { /** the criteria to check if a window matches */ Match match; - /** destination workspace/command, depending on the type */ + /** destination workspace/command/output, depending on the type */ union { char *command; char *workspace; diff --git a/src/config.c b/src/config.c index 24c7b541..2d5d78af 100644 --- a/src/config.c +++ b/src/config.c @@ -103,6 +103,8 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, FREE(assign->dest.workspace); else if (assign->type == A_COMMAND) FREE(assign->dest.command); + else if (assign->type == A_TO_OUTPUT) + FREE(assign->dest.output); match_free(&(assign->match)); TAILQ_REMOVE(&assignments, assign, assignments); FREE(assign); From 98df2e21fa6eadae10a7919077f76c28dec7e3e1 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 19 Mar 2018 03:00:35 +0200 Subject: [PATCH 022/218] Free ran_assignments When we run 'reload' all the assignments are freed: https://github.com/i3/i3/blob/e3e09119bf994ea3f5222441832952a8dd352941/src/config.c#L99-L109 Assignments are saved to each window after they are executed: https://github.com/i3/i3/blob/e3e09119bf994ea3f5222441832952a8dd352941/src/assignments.c#L41-L46 This means that the pointers stored in window->ran_assignments are invalid (shouldn't be dangerous currently but could lead to a segfault if the code is modified) after a 'reload'. --- src/config.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/config.c b/src/config.c index 24c7b541..3740c2b5 100644 --- a/src/config.c +++ b/src/config.c @@ -160,10 +160,16 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, FREE(barconfig); } - /* Invalidate pixmap caches in case font or colors changed */ Con *con; - TAILQ_FOREACH(con, &all_cons, all_cons) - FREE(con->deco_render_params); + TAILQ_FOREACH(con, &all_cons, all_cons) { + /* Assignments changed, previously ran assignments are invalid. */ + if (con->window) { + con->window->nr_assignments = 0; + FREE(con->window->ran_assignments); + } + /* Invalidate pixmap caches in case font or colors changed. */ + FREE(con->deco_render_params); + } /* Get rid of the current font */ free_font(); From 6306acdb650c857089aef51dab43746d36b5462a Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 19 Mar 2018 03:17:32 +0200 Subject: [PATCH 023/218] run_assignments: check for A_COMMAND early --- src/assignments.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/assignments.c b/src/assignments.c index a0f5d5a7..abacc0a3 100644 --- a/src/assignments.c +++ b/src/assignments.c @@ -22,7 +22,7 @@ void run_assignments(i3Window *window) { /* Check if any assignments match */ Assignment *current; TAILQ_FOREACH(current, &assignments, assignments) { - if (!match_matches_window(&(current->match), window)) + if (current->type != A_COMMAND || !match_matches_window(&(current->match), window)) continue; bool skip = false; @@ -45,19 +45,16 @@ void run_assignments(i3Window *window) { window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment *) * window->nr_assignments); window->ran_assignments[window->nr_assignments - 1] = current; - DLOG("matching assignment, would do:\n"); - if (current->type == A_COMMAND) { - DLOG("execute command %s\n", current->dest.command); - char *full_command; - sasprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command); - CommandResult *result = parse_command(full_command, NULL); - free(full_command); + DLOG("matching assignment, execute command %s\n", current->dest.command); + char *full_command; + sasprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command); + CommandResult *result = parse_command(full_command, NULL); + free(full_command); - if (result->needs_tree_render) - needs_tree_render = true; + if (result->needs_tree_render) + needs_tree_render = true; - command_result_free(result); - } + command_result_free(result); } /* If any of the commands required re-rendering, we will do that now. */ From b4679378089d0cdbbad23d0e87fbba5f906ae407 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 19 Mar 2018 18:30:22 +0200 Subject: [PATCH 024/218] Fix userguide link [[move_to_outputs]] doesn't work currently: https://i3wm.org/docs/userguide.html#move_to_outputs This does: https://i3wm.org/docs/userguide.html#_moving_containers_workspaces_to_randr_outputs This combines a 'BlockId Element' with an 'anchor'. Both should work now. --- docs/userguide | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index ccedc577..9ba181e0 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2258,8 +2258,7 @@ See <> for how to move a container/workspace to a different RandR output. [[move_to_outputs]] -[[_moving_containers_workspaces_to_randr_outputs]] -=== Moving containers/workspaces to RandR outputs +=== [[_moving_containers_workspaces_to_randr_outputs]]Moving containers/workspaces to RandR outputs To move a container to another RandR output (addressed by names like +LVDS1+ or +VGA1+) or to a RandR output identified by a specific direction (like +left+, From 130b3ce3a9cf249e96719f8f90f2ac462112cca7 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 17 Mar 2018 23:28:23 +0200 Subject: [PATCH 025/218] Check for B_UPON_KEYRELEASE_IGNORE_MODS with bindsyms From 548d74015c50d7fae14bfb8bb1989acde5fc22ae: > 1. press $mod, press x, release x, release $mod > 2. press $mod, press x, release $mod, release x case (2.) didn't work, now it should be fixed. --- src/bindings.c | 3 ++- testcases/t/258-keypress-release.t | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/bindings.c b/src/bindings.c index 9edc04dc..823730ff 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -236,7 +236,8 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas const bool mods_match = (modifiers_mask == modifiers_state); DLOG("binding_keycode->modifiers = %d, modifiers_mask = %d, modifiers_state = %d, mods_match = %s\n", binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no")); - if (binding_keycode->keycode == input_keycode && mods_match) { + if (binding_keycode->keycode == input_keycode && + (mods_match || (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS && is_release))) { found_keycode = true; break; } diff --git a/testcases/t/258-keypress-release.t b/testcases/t/258-keypress-release.t index 8bca0d86..b92f723f 100644 --- a/testcases/t/258-keypress-release.t +++ b/testcases/t/258-keypress-release.t @@ -29,6 +29,8 @@ bindsym --release Control+Print nop Control+Print # see issue #2442 bindsym Mod1+b nop Mod1+b bindsym --release Mod1+Shift+b nop Mod1+Shift+b release + +bindsym --release Shift+x nop Shift+x EOT use i3test::XTEST; use ExtUtils::PkgConfig; @@ -85,6 +87,29 @@ is(listen_for_binding( 'Mod1+Shift+b release', 'triggered the "Mod1+Shift+b" release keybinding'); +is(listen_for_binding( + sub { + xtest_key_press(50); # Shift + xtest_key_press(53); # x + xtest_key_release(53); # x + xtest_key_release(50); # Shift + xtest_sync_with_i3; + }, + ), + 'Shift+x', + 'triggered the "Shift+x" keybinding by releasing x first'); + +is(listen_for_binding( + sub { + xtest_key_press(50); # Shift + xtest_key_press(53); # x + xtest_key_release(50); # Shift + xtest_key_release(53); # x + xtest_sync_with_i3; + }, + ), + 'Shift+x', + 'triggered the "Shift+x" keybinding by releasing Shift first'); } done_testing; From ff579ef22f7ba2fa4762564f84bfa062e79cfaa2 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 16 Mar 2018 03:38:13 +0200 Subject: [PATCH 026/218] Correctly handle bindings for the same mod key with and without --release Before this commit, get_binding() exited on the first match without marking the rest --release bindings with B_UPON_KEYRELEASE_IGNORE_MODS. Similarly, once it found a --release binding during a KeyPress event it would stop searching for a matching key press binding. Example config, placing the --release line first will trigger the second problem: # i3 config file (v4) bindsym Super_L exec notify-send "press" # or # bindcode 133 exec notify-send "press" bindsym --release Super_L exec notify-send "release" # or # bindcode --release 133 exec notify-send "release" Fixes #2733 --- src/bindings.c | 28 ++++++++++++++++------------ testcases/t/258-keypress-release.t | 24 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 823730ff..38002396 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -198,6 +198,7 @@ void regrab_all_buttons(xcb_connection_t *conn) { */ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_release, uint16_t input_code, input_type_t input_type) { Binding *bind; + Binding *result = NULL; if (!is_release) { /* On a press event, we first reset all B_UPON_KEYRELEASE_IGNORE_MODS @@ -271,23 +272,26 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas if (bind->release == B_UPON_KEYRELEASE && !is_release) { bind->release = B_UPON_KEYRELEASE_IGNORE_MODS; DLOG("marked bind %p as B_UPON_KEYRELEASE_IGNORE_MODS\n", bind); - /* The correct binding has been found, so abort the search, but - * also don’t return this binding, since it should not be executed - * yet (only when the keys are released). */ - bind = TAILQ_END(bindings); - break; - } - - /* Check if the binding is for a press or a release event */ - if ((bind->release == B_UPON_KEYPRESS && is_release) || - (bind->release >= B_UPON_KEYRELEASE && !is_release)) { + if (result) { + break; + } continue; } - break; + /* Check if the binding is for a press or a release event */ + if ((bind->release == B_UPON_KEYPRESS && is_release)) { + continue; + } + + if (is_release) { + return bind; + } else if (!result) { + /* Continue looping to mark needed B_UPON_KEYRELEASE_IGNORE_MODS. */ + result = bind; + } } - return (bind == TAILQ_END(bindings) ? NULL : bind); + return result; } /* diff --git a/testcases/t/258-keypress-release.t b/testcases/t/258-keypress-release.t index b92f723f..766a8a1b 100644 --- a/testcases/t/258-keypress-release.t +++ b/testcases/t/258-keypress-release.t @@ -31,6 +31,11 @@ bindsym Mod1+b nop Mod1+b bindsym --release Mod1+Shift+b nop Mod1+Shift+b release bindsym --release Shift+x nop Shift+x + +# see issue #2733 +# 133 == Mod4 +bindcode 133 nop 133 +bindcode --release 133 nop 133 release EOT use i3test::XTEST; use ExtUtils::PkgConfig; @@ -110,6 +115,25 @@ is(listen_for_binding( ), 'Shift+x', 'triggered the "Shift+x" keybinding by releasing Shift first'); + +is(listen_for_binding( + sub { + xtest_key_press(133); + xtest_sync_with_i3; + }, + ), + '133', + 'triggered the 133 keycode press binding'); + +is(listen_for_binding( + sub { + xtest_key_release(133); + xtest_sync_with_i3; + }, + ), + '133 release', + 'triggered the 133 keycode release binding'); + } done_testing; From dc0337d2e543c57f3b40bed5bb3f3a324718ab72 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 20 Mar 2018 01:44:50 +0200 Subject: [PATCH 027/218] Reset B_UPON_KEYRELEASE_IGNORE_MODS bindings when switching modes With example config: mode "a_mode" { bindcode 27 --release mode "default" } bindsym $mod+r mode "a_mode" The first time $mod+r is pressed "a_mode" is activated like normal. When r (bindcode 27) is pressed to exit the mode: - On the KeyPress event the corresponding bind->release is correctly marked as B_UPON_KEYRELEASE_IGNORE_MODS. - On the KeyRelease event the command 'mode "default"' is executed but bind->release is still B_UPON_KEYRELEASE_IGNORE_MODS since they are only reset on KeyPress events. The second time $mod+r is pressed and "a_mode" is activated and when the r key is released the 'mode "default"' is executed even though the mods are not matching since bind->release == B_UPON_KEYRELEASE_IGNORE_MODS. This still doesn't catch 2 cases: 1. When the order is: press $mod -> press r -> release $mod -> release r. Since 'r' is released without any modifiers the binding matches. 2. With: mode "resize" { bindsym --release r mode "default" } bindsym r mode "resize" This is arguably correct: on the KeyPress event we switch to the mode and on the KeyRelease we switch back. --- src/bindings.c | 8 ++++++++ testcases/t/258-keypress-release.t | 31 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/bindings.c b/src/bindings.c index 38002396..228f7a9d 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -649,6 +649,14 @@ void switch_mode(const char *new_mode) { translate_keysyms(); grab_all_keys(conn); + /* Reset all B_UPON_KEYRELEASE_IGNORE_MODS bindings to avoid possibly + * activating one of them. */ + Binding *bind; + TAILQ_FOREACH(bind, bindings, bindings) { + if (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS) + bind->release = B_UPON_KEYRELEASE; + } + char *event_msg; sasprintf(&event_msg, "{\"change\":\"%s\", \"pango_markup\":%s}", mode->name, (mode->pango_markup ? "true" : "false")); diff --git a/testcases/t/258-keypress-release.t b/testcases/t/258-keypress-release.t index 766a8a1b..614164ab 100644 --- a/testcases/t/258-keypress-release.t +++ b/testcases/t/258-keypress-release.t @@ -36,6 +36,13 @@ bindsym --release Shift+x nop Shift+x # 133 == Mod4 bindcode 133 nop 133 bindcode --release 133 nop 133 release + +mode "a_mode" { + # 27 == r + bindcode 27 --release mode "default" +} +bindsym Mod1+r mode "a_mode" +bindcode 27 nop do not receive EOT use i3test::XTEST; use ExtUtils::PkgConfig; @@ -134,6 +141,30 @@ is(listen_for_binding( '133 release', 'triggered the 133 keycode release binding'); +for my $i (1 .. 2) { + is(listen_for_binding( + sub { + xtest_key_press(64); # Alt_l + xtest_key_press(27); # r + xtest_key_release(27); # r + xtest_key_release(64); # Alt_l + xtest_sync_with_i3; + }, + ), + 'mode "a_mode"', + "switched to mode \"a_mode\" $i/2"); + + is(listen_for_binding( + sub { + xtest_key_press(27); # r + xtest_key_release(27); # r + xtest_sync_with_i3; + }, + ), + 'mode "default"', + "switched back to default $i/2"); +} + } done_testing; From 174dc389ffe840e89199b07e6d08317ebbc9f725 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 18 Mar 2018 02:08:16 +0200 Subject: [PATCH 028/218] Remove 'method' from cmd_move_window_to_position For command: move window to [absolute] position X px Y px if the optional keyword 'absolute' is provided the end result is the same even though it is implemented differently. Only difference is that with absolute the floating window can move completely outside of any output. This commit removes the 'method' argument and only keeps the sane implementation. --- docs/userguide | 11 +++++++---- include/commands.h | 2 +- parser-specs/commands.spec | 2 +- src/commands.c | 23 ++++++----------------- 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/docs/userguide b/docs/userguide index ccedc577..7240409c 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2030,10 +2030,13 @@ Use the +move+ command to move a container. # defaults to 10 pixels. move [ px] -# Moves the container either to a specific location -# or to the center of the screen. If 'absolute' is -# used, it is moved to the center of all outputs. -move [absolute] position [px] [px] +# Moves the container to the specified pos_x and pos_y +# coordinates on the screen. +move position [px] [px] + +# Moves the container to the center of the screen. +# If 'absolute' is used, it is moved to the center of +# all outputs. move [absolute] position center # Moves the container to the current position of the diff --git a/include/commands.h b/include/commands.h index 1057f021..aaa0875f 100644 --- a/include/commands.h +++ b/include/commands.h @@ -264,7 +264,7 @@ void cmd_focus_output(I3_CMD, const char *name); * Implementation of 'move [window|container] [to] [absolute] position [px] [px] * */ -void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y); +void cmd_move_window_to_position(I3_CMD, long x, long y); /** * Implementation of 'move [window|container] [to] [absolute] position center diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 0289fa1a..4048768e 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -396,7 +396,7 @@ state MOVE_TO_POSITION_X: state MOVE_TO_POSITION_Y: 'px', end - -> call cmd_move_window_to_position($method, &coord_x, &coord_y) + -> call cmd_move_window_to_position(&coord_x, &coord_y) # mode state MODE: diff --git a/src/commands.c b/src/commands.c index 899bbb90..b62daad9 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1694,7 +1694,7 @@ void cmd_focus_output(I3_CMD, const char *name) { * Implementation of 'move [window|container] [to] [absolute] position [px] [px] * */ -void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) { +void cmd_move_window_to_position(I3_CMD, long x, long y) { bool has_error = false; owindow *current; @@ -1712,24 +1712,13 @@ void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) { continue; } - if (strcmp(method, "absolute") == 0) { - current->con->parent->rect.x = x; - current->con->parent->rect.y = y; + Rect newrect = current->con->parent->rect; - DLOG("moving to absolute position %ld %ld\n", x, y); - floating_maybe_reassign_ws(current->con->parent); - cmd_output->needs_tree_render = true; - } + DLOG("moving to position %ld %ld\n", x, y); + newrect.x = x; + newrect.y = y; - if (strcmp(method, "position") == 0) { - Rect newrect = current->con->parent->rect; - - DLOG("moving to position %ld %ld\n", x, y); - newrect.x = x; - newrect.y = y; - - floating_reposition(current->con->parent, newrect); - } + floating_reposition(current->con->parent, newrect); } // XXX: default reply for now, make this a better reply From 5e8a3f3f0cd2a986b721e1480831ca5ff80713c5 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 18 Mar 2018 02:41:12 +0200 Subject: [PATCH 029/218] cmd_move_window_to_position: improve error message --- include/floating.h | 2 +- src/commands.c | 6 ++++-- src/floating.c | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/floating.h b/include/floating.h index babfafc9..4382437b 100644 --- a/include/floating.h +++ b/include/floating.h @@ -143,7 +143,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, * outputs. * */ -void floating_reposition(Con *con, Rect newrect); +bool floating_reposition(Con *con, Rect newrect); /** * Sets size of the CT_FLOATING_CON to specified dimensions. Might limit the diff --git a/src/commands.c b/src/commands.c index b62daad9..98625a91 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1718,10 +1718,12 @@ void cmd_move_window_to_position(I3_CMD, long x, long y) { newrect.x = x; newrect.y = y; - floating_reposition(current->con->parent, newrect); + if (!floating_reposition(current->con->parent, newrect)) { + yerror("Cannot move window/container out of bounds."); + has_error = true; + } } - // XXX: default reply for now, make this a better reply if (!has_error) ysuccess(true); } diff --git a/src/floating.c b/src/floating.c index e958153d..2130d673 100644 --- a/src/floating.c +++ b/src/floating.c @@ -864,12 +864,12 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ * outputs. * */ -void floating_reposition(Con *con, Rect newrect) { +bool floating_reposition(Con *con, Rect newrect) { /* Sanity check: Are the new coordinates on any output? If not, we * ignore that request. */ if (!contained_by_output(newrect)) { ELOG("No output found at destination coordinates. Not repositioning.\n"); - return; + return false; } con->rect = newrect; @@ -881,6 +881,7 @@ void floating_reposition(Con *con, Rect newrect) { con->scratchpad_state = SCRATCHPAD_CHANGED; tree_render(); + return true; } /* From c42de09b1b0b0bcf96342d662f4bef3011b8ce5d Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Sun, 24 Jul 2016 20:43:56 -0400 Subject: [PATCH 030/218] Support _NET_WM_STATE_FOCUSED _NET_WM_STATE_FOCUSED is set on _NET_WM_STATE to indicate that the window is focused. It must be set when the window is newly focused and removed once the window no longer has focus. > _NET_WM_STATE_FOCUSED indicates whether the window's decorations are > drawn in an active state. Clients MUST regard it as a read-only hint. > It cannot be set at map time or changed via a _NET_WM_STATE client > message. For example, this is used by GTK applications to show the decoration in an active or inactive state. This change can be tested by opening a GTK application (like evince), focusing the window and unfocusing the window, and observing a change in the window decorations. Fixes #2273 --- include/atoms_NET_SUPPORTED.xmacro | 1 + include/ewmh.h | 6 +++++ src/ewmh.c | 14 ++++++++++ src/x.c | 24 ++++++++++++++--- testcases/lib/i3test.pm.in | 35 ++++++++++++++++++++++++ testcases/t/158-wm_take_focus.t | 3 +++ testcases/t/295-net-wm-state-focused.t | 37 ++++++++++++++++++++++++++ 7 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 testcases/t/295-net-wm-state-focused.t diff --git a/include/atoms_NET_SUPPORTED.xmacro b/include/atoms_NET_SUPPORTED.xmacro index a7b9676d..a81948a9 100644 --- a/include/atoms_NET_SUPPORTED.xmacro +++ b/include/atoms_NET_SUPPORTED.xmacro @@ -8,6 +8,7 @@ xmacro(_NET_WM_STATE_FULLSCREEN) xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) xmacro(_NET_WM_STATE_MODAL) xmacro(_NET_WM_STATE_HIDDEN) +xmacro(_NET_WM_STATE_FOCUSED) xmacro(_NET_WM_STATE) xmacro(_NET_WM_WINDOW_TYPE) xmacro(_NET_WM_WINDOW_TYPE_NORMAL) diff --git a/include/ewmh.h b/include/ewmh.h index 5844faa6..01ae67f9 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -83,6 +83,12 @@ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows); */ void ewmh_update_sticky(xcb_window_t window, bool sticky); +/** + * Set or remove _NEW_WM_STATE_FOCUSED on the window. + * + */ +void ewmh_update_focused(xcb_window_t window, bool is_focused); + /** * Set up the EWMH hints on the root window. * diff --git a/src/ewmh.c b/src/ewmh.c index f8422bda..e5dcafcb 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -284,6 +284,20 @@ void ewmh_update_sticky(xcb_window_t window, bool sticky) { } } +/* + * Set or remove _NEW_WM_STATE_FOCUSED on the window. + * + */ +void ewmh_update_focused(xcb_window_t window, bool is_focused) { + if (is_focused) { + DLOG("Setting _NET_WM_STATE_FOCUSED for window = %d.\n", window); + xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED); + } else { + DLOG("Removing _NET_WM_STATE_FOCUSED for window = %d.\n", window); + xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED); + } +} + /* * Set up the EWMH hints on the root window. * diff --git a/src/x.c b/src/x.c index 7829079b..629520d4 100644 --- a/src/x.c +++ b/src/x.c @@ -99,6 +99,23 @@ static con_state *state_for_frame(xcb_window_t window) { return NULL; } +/* + * Changes the atoms on the root window and the windows themselves to properly + * reflect the current focus for ewmh compliance. + * + */ +static void change_ewmh_focus(xcb_window_t new_focus, xcb_window_t old_focus) { + ewmh_update_active_window(new_focus); + + if (new_focus != XCB_WINDOW_NONE) { + ewmh_update_focused(new_focus, true); + } + + if (old_focus != XCB_WINDOW_NONE) { + ewmh_update_focused(old_focus, false); + } +} + /* * Initializes the X11 part for the given container. Called exactly once for * every container from con_new(). @@ -1120,7 +1137,7 @@ void x_push_changes(Con *con) { to_focus, focused, focused->name); send_take_focus(to_focus, last_timestamp); - ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE)); + change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused); if (to_focus != last_focused && is_con_attached(focused)) ipc_send_window_event("focus", focused); @@ -1139,7 +1156,7 @@ void x_push_changes(Con *con) { xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values); } - ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE)); + change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused); if (to_focus != XCB_NONE && to_focus != last_focused && focused->window != NULL && is_con_attached(focused)) ipc_send_window_event("focus", focused); @@ -1154,7 +1171,8 @@ void x_push_changes(Con *con) { * root window in order to avoid an X11 fallback mechanism causing a ghosting effect (see #1378). */ DLOG("Still no window focused, better set focus to the EWMH support window (%d)\n", ewmh_window); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, ewmh_window, last_timestamp); - ewmh_update_active_window(XCB_WINDOW_NONE); + change_ewmh_focus(XCB_WINDOW_NONE, last_focused); + focused_id = ewmh_window; } diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index e754c0c1..68ac1ee5 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -51,6 +51,7 @@ our @EXPORT = qw( kill_all_windows events_for listen_for_binding + is_net_wm_state_focused ); =head1 NAME @@ -1026,6 +1027,40 @@ sub listen_for_binding { return $command; } +=head2 is_net_wm_state_focused + +Returns true if the given window has the _NET_WM_STATE_FOCUSED atom. + + ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set'); + +=cut +sub is_net_wm_state_focused { + my ($window) = @_; + + sync_with_i3; + my $atom = $x->atom(name => '_NET_WM_STATE_FOCUSED'); + my $cookie = $x->get_property( + 0, + $window->{id}, + $x->atom(name => '_NET_WM_STATE')->id, + GET_PROPERTY_TYPE_ANY, + 0, + 4096 + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + my $len = $reply->{length}; + return 0 if $len == 0; + + my @atoms = unpack("L$len", $reply->{value}); + for (my $i = 0; $i < $len; $i++) { + return 1 if $atoms[$i] == $atom->id; + } + + return 0; +} + + =head1 AUTHOR Michael Stapelberg diff --git a/testcases/t/158-wm_take_focus.t b/testcases/t/158-wm_take_focus.t index 41d400d5..c4b42964 100644 --- a/testcases/t/158-wm_take_focus.t +++ b/testcases/t/158-wm_take_focus.t @@ -55,6 +55,7 @@ subtest 'Window without WM_TAKE_FOCUS', sub { my $window = open_window; ok(!recv_take_focus($window), 'did not receive ClientMessage'); + ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set'); my ($nodes) = get_ws_content($ws); my $con = shift @$nodes; @@ -91,6 +92,7 @@ subtest 'Window with WM_TAKE_FOCUS and without InputHint', sub { $window->map; ok(!recv_take_focus($window), 'did not receive ClientMessage'); + ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set'); my ($nodes) = get_ws_content($ws); my $con = shift @$nodes; @@ -112,6 +114,7 @@ subtest 'Window with WM_TAKE_FOCUS and unspecified InputHint', sub { my $window = open_window({ protocols => [ $take_focus ] }); ok(!recv_take_focus($window), 'did not receive ClientMessage'); + ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set'); my ($nodes) = get_ws_content($ws); my $con = shift @$nodes; diff --git a/testcases/t/295-net-wm-state-focused.t b/testcases/t/295-net-wm-state-focused.t new file mode 100644 index 00000000..1881154e --- /dev/null +++ b/testcases/t/295-net-wm-state-focused.t @@ -0,0 +1,37 @@ +#!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 for setting and removing the _NET_WM_STATE_FOCUSED atom properly. +# Ticket: #2273 +use i3test; +use X11::XCB qw(:all); + +my ($windowA, $windowB); + +fresh_workspace; +$windowA = open_window; + +ok(is_net_wm_state_focused($windowA), 'a newly opened window that is focused should have _NET_WM_STATE_FOCUSED set'); + +$windowB = open_window; + +ok(!is_net_wm_state_focused($windowA), 'when a another window is focused, the old window should not have _NET_WM_STATE_FOCUSED set'); + +fresh_workspace; + +ok(!is_net_wm_state_focused($windowB), 'when focus moves to the ewmh support window, neither window should have _NET_WM_STATE_FOCUSED set'); + +done_testing; From 3ccaf11eabffe4b5873cfc7987315f590c5a9300 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 23 Mar 2018 15:46:40 +0200 Subject: [PATCH 031/218] Improve directional moving of fullscreen containers Fixes #2993. --- src/move.c | 8 +++++++- testcases/t/516-move.t | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/move.c b/src/move.c index a60a27ca..e8620c24 100644 --- a/src/move.c +++ b/src/move.c @@ -256,7 +256,13 @@ void tree_move(Con *con, int direction) { return; } - if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) { + if (con->fullscreen_mode == CF_GLOBAL) { + DLOG("Not moving fullscreen global container\n"); + return; + } + + if ((con->fullscreen_mode == CF_OUTPUT) || + (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1)) { /* This is the only con on this workspace */ move_to_output_directed(con, direction); return; diff --git a/testcases/t/516-move.t b/testcases/t/516-move.t index 5bcdb09a..3db8c4f0 100644 --- a/testcases/t/516-move.t +++ b/testcases/t/516-move.t @@ -97,4 +97,32 @@ is(scalar @{get_ws_content('left-top')}, 1, 'moved some window to left-bottom wo $compare_window = shift @{get_ws_content('left-top')}; is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace'); +##################################################################### +# Moving a fullscreen container should change its output. +##################################################################### + +kill_all_windows; + +cmd 'workspace left-top'; +open_window; +my $fs_window = open_window; +open_window; + +cmd '[id=' . $fs_window->id . '] fullscreen enable, move right'; +is(scalar @{get_ws_content('right-top')}, 1, 'moved fullscreen window to right-top workspace'); + +##################################################################### +# Moving a global fullscreen container should not change its output. +##################################################################### + +kill_all_windows; + +cmd 'workspace left-top'; +open_window; +open_window; +open_window; + +cmd 'fullscreen global, move right, fullscreen disable'; +is(scalar @{get_ws_content('right-top')}, 0, 'global fullscreen window didn\'t change workspace with move'); + done_testing; From e09861f73fa21f95b58cd0ecec4647d585f91326 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 17 Mar 2018 17:42:49 +0200 Subject: [PATCH 032/218] contained_by_output: return output and rename to output_containing_rect --- include/randr.h | 9 ++++----- src/floating.c | 2 +- src/randr.c | 26 ++++++++++++++++++-------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/include/randr.h b/include/randr.h index bfbfd5a9..d365a9e4 100644 --- a/include/randr.h +++ b/include/randr.h @@ -95,15 +95,14 @@ Output *get_output_containing(unsigned int x, unsigned int y); */ Output *get_output_with_dimensions(Rect rect); -/* - * In contained_by_output, we check if any active output contains part of the container. +/** + * In output_containing_rect, we check if any active output contains part of the container. * We do this by checking if the output rect is intersected by the Rect. * This is the 2-dimensional counterpart of get_output_containing. - * Since we don't actually need the outputs intersected by the given Rect (There could - * be many), we just return true or false for convenience. + * Returns the output with the maximum intersecting area. * */ -bool contained_by_output(Rect rect); +Output *output_containing_rect(Rect rect); /** * Gets the output which is the next one in the given direction. diff --git a/src/floating.c b/src/floating.c index e958153d..29b9cab3 100644 --- a/src/floating.c +++ b/src/floating.c @@ -867,7 +867,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ void floating_reposition(Con *con, Rect newrect) { /* Sanity check: Are the new coordinates on any output? If not, we * ignore that request. */ - if (!contained_by_output(newrect)) { + if (!output_containing_rect(newrect)) { ELOG("No output found at destination coordinates. Not repositioning.\n"); return; } diff --git a/src/randr.c b/src/randr.c index 85add08f..efdd0352 100644 --- a/src/randr.c +++ b/src/randr.c @@ -136,27 +136,37 @@ Output *get_output_with_dimensions(Rect rect) { } /* - * In contained_by_output, we check if any active output contains part of the container. + * In output_containing_rect, we check if any active output contains part of the container. * We do this by checking if the output rect is intersected by the Rect. * This is the 2-dimensional counterpart of get_output_containing. - * Since we don't actually need the outputs intersected by the given Rect (There could - * be many), we just return true or false for convenience. + * Returns the output with the maximum intersecting area. * */ -bool contained_by_output(Rect rect) { +Output *output_containing_rect(Rect rect) { Output *output; int lx = rect.x, uy = rect.y; int rx = rect.x + rect.width, by = rect.y + rect.height; + long max_area = 0; + Output *result = NULL; TAILQ_FOREACH(output, &outputs, outputs) { if (!output->active) continue; + int lx_o = (int)output->rect.x, uy_o = (int)output->rect.y; + int rx_o = (int)(output->rect.x + output->rect.width), by_o = (int)(output->rect.y + output->rect.height); DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", rect.x, rect.y, output->rect.x, output->rect.y, output->rect.width, output->rect.height); - if (rx >= (int)output->rect.x && lx <= (int)(output->rect.x + output->rect.width) && - by >= (int)output->rect.y && uy <= (int)(output->rect.y + output->rect.height)) - return true; + int left = max(lx, lx_o); + int right = min(rx, rx_o); + int bottom = min(by, by_o); + int top = max(uy, uy_o); + if (left < right && bottom > top) { + long area = (right - left) * (bottom - top); + if (area > max_area) { + result = output; + } + } } - return false; + return result; } /* From 8a3ef3a81bd4946777c7e3585384283bf12d89be Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 17 Mar 2018 20:42:54 +0200 Subject: [PATCH 033/218] Introduce get_output_from_rect --- include/randr.h | 8 ++++++++ src/randr.c | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/randr.h b/include/randr.h index d365a9e4..39182c54 100644 --- a/include/randr.h +++ b/include/randr.h @@ -88,6 +88,14 @@ Output *get_output_by_name(const char *name, const bool require_active); */ Output *get_output_containing(unsigned int x, unsigned int y); +/** + * Returns the active output which contains the midpoint of the given rect. If + * such an output doesn't exist, returns the output which contains most of the + * rectangle or NULL if there is no output which intersects with it. + * + */ +Output *get_output_from_rect(Rect rect); + /** * Returns the active output which spans exactly the area specified by * rect or NULL if there is no output like this. diff --git a/src/randr.c b/src/randr.c index efdd0352..c43c6455 100644 --- a/src/randr.c +++ b/src/randr.c @@ -114,6 +114,20 @@ Output *get_output_containing(unsigned int x, unsigned int y) { return NULL; } +/* + * Returns the active output which contains the midpoint of the given rect. If + * such an output doesn't exist, returns the output which contains most of the + * rectangle or NULL if there is no output which intersects with it. + * + */ +Output *get_output_from_rect(Rect rect) { + unsigned int mid_x = rect.x + rect.width / 2; + unsigned int mid_y = rect.y + rect.height / 2; + Output *output = get_output_containing(mid_x, mid_y); + + return output ? output : output_containing_rect(rect); +} + /* * Returns the active output which spans exactly the area specified by * rect or NULL if there is no output like this. From 128122e7663a5a1f38bd8f921ecaef55ff2a4b13 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 17 Mar 2018 17:47:16 +0200 Subject: [PATCH 034/218] floating_enable: change reassign logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows the floating container's top left corner to be mapped outside any output as long as they are contained partially by one. This, for example, will allow: mpv --geometry +1+1 video.mp4 For windows mapped to (0, 0) see comment in floating.c:270-273: /* Some clients (like GIMP’s color picker window) get mapped * to (0, 0), so we push them to a reasonable position * (centered over their leader) */ The floating_reassign_ws call is removed since we try to place the new floating container in the current output: /* Sanity check: Are the coordinates on the appropriate output? If not, we * need to change them */ Fixes #1341 --- src/floating.c | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/src/floating.c b/src/floating.c index 29b9cab3..1c310abe 100644 --- a/src/floating.c +++ b/src/floating.c @@ -284,10 +284,7 @@ void floating_enable(Con *con, bool automatic) { /* Sanity check: Are the coordinates on the appropriate output? If not, we * need to change them */ - Output *current_output = get_output_containing(nc->rect.x + - (nc->rect.width / 2), - nc->rect.y + (nc->rect.height / 2)); - + Output *current_output = get_output_from_rect(nc->rect); Con *correct_output = con_get_output(ws); if (!current_output || current_output->con != correct_output) { DLOG("This floating window is on the wrong output, fixing coordinates (currently (%d, %d))\n", @@ -295,11 +292,13 @@ void floating_enable(Con *con, bool automatic) { /* If moving from one output to another, keep the relative position * consistent (e.g. a centered dialog will remain centered). */ - if (current_output) + if (current_output) { floating_fix_coordinates(nc, ¤t_output->con->rect, &correct_output->rect); - else { - nc->rect.x = correct_output->rect.x; - nc->rect.y = correct_output->rect.y; + /* Make sure that the result is in the correct output. */ + current_output = get_output_from_rect(nc->rect); + } + if (!current_output || current_output->con != correct_output) { + floating_center(nc, ws->rect); } } @@ -320,21 +319,6 @@ void floating_enable(Con *con, bool automatic) { if (set_focus) 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. */ - if (floating_maybe_reassign_ws(nc)) { - goto done; - } - - /* Sanitize coordinates: Check if they are on any output */ - if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) { - goto done; - } - - ELOG("No output found at destination coordinates, centering floating window on current ws\n"); - floating_center(nc, ws->rect); - -done: floating_set_hint_atom(nc, true); ipc_send_window_event("floating", con); } From 8e9b26fc9028d19b4756bd8e3ed3a3ecfcf5fd6f Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 17 Mar 2018 20:43:11 +0200 Subject: [PATCH 035/218] floating_maybe_reassign_ws: use get_output_from_rect This significantly reduces the number of ELOGs while dragging floating containers. The behaviour is improved since floating containers in the edge of the screen will still get reassigned to their closest workspace. For example, consider this setup: fake-outputs 500x500+0+0,500x500+500+0 Now, open a window in the right output and run: i3-msg floating enable, move position 0 px 450 px The window is on the bottom edge of the left workspace but if you run: i3-msg focus mode_toggle focus will go to the right workspace since floating_maybe_reassign_ws didn't change the assigned workspace of the floating container. --- src/floating.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/floating.c b/src/floating.c index 1c310abe..13b30f16 100644 --- a/src/floating.c +++ b/src/floating.c @@ -413,9 +413,7 @@ void floating_raise_con(Con *con) { * */ bool floating_maybe_reassign_ws(Con *con) { - Output *output = get_output_containing( - con->rect.x + (con->rect.width / 2), - con->rect.y + (con->rect.height / 2)); + Output *output = get_output_from_rect(con->rect); if (!output) { ELOG("No output found at destination coordinates?\n"); From 4097769105bf876de9dfa1919e0a97e7b3e62c58 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 24 Mar 2018 14:26:11 +0200 Subject: [PATCH 036/218] _workspace_show -> workspace_show a9b57a44a9d328d5153158445bc6a5c7b78420b0 removed the extra parameter of workspace_show. --- src/workspace.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index 8c46a949..2d574e2a 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -340,7 +340,7 @@ static void workspace_reassign_sticky(Con *con) { /* * Callback to reset the urgent flag of the given con to false. May be started by - * _workspace_show to avoid urgency hints being lost by switching to a workspace + * workspace_show to avoid urgency hints being lost by switching to a workspace * focusing the con. * */ @@ -360,7 +360,11 @@ static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents } } -static void _workspace_show(Con *workspace) { +/* + * Switches to the given workspace + * + */ +void workspace_show(Con *workspace) { Con *current, *old = NULL; Con *old_focus = focused; @@ -487,14 +491,6 @@ static void _workspace_show(Con *workspace) { output_push_sticky_windows(old_focus); } -/* - * Switches to the given workspace - * - */ -void workspace_show(Con *workspace) { - _workspace_show(workspace); -} - /* * Looks up the workspace by name and switches to it. * @@ -502,7 +498,7 @@ void workspace_show(Con *workspace) { void workspace_show_by_name(const char *num) { Con *workspace; workspace = workspace_get(num, NULL); - _workspace_show(workspace); + workspace_show(workspace); } /* From f46bb0396e68ddaa0b491b973f8dfedd98eee3e7 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 24 Mar 2018 15:14:20 +0200 Subject: [PATCH 037/218] workspace_show: remove redundant if --- src/workspace.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index 2d574e2a..edd3ee6f 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -397,10 +397,8 @@ void workspace_show(Con *workspace) { * focused) are skipped, see bug #868. */ if (current && !con_is_internal(current)) { FREE(previous_workspace_name); - if (current) { - previous_workspace_name = sstrdup(current->name); - DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name); - } + previous_workspace_name = sstrdup(current->name); + DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name); } workspace_reassign_sticky(workspace); From 037b1c371097d4fae545f26c5c55365b8cdefdd8 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 23 Mar 2018 12:56:30 +0200 Subject: [PATCH 038/218] _con_move_to_con: showing target_ws is useless The current_ws is shown latter anyway: if (!ignore_focus) { workspace_show(current_ws); ... This also causes the following bug: - Open a window in an empty workspace - Switch to another workspace - seturgent to the first window - Move another window to the first workspace - Urgent flag is now reset --- src/con.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/con.c b/src/con.c index 985d07da..25bea8e5 100644 --- a/src/con.c +++ b/src/con.c @@ -1170,20 +1170,6 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi floating_fix_coordinates(con, &(source_output->rect), &(dest_output->rect)); } else DLOG("Not fixing coordinates, fix_coordinates flag = %d\n", fix_coordinates); - - /* If moving to a visible workspace, call show so it can be considered - * focused. Must do before attaching because workspace_show checks to see - * if focused container is in its area. */ - if (!ignore_focus && workspace_is_visible(target_ws)) { - workspace_show(target_ws); - - /* Don’t warp if told so (when dragging floating windows with the - * mouse for example) */ - if (dont_warp) - x_set_warp_to(NULL); - else - x_set_warp_to(&(con->rect)); - } } /* If moving a fullscreen container and the destination already has a From 9a1fcff4e0e4a1b2bd825c25a9fc468b0a1841d8 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 23 Mar 2018 13:00:36 +0200 Subject: [PATCH 039/218] _con_move_to_con: remove outdated comment --- src/con.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/con.c b/src/con.c index 25bea8e5..669ff5d3 100644 --- a/src/con.c +++ b/src/con.c @@ -1213,10 +1213,6 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* 7: when moving to another workspace, we leave the focus on the current * workspace. (see also #809) */ - - /* Descend focus stack in case focus_next is a workspace which can - * occur if we move to the same workspace. Also show current workspace - * to ensure it is focused. */ if (!ignore_focus) { workspace_show(current_ws); if (dont_warp) { From 9e3b48dd22b469875eefaa4a3c6792ac6ccf4e61 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 23 Mar 2018 13:27:05 +0200 Subject: [PATCH 040/218] con_activate -> con_focus when it is used as a building block --- src/con.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/con.c b/src/con.c index 669ff5d3..480b9e9e 100644 --- a/src/con.c +++ b/src/con.c @@ -1208,7 +1208,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* Restore focus if the output's focused workspace has changed. */ if (con_get_workspace(focused) != old_focus) - con_activate(old_focus); + con_focus(old_focus); } /* 7: when moving to another workspace, we leave the focus on the current From 37106aa84b18d74e556acf6ab1a03ebaa385136a Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 25 Mar 2018 14:25:20 +0300 Subject: [PATCH 041/218] Prefer fullscreen floating containers when on directional focus Fixes #3201 --- src/tree.c | 13 ++++-- ...regress-focus-behind-fullscreen-floating.t | 40 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 testcases/t/296-regress-focus-behind-fullscreen-floating.t diff --git a/src/tree.c b/src/tree.c index 6c6a614e..96766b6a 100644 --- a/src/tree.c +++ b/src/tree.c @@ -567,9 +567,16 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) if (!workspace) return false; - Con *focus = con_descend_tiling_focused(workspace); - if (focus == workspace) { - focus = con_descend_focused(workspace); + /* Use descend_focused first to give higher priority to floating or + * tiling fullscreen containers. */ + Con *focus = con_descend_focused(workspace); + if (focus->fullscreen_mode == CF_NONE) { + Con *focus_tiling = con_descend_tiling_focused(workspace); + /* If descend_tiling returned a workspace then focus is either a + * floating container or the same workspace. */ + if (focus_tiling != workspace) { + focus = focus_tiling; + } } workspace_show(workspace); diff --git a/testcases/t/296-regress-focus-behind-fullscreen-floating.t b/testcases/t/296-regress-focus-behind-fullscreen-floating.t new file mode 100644 index 00000000..0867f082 --- /dev/null +++ b/testcases/t/296-regress-focus-behind-fullscreen-floating.t @@ -0,0 +1,40 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test that directional focus gives focus to floating fullscreen containers when +# switching workspaces. +# Ticket: #3201 +# Bug still in: 4.15-59-gb849fe3e +use i3test i3_config => < 0); +my $ws = fresh_workspace(output => 1); +open_window; +open_floating_window; +cmd 'fullscreen enable'; +my $expected_focus = get_focused($ws); + +cmd 'focus left'; +cmd 'focus right'; + +is (get_focused($ws), $expected_focus, 'floating fullscreen window focused after directional focus'); + +done_testing; From ee1f5511955316e6e6abdcaad427bc41cf8d53f0 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 25 Mar 2018 20:04:31 +0300 Subject: [PATCH 042/218] startup.c: free timer Small memleak. The timer is not called used again since ev_timer_init is called with repeat = 0. --- src/startup.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/startup.c b/src/startup.c index 166842e0..1e733fcb 100644 --- a/src/startup.c +++ b/src/startup.c @@ -49,6 +49,7 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) { if (!sequence) { DLOG("Sequence already deleted, nevermind.\n"); + free(w); return; } From a5014dc7f84fb820929b36264d058491ecbace93 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 25 Mar 2018 20:23:46 +0300 Subject: [PATCH 043/218] cfg_workspace: memleak on duplicate workspace assignment assignment->output is set but lost since TAILQ_INSERT_TAIL is never called when duplicate is set. This essentially happens on every reload. --- src/config_directives.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/config_directives.c b/src/config_directives.c index da1fb580..491c840a 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -328,21 +328,18 @@ CFGFUN(workspace, const char *workspace, const char *output) { * don’t have assignments of a single workspace to different * outputs */ struct Workspace_Assignment *assignment; - bool duplicate = false; TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { if (strcasecmp(assignment->name, workspace) == 0) { ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n", workspace); - assignment->output = sstrdup(output); - duplicate = true; + return; } } - if (!duplicate) { - assignment = scalloc(1, sizeof(struct Workspace_Assignment)); - assignment->name = sstrdup(workspace); - assignment->output = sstrdup(output); - TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments); - } + + assignment = scalloc(1, sizeof(struct Workspace_Assignment)); + assignment->name = sstrdup(workspace); + assignment->output = sstrdup(output); + TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments); } CFGFUN(ipc_socket, const char *path) { From e19a1209614a5cab8b66ca138ad24df2c0345244 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 25 Mar 2018 20:35:53 +0300 Subject: [PATCH 044/218] Free A_TO_WORKSPACE_NUMBER assignments --- src/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index fd379fde..95b7ec98 100644 --- a/src/config.c +++ b/src/config.c @@ -99,7 +99,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, struct Assignment *assign; while (!TAILQ_EMPTY(&assignments)) { assign = TAILQ_FIRST(&assignments); - if (assign->type == A_TO_WORKSPACE) + if (assign->type == A_TO_WORKSPACE || assign->type == A_TO_WORKSPACE_NUMBER) FREE(assign->dest.workspace); else if (assign->type == A_COMMAND) FREE(assign->dest.command); From b0997234ab9b49c43aca5dcd3d15abda88de9877 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 25 Mar 2018 20:48:20 +0300 Subject: [PATCH 045/218] con_toggle_layout: free(tm_dup) outside loop --- src/con.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/con.c b/src/con.c index 480b9e9e..8953f0ed 100644 --- a/src/con.c +++ b/src/con.c @@ -1917,7 +1917,6 @@ void con_toggle_layout(Con *con, const char *toggle_mode) { * now let's activate the current layout (next in list) */ if (current_layout_found) { new_layout = layout; - free(tm_dup); break; } @@ -1925,6 +1924,7 @@ void con_toggle_layout(Con *con, const char *toggle_mode) { current_layout_found = true; } } + free(tm_dup); if (new_layout != L_DEFAULT) { con_set_layout(con, new_layout); From c7dde08673a7c975a6d78b82b7e6579f56d491bf Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 25 Mar 2018 22:29:00 +0300 Subject: [PATCH 046/218] _con_move_to_con: focus_next isn't always con_next_focused(con) con_next_focused uses con's parent. But since con can be inside an unfocused container this means that one of it's siblings could become focused in the current workspace. --- src/con.c | 10 ++++++++-- testcases/t/294-focus-order.t | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/con.c b/src/con.c index 8953f0ed..3861f046 100644 --- a/src/con.c +++ b/src/con.c @@ -1140,7 +1140,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* 1: save the container which is going to be focused after the current * container is moved away */ - Con *focus_next = con_next_focused(con); + Con *focus_next = NULL; + if (!ignore_focus && source_ws == current_ws) { + focus_next = con_descend_focused(source_ws); + if (focus_next == con || con_has_parent(focus_next, con)) { + focus_next = con_next_focused(con); + } + } /* 2: we go up one level, but only when target is a normal container */ if (target->type != CT_WORKSPACE) { @@ -1223,7 +1229,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) + if (focus_next) con_activate(con_descend_focused(focus_next)); /* 8. If anything within the container is associated with a startup sequence, diff --git a/testcases/t/294-focus-order.t b/testcases/t/294-focus-order.t index 0f116241..71f19ded 100644 --- a/testcases/t/294-focus-order.t +++ b/testcases/t/294-focus-order.t @@ -27,6 +27,7 @@ sub kill_and_confirm_focus { } my @windows; +my $ws; sub focus_windows { for (my $i = $#windows; $i >= 0; $i--) { @@ -122,4 +123,22 @@ $windows[0] = open_window; cmd '[id=' . $windows[3]->id . '] move right'; confirm_focus('split-v + unfocused move'); +###################################################################### +# Test that moving an unfocused container from inside a split +# container to another workspace doesn't focus sibling. +###################################################################### + +$ws = fresh_workspace; +$windows[0] = open_window; +$windows[1] = open_window; +cmd 'split v'; +open_window; +cmd 'mark a'; + +cmd '[id=' . $windows[0]->id . '] focus'; +cmd '[con_mark=a] move to workspace ' . get_unused_workspace; + +is(@{get_ws_content($ws)}, 2, 'Sanity check: marked window moved'); +confirm_focus('Move unfocused window from split container'); + done_testing; From a0309cbd52636e900bd75c832b953e58d03eb471 Mon Sep 17 00:00:00 2001 From: Orestis Date: Mon, 26 Mar 2018 18:59:34 +0300 Subject: [PATCH 047/218] xcb_drag_prepare_cb: drain events (#3193) As discussed in PR #3085, X11 events can appear while dragloop->callback() is running. Co-authored-by: Michael Stapelberg --- src/floating.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/floating.c b/src/floating.c index fc68df8e..1bc4996a 100644 --- a/src/floating.c +++ b/src/floating.c @@ -669,8 +669,7 @@ struct drag_x11_cb { const void *extra; }; -static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) { - struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data; +static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) { xcb_motion_notify_event_t *last_motion_notify = NULL; xcb_generic_event_t *event; @@ -731,12 +730,14 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) { if (dragloop->result != DRAGGING) { free(last_motion_notify); - return; + ev_break(EV_A_ EVBREAK_ONE); + return true; } } - if (last_motion_notify == NULL) - return; + if (last_motion_notify == NULL) { + return true; + } /* Ensure that we are either dragging the resize handle (con is NULL) or that the * container still exists. The latter might not be true, e.g., if the window closed @@ -749,9 +750,17 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) { last_motion_notify->root_y, dragloop->extra); } - free(last_motion_notify); + FREE(last_motion_notify); xcb_flush(conn); + return false; +} + +static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) { + struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data; + while (!drain_drag_events(EV_A, dragloop)) { + /* repeatedly drain events: draining might produce additional ones */ + } } /* @@ -826,8 +835,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ main_set_x11_cb(false); ev_prepare_start(main_loop, prepare); - while (loop.result == DRAGGING) - ev_run(main_loop, EVRUN_ONCE); + ev_loop(main_loop, 0); ev_prepare_stop(main_loop, prepare); main_set_x11_cb(true); From e424a31307765b561b0d111bd355036fd5a1f61e Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 27 Mar 2018 03:36:54 +0300 Subject: [PATCH 048/218] cmd_append_layout: resolve_tilde already allocates memory Fixes a small memory leak with all append_layout commands. --- src/commands.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/commands.c b/src/commands.c index 98625a91..500a697e 100644 --- a/src/commands.c +++ b/src/commands.c @@ -789,11 +789,10 @@ void cmd_nop(I3_CMD, const char *comment) { * */ void cmd_append_layout(I3_CMD, const char *cpath) { - char *path = sstrdup(cpath); - LOG("Appending layout \"%s\"\n", path); + LOG("Appending layout \"%s\"\n", cpath); /* Make sure we allow paths like '~/.i3/layout.json' */ - path = resolve_tilde(path); + char *path = resolve_tilde(cpath); char *buf = NULL; ssize_t len; From e6bd2006bcbdd39d23cc43051de7743708ca43c3 Mon Sep 17 00:00:00 2001 From: Elouan Martinet Date: Tue, 20 Mar 2018 13:06:17 +0000 Subject: [PATCH 049/218] Fix potential memory leak xkb_state_new uses calloc and may fail in a rare case, which would cause a memory leak. Note that xkb_state_unref checks if the parameter given is not null (!state) before freeing. Calls to xkb_state_new have been grouped to remove code duplication. Signed-off-by: Elouan Martinet --- src/bindings.c | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index c145b956..fe77bc8f 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -431,31 +431,20 @@ static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key, * */ void translate_keysyms(void) { - struct xkb_state *dummy_state = xkb_state_new(xkb_keymap); - if (dummy_state == NULL) { - ELOG("Could not create XKB state, cannot translate keysyms.\n"); - return; - } - - struct xkb_state *dummy_state_no_shift = xkb_state_new(xkb_keymap); - if (dummy_state_no_shift == NULL) { - ELOG("Could not create XKB state, cannot translate keysyms.\n"); - return; - } - - struct xkb_state *dummy_state_numlock = xkb_state_new(xkb_keymap); - if (dummy_state_numlock == NULL) { - ELOG("Could not create XKB state, cannot translate keysyms.\n"); - return; - } - - struct xkb_state *dummy_state_numlock_no_shift = xkb_state_new(xkb_keymap); - if (dummy_state_numlock_no_shift == NULL) { - ELOG("Could not create XKB state, cannot translate keysyms.\n"); - return; - } - + struct xkb_state *dummy_state = NULL; + struct xkb_state *dummy_state_no_shift = NULL; + struct xkb_state *dummy_state_numlock = NULL; + struct xkb_state *dummy_state_numlock_no_shift = NULL; bool has_errors = false; + + if ((dummy_state = xkb_state_new(xkb_keymap)) == NULL || + (dummy_state_no_shift = xkb_state_new(xkb_keymap)) == NULL || + (dummy_state_numlock = xkb_state_new(xkb_keymap)) == NULL || + (dummy_state_numlock_no_shift = xkb_state_new(xkb_keymap)) == NULL) { + ELOG("Could not create XKB state, cannot translate keysyms.\n"); + goto out; + } + Binding *bind; TAILQ_FOREACH(bind, bindings, bindings) { #define ADD_TRANSLATED_KEY(code, mods) \ @@ -620,6 +609,7 @@ void translate_keysyms(void) { #undef ADD_TRANSLATED_KEY } +out: xkb_state_unref(dummy_state); xkb_state_unref(dummy_state_no_shift); xkb_state_unref(dummy_state_numlock); From 6d983b5ee0ed6ed7bb95cedf1ad01cf55a0f0488 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 25 Mar 2018 04:45:19 +0300 Subject: [PATCH 050/218] _con_move_to_con: don't change focus when moving to active workspace Seems to be the intention, indicated by this comment (con.c:1307-1309): /* For split containers, we use the currently focused container within it. * This allows setting marks on, e.g., tabbed containers which will move * con to a new tab behind the focused tab. */ Related to #3085. --- src/con.c | 11 +++++++++-- src/output.c | 6 ++++++ testcases/t/294-focus-order.t | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/con.c b/src/con.c index 3861f046..d8c30dcf 100644 --- a/src/con.c +++ b/src/con.c @@ -1209,12 +1209,19 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* We need to save the focused workspace on the output in case the * 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 *old_focus_ws = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head)); + Con *old_focus = focused; con_activate(con_descend_focused(con)); /* Restore focus if the output's focused workspace has changed. */ - if (con_get_workspace(focused) != old_focus) + if (con_get_workspace(focused) != old_focus_ws) { con_focus(old_focus); + } + + /* Restore focus to the currently focused container. */ + if (old_focus_ws == current_ws && old_focus->type != CT_WORKSPACE) { + con_activate(old_focus); + } } /* 7: when moving to another workspace, we leave the focus on the current diff --git a/src/output.c b/src/output.c index c76dfd03..571c01cf 100644 --- a/src/output.c +++ b/src/output.c @@ -101,6 +101,12 @@ void output_push_sticky_windows(Con *to_focus) { if (con_is_sticky(current)) { bool ignore_focus = (to_focus == NULL) || (current != to_focus->parent); con_move_to_workspace(current, visible_ws, true, false, ignore_focus); + if (!ignore_focus) { + Con *current_ws = con_get_workspace(focused); + con_activate(con_descend_focused(current)); + /* Pushing sticky windows shouldn't change the focused workspace. */ + con_activate(con_descend_focused(current_ws)); + } } } } diff --git a/testcases/t/294-focus-order.t b/testcases/t/294-focus-order.t index 71f19ded..217cc844 100644 --- a/testcases/t/294-focus-order.t +++ b/testcases/t/294-focus-order.t @@ -141,4 +141,39 @@ cmd '[con_mark=a] move to workspace ' . get_unused_workspace; is(@{get_ws_content($ws)}, 2, 'Sanity check: marked window moved'); confirm_focus('Move unfocused window from split container'); +###################################################################### +# Moving containers to another workspace puts them on the top of the +# focus stack but behind the focused container. +###################################################################### + +for my $new_workspace (0 .. 1) { + fresh_workspace; + $windows[2] = open_window; + $windows[1] = open_window; + fresh_workspace if $new_workspace; + $windows[3] = open_window; + $windows[0] = open_window; + cmd 'mark target'; + + cmd '[id=' . $windows[2]->id . '] move to mark target'; + cmd '[id=' . $windows[1]->id . '] move to mark target'; + confirm_focus('\'move to mark\' focus order' . ($new_workspace ? ' when moving containers from other workspace' : '')); +} + +###################################################################### +# Same but with workspace commands. +###################################################################### + +fresh_workspace; +$windows[2] = open_window; +$windows[1] = open_window; +$ws = fresh_workspace; +$windows[3] = open_window; +$windows[0] = open_window; +cmd 'mark target'; + +cmd '[id=' . $windows[2]->id . '] move to workspace ' . $ws; +cmd '[id=' . $windows[1]->id . '] move to workspace ' . $ws; +confirm_focus('\'move to workspace\' focus order when moving containers from other workspace'); + done_testing; From 6a2728ba796aa88f7cae034b759169f032408d56 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 27 Mar 2018 22:18:17 +0300 Subject: [PATCH 051/218] Introduce get_existing_workspace_by_name --- include/workspace.h | 7 +++++++ src/commands.c | 11 +++-------- src/load_layout.c | 9 +-------- src/randr.c | 6 +----- src/workspace.c | 37 ++++++++++++++++++++----------------- 5 files changed, 32 insertions(+), 38 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index 8d109e9e..3e7183b9 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -24,6 +24,13 @@ #define NET_WM_DESKTOP_NONE 0xFFFFFFF0 #define NET_WM_DESKTOP_ALL 0xFFFFFFFF +/** + * Returns the workspace with the given name or NULL if such a workspace does + * not exist. + * + */ +Con *get_existing_workspace_by_name(const char *name); + /** * Returns a pointer to the workspace with the given number (starting at 0), * creating the workspace if necessary (by allocating the necessary amount of diff --git a/src/commands.c b/src/commands.c index 500a697e..e6f6174b 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1955,11 +1955,9 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { LOG("Renaming current workspace to \"%s\"\n", new_name); } - Con *output, *workspace = NULL; + Con *workspace; if (old_name) { - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), - !strcasecmp(child->name, old_name)); + workspace = get_existing_workspace_by_name(old_name); } else { workspace = con_get_workspace(focused); old_name = workspace->name; @@ -1970,10 +1968,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { return; } - Con *check_dest = NULL; - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(check_dest, output_get_content(output), - !strcasecmp(child->name, new_name)); + Con *check_dest = get_existing_workspace_by_name(new_name); /* If check_dest == workspace, the user might be changing the case of the * workspace, or it might just be a no-op. */ diff --git a/src/load_layout.c b/src/load_layout.c index aa7ac03c..add78875 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -108,18 +108,11 @@ static int json_end_map(void *ctx) { /* Prevent name clashes when appending a workspace, e.g. when the * user tries to restore a workspace called “1” but already has a * workspace called “1”. */ - Con *output; - Con *workspace = NULL; - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name)); char *base = sstrdup(json_node->name); int cnt = 1; - while (workspace != NULL) { + while (get_existing_workspace_by_name(json_node->name) != NULL) { FREE(json_node->name); sasprintf(&(json_node->name), "%s_%d", base, cnt++); - workspace = NULL; - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name)); } free(base); diff --git a/src/randr.c b/src/randr.c index c43c6455..d7e1acca 100644 --- a/src/randr.c +++ b/src/randr.c @@ -427,11 +427,7 @@ void init_ws_for_output(Output *output, Con *content) { if (strcmp(assignment->output, output_primary_name(output)) != 0) continue; - /* check if this workspace actually exists */ - Con *workspace = NULL, *out; - TAILQ_FOREACH(out, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(out), - !strcasecmp(child->name, assignment->name)); + Con *workspace = get_existing_workspace_by_name(assignment->name); if (workspace == NULL) continue; diff --git a/src/workspace.c b/src/workspace.c index edd3ee6f..bdd6f6f4 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -19,6 +19,20 @@ static char *previous_workspace_name = NULL; * keybindings. */ static char **binding_workspace_names = NULL; +/* + * Returns the workspace with the given name or NULL if such a workspace does + * not exist. + * + */ +Con *get_existing_workspace_by_name(const char *name) { + Con *output, *workspace = NULL; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, name)); + } + + return workspace; +} + /* * Sets ws->layout to splith/splitv if default_orientation was specified in the * configfile. Otherwise, it uses splith/splitv depending on whether the output @@ -46,15 +60,12 @@ static void _workspace_apply_default_orientation(Con *ws) { * */ Con *workspace_get(const char *num, bool *created) { - Con *output, *workspace = NULL; - - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num)); + Con *workspace = get_existing_workspace_by_name(num); if (workspace == NULL) { LOG("Creating new workspace \"%s\"\n", num); /* unless an assignment is found, we will create this workspace on the current output */ - output = con_get_output(focused); + Con *output = con_get_output(focused); /* look for assignments */ struct Workspace_Assignment *assignment; @@ -172,7 +183,6 @@ void extract_workspace_names_from_bindings(void) { */ Con *create_workspace_on_output(Output *output, Con *content) { /* add a workspace to this output */ - Con *out, *current; char *name; bool exists = true; Con *ws = con_new(NULL, NULL); @@ -198,10 +208,7 @@ Con *create_workspace_on_output(Output *output, Con *content) { if (assigned) continue; - current = NULL; - TAILQ_FOREACH(out, &(croot->nodes_head), nodes) - GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, target_name)); - exists = (current != NULL); + exists = (get_existing_workspace_by_name(target_name) != NULL); if (!exists) { ws->name = sstrdup(target_name); /* Set ->num to the number of the workspace, if the name actually @@ -222,7 +229,7 @@ Con *create_workspace_on_output(Output *output, Con *content) { ws->num = c; - current = NULL; + Con *out, *current = NULL; TAILQ_FOREACH(out, &(croot->nodes_head), nodes) GREP_FIRST(current, output_get_content(out), child->num == ws->num); exists = (current != NULL); @@ -940,14 +947,10 @@ bool workspace_move_to_output(Con *ws, const char *name) { TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { if (assignment->output == NULL || strcmp(assignment->output, output_primary_name(current_output)) != 0) continue; - /* check if this workspace is already attached to the tree */ - Con *workspace = NULL, *out; - TAILQ_FOREACH(out, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(out), - !strcasecmp(child->name, assignment->name)); - if (workspace != NULL) + if (get_existing_workspace_by_name(assignment->name) != NULL) { continue; + } /* so create the workspace referenced to by this assignment */ LOG("Creating workspace from assignment %s.\n", assignment->name); From 0b5799412aa1fec5f989ecdea410acc46b10e9a4 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 27 Mar 2018 22:36:51 +0300 Subject: [PATCH 052/218] Introduce get_existing_workspace_by_num --- include/workspace.h | 7 +++++++ src/commands.c | 15 ++------------- src/manage.c | 6 +----- src/workspace.c | 25 ++++++++++++++++--------- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index 3e7183b9..1cb24939 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -31,6 +31,13 @@ */ Con *get_existing_workspace_by_name(const char *name); +/** + * Returns the workspace with the given number or NULL if such a workspace does + * not exist. + * + */ +Con *get_existing_workspace_by_num(int num); + /** * Returns a pointer to the workspace with the given number (starting at 0), * creating the workspace if necessary (by allocating the necessary amount of diff --git a/src/commands.c b/src/commands.c index e6f6174b..d2d15618 100644 --- a/src/commands.c +++ b/src/commands.c @@ -394,21 +394,15 @@ 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, *ws = NULL; long parsed_num = ws_name_to_number(which); - if (parsed_num == -1) { LOG("Could not parse initial part of \"%s\" as a number.\n", which); yerror("Could not parse number \"%s\"", which); return; } - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(ws, output_get_content(output), - child->num == parsed_num); - + Con *ws = get_existing_workspace_by_num(parsed_num); if (!ws) { ws = workspace_get(which, NULL); } @@ -901,7 +895,6 @@ void cmd_workspace(I3_CMD, const char *which) { */ void cmd_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); - Con *output, *workspace = NULL; if (con_get_fullscreen_con(croot, CF_GLOBAL)) { LOG("Cannot switch workspace while in global fullscreen\n"); @@ -910,17 +903,13 @@ void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_a } long parsed_num = ws_name_to_number(which); - if (parsed_num == -1) { LOG("Could not parse initial part of \"%s\" as a number.\n", which); yerror("Could not parse number \"%s\"", which); return; } - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), - child->num == parsed_num); - + Con *workspace = get_existing_workspace_by_num(parsed_num); if (!workspace) { LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num); ysuccess(true); diff --git a/src/manage.c b/src/manage.c index 8b306052..9dcc93f5 100644 --- a/src/manage.c +++ b/src/manage.c @@ -265,13 +265,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki Con *assigned_ws = NULL; if (assignment->type == A_TO_WORKSPACE_NUMBER) { - Con *output = NULL; long parsed_num = ws_name_to_number(assignment->dest.workspace); - /* This will only work for workspaces that already exist. */ - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - GREP_FIRST(assigned_ws, output_get_content(output), child->num == parsed_num); - } + assigned_ws = get_existing_workspace_by_num(parsed_num); } /* A_TO_WORKSPACE type assignment or fallback from A_TO_WORKSPACE_NUMBER * when the target workspace number does not exist yet. */ diff --git a/src/workspace.c b/src/workspace.c index bdd6f6f4..643b78dd 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -33,6 +33,20 @@ Con *get_existing_workspace_by_name(const char *name) { return workspace; } +/* + * Returns the workspace with the given number or NULL if such a workspace does + * not exist. + * + */ +Con *get_existing_workspace_by_num(int num) { + Con *output, *workspace = NULL; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + GREP_FIRST(workspace, output_get_content(output), child->num == num); + } + + return workspace; +} + /* * Sets ws->layout to splith/splitv if default_orientation was specified in the * configfile. Otherwise, it uses splith/splitv depending on whether the output @@ -225,17 +239,10 @@ Con *create_workspace_on_output(Output *output, Con *content) { DLOG("Getting next unused workspace by number\n"); int c = 0; while (exists) { - c++; - - ws->num = c; - - Con *out, *current = NULL; - TAILQ_FOREACH(out, &(croot->nodes_head), nodes) - GREP_FIRST(current, output_get_content(out), child->num == ws->num); - exists = (current != NULL); - + exists = (get_existing_workspace_by_num(++c) != NULL); DLOG("result for ws %d: exists = %d\n", c, exists); } + ws->num = c; sasprintf(&(ws->name), "%d", c); } con_attach(ws, content, false); From 4143f3abfc45895ac109cdcbd22c4268d39b9ef1 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Thu, 29 Mar 2018 17:42:58 +0300 Subject: [PATCH 053/218] Fix memory leak when _XKB_RULES_NAMES can't be found Steps to reproduce: 1. Force the branch to be taken: diff --git a/src/bindings.c b/src/bindings.c index fe77bc8f..caa5848c 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -941,7 +941,7 @@ bool load_keymap(void) { struct xkb_keymap *new_keymap = NULL; int32_t device_id; - if (xkb_supported && (device_id = xkb_x11_get_core_keyboard_device_id(conn)) > -1) { + if (0) { if ((new_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) { ELOG("xkb_x11_keymap_new_from_device failed\n"); return false; 2. Run `python2 ./xproperty.py _XKB_RULES_NAMES ''` (from https://github.com/siemer/xproperty) in the xinitrc 3. Memory sanitizers detect memory leaks. Note: We don't (and didn't) pass NULL in xkb_keymap_new_from_names() but an xkb_rule_names structures with NULL fields (fill_rmlvo_from_root only fills its argument when there are no errors) should be equivalent: https://github.com/xkbcommon/libxkbcommon/blob/767fa86d42a5e25e7043622d189247e02a5ca379/NEWS#L349-L351 > The function xkb_keymap_new_from_names() now accepts a NULL value for the 'names' parameter, instead of failing. This is equivalent to passing a 'struct xkb_rule_names' with all fields set to NULL. Fixes #2535. --- src/bindings.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index fe77bc8f..1ec41920 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -958,10 +958,7 @@ bool load_keymap(void) { .options = NULL}; if (fill_rmlvo_from_root(&names) == -1) { ELOG("Could not get _XKB_RULES_NAMES atom from root window, falling back to defaults.\n"); - if ((new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0)) == NULL) { - ELOG("xkb_keymap_new_from_names(NULL) failed\n"); - return false; - } + /* Using NULL for the fields of xkb_rule_names. */ } new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0); free((char *)names.rules); @@ -970,7 +967,7 @@ bool load_keymap(void) { free((char *)names.variant); free((char *)names.options); if (new_keymap == NULL) { - ELOG("xkb_keymap_new_from_names(RMLVO) failed\n"); + ELOG("xkb_keymap_new_from_names failed\n"); return false; } } From 4e0bf58109831dc4a784b7f27ed339de3f1941b2 Mon Sep 17 00:00:00 2001 From: Joona Date: Fri, 30 Mar 2018 03:57:41 -0700 Subject: [PATCH 054/218] Add --modifier flag to i3-config-wizard (#3210) Add --modifier flag to i3-config-wizard The --modifier flag accepts either alt or win, and will generate the configuration file without opening a window. Also adds i3-config-wizard's flags to the manpage. Fixes #3136. --- i3-config-wizard/main.c | 20 ++++++++++++++++++-- man/i3-config-wizard.man | 16 +++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index b368921f..7f9f2d64 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -745,10 +745,12 @@ int main(int argc, char *argv[]) { char *pattern = "pango:monospace 8"; char *patternbold = "pango:monospace bold 8"; int o, option_index = 0; + bool headless_run = false; static struct option long_options[] = { {"socket", required_argument, 0, 's'}, {"version", no_argument, 0, 'v'}, + {"modifier", required_argument, 0, 'm'}, {"limit", required_argument, 0, 'l'}, {"prompt", required_argument, 0, 'P'}, {"prefix", required_argument, 0, 'p'}, @@ -756,7 +758,7 @@ int main(int argc, char *argv[]) { {"help", no_argument, 0, 'h'}, {0, 0, 0, 0}}; - char *options_string = "s:vh"; + char *options_string = "sm:vh"; while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch (o) { @@ -767,9 +769,18 @@ int main(int argc, char *argv[]) { case 'v': printf("i3-config-wizard " I3_VERSION "\n"); return 0; + case 'm': + headless_run = true; + if (strcmp(optarg, "alt") == 0) + modifier = MOD_Mod1; + else if (strcmp(optarg, "win") == 0) + modifier = MOD_Mod4; + else + err(EXIT_FAILURE, "Invalid modifier key %s", optarg); + break; case 'h': printf("i3-config-wizard " I3_VERSION "\n"); - printf("i3-config-wizard [-s ] [-v]\n"); + printf("i3-config-wizard [-s ] [-m win|alt] [-v] [-h]\n"); return 0; } } @@ -826,6 +837,11 @@ int main(int argc, char *argv[]) { modmap_cookie = xcb_get_modifier_mapping(conn); symbols = xcb_key_symbols_alloc(conn); + if (headless_run) { + finish(); + return 0; + } + /* Place requests for the atoms we need as soon as possible */ #define xmacro(atom) \ xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom); diff --git a/man/i3-config-wizard.man b/man/i3-config-wizard.man index 5a9ca39e..e8cce007 100644 --- a/man/i3-config-wizard.man +++ b/man/i3-config-wizard.man @@ -9,7 +9,21 @@ i3-config-wizard - creates a keysym based config based on your layout == SYNOPSIS -i3-config-wizard +i3-config-wizard [*-s* 'socket'] [*-m* 'modifier'] [*-v*] [*-h*] + +== OPTIONS + +*-s, --socket* 'socket':: +Overwrites the path to the i3 IPC socket. + +*-m, --modifier* 'modifier':: +Generates the configuration file headlessly. Accepts win or alt. + +*-v, --version*:: +Display version number and exit. + +*-h, --help*:: +Display a short help message and exit. == FILES From 60875c7bcb447baaec8410fc49a3f82bc9e1c388 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Thu, 14 Sep 2017 12:16:19 +0300 Subject: [PATCH 055/218] Use con_detach instead of TAILQ_REMOVE in floating --- src/floating.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/floating.c b/src/floating.c index 1bc4996a..dcf73b41 100644 --- a/src/floating.c +++ b/src/floating.c @@ -178,9 +178,7 @@ void floating_enable(Con *con, bool automatic) { /* 1: detach the container from its parent */ /* TODO: refactor this with tree_close_internal() */ - TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); - TAILQ_REMOVE(&(con->parent->focus_head), con, focused); - + con_detach(con); con_fix_percent(con->parent); /* 2: create a new container to render the decoration on, add @@ -335,12 +333,10 @@ void floating_disable(Con *con, bool automatic) { Con *parent = con->parent; /* 1: detach from parent container */ - TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); - TAILQ_REMOVE(&(con->parent->focus_head), con, focused); + con_detach(con); /* 2: kill parent container */ - TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); - TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); + con_detach(con->parent); /* clear the pointer before calling tree_close_internal in which the memory is freed */ con->parent = NULL; tree_close_internal(parent, DONT_KILL_WINDOW, true, false); From 10a3c1e8275120340a6e25727026e41eb5c91124 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 13 Sep 2017 03:54:40 +0300 Subject: [PATCH 056/218] Fix focus order in floating_disable for unfocused windows Partially fixes issue #2938 --- include/move.h | 10 ++++++++++ src/floating.c | 45 ++++++++++++--------------------------------- src/move.c | 5 +---- 3 files changed, 23 insertions(+), 37 deletions(-) diff --git a/include/move.h b/include/move.h index 64b12b80..df644a6b 100644 --- a/include/move.h +++ b/include/move.h @@ -17,3 +17,13 @@ * */ void tree_move(Con *con, int direction); + +typedef enum { BEFORE, + AFTER } position_t; + +/** + * This function detaches 'con' from its parent and inserts it either before or + * after 'target'. + * + */ +void insert_con_into(Con *con, Con *target, position_t position); diff --git a/src/floating.c b/src/floating.c index dcf73b41..834ee168 100644 --- a/src/floating.c +++ b/src/floating.c @@ -327,43 +327,22 @@ void floating_disable(Con *con, bool automatic) { return; } - const bool set_focus = (con == focused); - Con *ws = con_get_workspace(con); - Con *parent = con->parent; + Con *tiling_focused = con_descend_tiling_focused(ws); - /* 1: detach from parent container */ - con_detach(con); - - /* 2: kill parent container */ - con_detach(con->parent); - /* clear the pointer before calling tree_close_internal in which the memory is freed */ - con->parent = NULL; - tree_close_internal(parent, DONT_KILL_WINDOW, true, false); - - /* 3: re-attach to the parent of the currently focused con on the workspace - * this floating con was on */ - Con *focused = con_descend_tiling_focused(ws); - - /* if there is no other container on this workspace, focused will be the - * workspace itself */ - if (focused->type == CT_WORKSPACE) - con->parent = focused; - else - con->parent = focused->parent; - - /* con_fix_percent will adjust the percent value */ - con->percent = 0.0; + if (tiling_focused->type == CT_WORKSPACE) { + Con *parent = con->parent; + con_detach(con); + con->parent = NULL; + tree_close_internal(parent, DONT_KILL_WINDOW, true, false); + con_attach(con, tiling_focused, false); + con->percent = 0.0; + con_fix_percent(con->parent); + } else { + insert_con_into(con, tiling_focused, AFTER); + } con->floating = FLOATING_USER_OFF; - - con_attach(con, con->parent, false); - - con_fix_percent(con->parent); - - if (set_focus) - con_activate(con); - floating_set_hint_atom(con, false); ipc_send_window_event("floating", con); } diff --git a/src/move.c b/src/move.c index e8620c24..5bff3dae 100644 --- a/src/move.c +++ b/src/move.c @@ -9,9 +9,6 @@ */ #include "all.h" -typedef enum { BEFORE, - AFTER } position_t; - /* * Returns the lowest container in the tree that has both a and b as descendants. * @@ -65,7 +62,7 @@ static bool is_focused_descendant(Con *con, Con *ancestor) { * after 'target'. * */ -static void insert_con_into(Con *con, Con *target, position_t position) { +void insert_con_into(Con *con, Con *target, position_t position) { Con *parent = target->parent; /* We need to preserve the old con->parent. While it might still be used to * insert the entry before/after it, we call the on_remove_child callback From 3a89f88fb6af78dd78f7cf415d74079fd7910be7 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Thu, 14 Sep 2017 13:00:14 +0300 Subject: [PATCH 057/218] Fix focus order in floating_enable for unfocused windows Partially fixes issue #2938 --- src/floating.c | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/src/floating.c b/src/floating.c index 834ee168..9c15bb10 100644 --- a/src/floating.c +++ b/src/floating.c @@ -176,6 +176,33 @@ void floating_enable(Con *con, bool automatic) { return; } + Con *focus_head_placeholder = NULL; + bool focus_before_parent = true; + if (!set_focus) { + /* Find recursively the ancestor container which is a child of our workspace. + * We need to reuse its focus position later. */ + Con *ancestor = con; + while (ancestor->parent->type != CT_WORKSPACE) { + focus_before_parent &= TAILQ_FIRST(&(ancestor->parent->focus_head)) == ancestor; + ancestor = ancestor->parent; + } + /* Consider the part of the focus stack of our current workspace: + * [ ... S_{i-1} S_{i} S_{i+1} ... ] + * Where S_{x} is a container tree and the container 'con' that is beeing switched to + * floating belongs in S_{i}. The new floating container, 'nc', will have the + * workspace as its parent so it needs to be placed in this stack. If C was focused + * we just need to call con_focus(). Otherwise, nc must be placed before or after S_{i}. + * We should avoid using the S_{i} container for our operations since it might get + * killed if it has no other children. So, the two possible positions are after S_{i-1} + * or before S_{i+1}. + */ + if (focus_before_parent) { + focus_head_placeholder = TAILQ_PREV(ancestor, focus_head, focused); + } else { + focus_head_placeholder = TAILQ_NEXT(ancestor, focused); + } + } + /* 1: detach the container from its parent */ /* TODO: refactor this with tree_close_internal() */ con_detach(con); @@ -194,12 +221,23 @@ void floating_enable(Con *con, bool automatic) { /* We insert nc already, even though its rect is not yet calculated. This * is necessary because otherwise the workspace might be empty (and get * closed in tree_close_internal()) even though it’s not. */ - if (set_focus) { - TAILQ_INSERT_TAIL(&(ws->floating_head), nc, floating_windows); + TAILQ_INSERT_HEAD(&(ws->floating_head), nc, floating_windows); + + struct focus_head *fh = &(ws->focus_head); + if (focus_before_parent) { + if (focus_head_placeholder) { + TAILQ_INSERT_AFTER(fh, focus_head_placeholder, nc, focused); + } else { + TAILQ_INSERT_HEAD(fh, nc, focused); + } } else { - TAILQ_INSERT_HEAD(&(ws->floating_head), nc, floating_windows); + if (focus_head_placeholder) { + TAILQ_INSERT_BEFORE(focus_head_placeholder, nc, focused); + } else { + /* Also used for the set_focus case */ + TAILQ_INSERT_TAIL(fh, nc, focused); + } } - TAILQ_INSERT_TAIL(&(ws->focus_head), nc, focused); /* check if the parent container is empty and close it if so */ if ((con->parent->type == CT_CON || con->parent->type == CT_FLOATING_CON) && From 2c6da57e81d1b6a14abfa8c76678e8060488fe7a Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 15 Sep 2017 02:29:08 +0300 Subject: [PATCH 058/218] Add testcases for toggling floating windows from different workspaces --- testcases/t/135-floating-focus.t | 180 +++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/testcases/t/135-floating-focus.t b/testcases/t/135-floating-focus.t index 97c4e4cd..282bab43 100644 --- a/testcases/t/135-floating-focus.t +++ b/testcases/t/135-floating-focus.t @@ -240,4 +240,184 @@ $ws = get_ws($tmp); is($ws->{floating_nodes}->[1]->{nodes}->[0]->{window}, $first->id, 'first on top'); is($ws->{floating_nodes}->[0]->{nodes}->[0]->{window}, $second->id, 'second behind'); +############################################################################# +# 9: verify that disabling / enabling floating for a window from a different +# workspace maintains the correct focus order. +############################################################################# + +sub open_window_helper { + my $floating = shift if @_; + if ($floating){ + return open_floating_window; + } + else { + return open_window; + } +} + +for my $floating (0, 1){ + $tmp = fresh_workspace; + $first = open_window; + $second = open_window_helper($floating); + is($x->input_focus, $second->id, "second window focused"); + + fresh_workspace; + cmd "[id=" . $second->id . "] floating toggle"; + cmd "workspace $tmp"; + sync_with_i3; + + my $workspace = get_ws($tmp); + is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $second->id, 'second window on first workspace, floating') unless $floating; + is($workspace->{nodes}->[1]->{window}, $second->id, 'second window on first workspace, right') unless !$floating; + is($x->input_focus, $second->id, 'second window still focused'); +} + +############################################################################# +# 10: verify that toggling floating for an unfocused window on another +# workspace doesn't make it focused. +############################################################################# + +for my $floating (0, 1){ + $tmp = fresh_workspace; + $first = open_window_helper($floating); + $second = open_window; + is($x->input_focus, $second->id, 'second (tiling) window focused'); + + fresh_workspace; + cmd "[id=" . $first->id . "] floating toggle"; + cmd "workspace $tmp"; + sync_with_i3; + + my $workspace = get_ws($tmp); + is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $first->id, 'first window on first workspace, floating') unless $floating; + is($workspace->{nodes}->[1]->{window}, $first->id, 'first window on first workspace, right') unless !$floating; + is($x->input_focus, $second->id, 'second window still focused'); +} + +############################################################################# +# 11: verify that toggling floating for a focused window on another workspace +# which has another, unfocused floating window maintains the focus of the +# first window. +############################################################################# +for my $floating (0, 1){ + $tmp = fresh_workspace; + $first = open_window; + $second = open_floating_window; + is($x->input_focus, $second->id, 'second (floating) window focused'); + $third = open_window_helper($floating); + is($x->input_focus, $third->id, "third (floating = $floating) window focused"); + + fresh_workspace; + cmd "[id=" . $third->id . "] floating toggle"; + cmd "workspace $tmp"; + sync_with_i3; + + my $workspace = get_ws($tmp); + is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $third->id, 'third window on first workspace, floating') unless $floating; + is($workspace->{nodes}->[1]->{window}, $third->id, 'third window on first workspace, right') unless !$floating; + is($x->input_focus, $third->id, 'third window still focused'); +} + +############################################################################# +# 12: verify that toggling floating for an unfocused window on another +# workspace which has another, focused floating window doesn't change focus. +############################################################################# + +for my $floating (0, 1){ + $tmp = fresh_workspace; + $first = open_window; + $second = open_window_helper($floating); + is($x->input_focus, $second->id, "second (floating = $floating) window focused"); + $third = open_floating_window; + is($x->input_focus, $third->id, 'third (floating) window focused'); + + fresh_workspace; + cmd "[id=" . $second->id . "] floating toggle"; + cmd "workspace $tmp"; + sync_with_i3; + + my $workspace = get_ws($tmp); + is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $second->id, 'second window on first workspace, floating') unless $floating; + is($workspace->{nodes}->[1]->{window}, $second->id, 'second window on first workspace, right') unless !$floating; + is($x->input_focus, $third->id, 'third window still focused'); +} + +############################################################################# +# 13: For layout [H1 [A V1[ B F ] ] ] verify that toggling F's floating +# mode maintains its focus. +############################################################################# + +for my $floating (0, 1){ + $tmp = fresh_workspace; + $first = open_window; + $second = open_window; + cmd "split v"; + sync_with_i3; + is($x->input_focus, $second->id, "second (floating = $floating) window focused"); + $third = open_window_helper($floating); + is($x->input_focus, $third->id, 'third (floating) window focused'); + + fresh_workspace; + cmd "[id=" . $third->id . "] floating toggle"; + cmd "workspace $tmp"; + sync_with_i3; + + my $workspace = get_ws($tmp); + is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $third->id, 'third window on first workspace, floating') unless $floating; + is($workspace->{nodes}->[1]->{nodes}->[1]->{window}, $third->id, 'third window on first workspace') unless !$floating; + is($x->input_focus, $third->id, 'third window still focused'); +} + +############################################################################# +# 14: For layout [H1 [A V1[ H2 [B H2 [ C V2 [ F D ] ] ] ] ] ] verify that +# toggling F's floating mode maintains its focus. +############################################################################# + +sub kill_and_confirm_focus { + my $focus = shift; + my $msg = shift; + cmd "kill"; + sync_with_i3; + is($x->input_focus, $focus, $msg); +} + +$tmp = fresh_workspace; +my $A = open_window; +my $B = open_window; +cmd "split v"; +my $C = open_window; +cmd "split h"; +my $F = open_window; +cmd "split v"; +my $D = open_window; +is($x->input_focus, $D->id, "D is focused"); + +sync_with_i3; +my $workspace = get_ws($tmp); +is($workspace->{nodes}->[1]->{nodes}->[1]->{nodes}->[1]->{nodes}->[0]->{window}, $F->id, 'F opened in its expected position'); + +fresh_workspace; +cmd "[id=" . $F->id . "] floating enable"; +cmd "workspace $tmp"; +sync_with_i3; + +$workspace = get_ws($tmp); +is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $F->id, 'F on first workspace, floating'); +is($workspace->{nodes}->[1]->{nodes}->[1]->{nodes}->[1]->{nodes}->[0]->{window}, $D->id, 'D where F used to be'); +is($x->input_focus, $D->id, 'D still focused'); + +fresh_workspace; +cmd "[id=" . $F->id . "] floating disable"; +cmd "workspace $tmp"; +sync_with_i3; + +$workspace = get_ws($tmp); +is($workspace->{nodes}->[1]->{nodes}->[1]->{nodes}->[1]->{nodes}->[1]->{window}, $F->id, 'F where D used to be'); +is($x->input_focus, $D->id, 'D still focused'); + +kill_and_confirm_focus($F->id, 'F focused after D is killed'); +kill_and_confirm_focus($C->id, 'C focused after F is killed'); +kill_and_confirm_focus($B->id, 'B focused after C is killed'); +kill_and_confirm_focus($A->id, 'A focused after B is killed'); + done_testing; From 791e407fd3ecbbfa478105c0025a3026f2fcbde8 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 25 Sep 2017 14:00:43 +0300 Subject: [PATCH 059/218] Remove special handling of floating containers in con_next_focused Explanation for the changed test: After $third is switched to floating, the test moves focus to $second. So, the parent of $second (the stacked container) is above $third in the focus stack and it's children ($first, $second) should get focused before $second. When $second is switched to floating the correct focus order for the workspace should be $second->parent (floating con is the parent) > $first->parent (stacked con) > $third. Fixes #1975 --- src/con.c | 45 +++++--------------------------- testcases/t/135-floating-focus.t | 8 ++---- testcases/t/294-focus-order.t | 31 ++++++++++++++++++++++ 3 files changed, 40 insertions(+), 44 deletions(-) diff --git a/src/con.c b/src/con.c index d8c30dcf..f57f0cb3 100644 --- a/src/con.c +++ b/src/con.c @@ -1436,40 +1436,6 @@ orientation_t con_orientation(Con *con) { * */ Con *con_next_focused(Con *con) { - Con *next; - /* floating containers are attached to a workspace, so we focus either the - * next floating container (if any) or the workspace itself. */ - if (con->type == CT_FLOATING_CON) { - DLOG("selecting next for CT_FLOATING_CON\n"); - next = TAILQ_NEXT(con, floating_windows); - DLOG("next = %p\n", next); - if (!next) { - next = TAILQ_PREV(con, floating_head, floating_windows); - DLOG("using prev, next = %p\n", next); - } - if (!next) { - Con *ws = con_get_workspace(con); - next = ws; - DLOG("no more floating containers for next = %p, restoring workspace focus\n", next); - while (next != TAILQ_END(&(ws->focus_head)) && !TAILQ_EMPTY(&(next->focus_head))) { - next = TAILQ_FIRST(&(next->focus_head)); - if (next == con) { - DLOG("skipping container itself, we want the next client\n"); - next = TAILQ_NEXT(next, focused); - } - } - if (next == TAILQ_END(&(ws->focus_head))) { - DLOG("Focus list empty, returning ws\n"); - next = ws; - } - } else { - /* Instead of returning the next CT_FLOATING_CON, we descend it to - * get an actual window to focus. */ - next = con_descend_focused(next); - } - return next; - } - /* dock clients cannot be focused, so we focus the workspace instead */ if (con->parent->type == CT_DOCKAREA) { DLOG("selecting workspace for dock client\n"); @@ -1478,10 +1444,9 @@ Con *con_next_focused(Con *con) { /* if 'con' is not the first entry in the focus stack, use the first one as * it’s currently focused already */ - Con *first = TAILQ_FIRST(&(con->parent->focus_head)); - if (first != con) { - DLOG("Using first entry %p\n", first); - next = first; + Con *next = TAILQ_FIRST(&(con->parent->focus_head)); + if (next != con) { + DLOG("Using first entry %p\n", next); } else { /* try to focus the next container on the same level as this one or fall * back to its parent */ @@ -1496,6 +1461,10 @@ Con *con_next_focused(Con *con) { next = TAILQ_FIRST(&(next->focus_head)); } + if (con->type == CT_FLOATING_CON && next != con->parent) { + next = con_descend_focused(next); + } + return next; } diff --git a/testcases/t/135-floating-focus.t b/testcases/t/135-floating-focus.t index 282bab43..168151f4 100644 --- a/testcases/t/135-floating-focus.t +++ b/testcases/t/135-floating-focus.t @@ -105,18 +105,14 @@ cmd 'split v'; cmd 'layout stacked'; $second = open_window({ background_color => '#00ff00' }); # window 6 $third = open_window({ background_color => '#0000ff' }); # window 7 - is($x->input_focus, $third->id, 'last container focused'); -cmd 'floating enable'; - cmd '[id="' . $second->id . '"] focus'; - -is($x->input_focus, $second->id, 'second con focused'); - cmd 'floating enable'; +cmd '[id="' . $third->id . '"] floating enable'; sync_with_i3; +is($x->input_focus, $second->id, 'second con focused'); # now kill the second one. focus should fall back to the third one, which is # also floating diff --git a/testcases/t/294-focus-order.t b/testcases/t/294-focus-order.t index 217cc844..c818f1d4 100644 --- a/testcases/t/294-focus-order.t +++ b/testcases/t/294-focus-order.t @@ -176,4 +176,35 @@ cmd '[id=' . $windows[2]->id . '] move to workspace ' . $ws; cmd '[id=' . $windows[1]->id . '] move to workspace ' . $ws; confirm_focus('\'move to workspace\' focus order when moving containers from other workspace'); +###################################################################### +# Test focus order with floating and tiling windows. +# See issue: 1975 +###################################################################### + +fresh_workspace; +$windows[2] = open_window; +$windows[0] = open_window; +$windows[3] = open_floating_window; +$windows[1] = open_floating_window; +focus_windows; + +confirm_focus('mix of floating and tiling windows'); + +###################################################################### +# Same but an unfocused tiling window is killed first. +###################################################################### + +fresh_workspace; +$windows[2] = open_window; +$windows[0] = open_window; +$windows[3] = open_floating_window; +$windows[1] = open_floating_window; +focus_windows; + +cmd '[id=' . $windows[1]->id . '] focus'; +cmd '[id=' . $windows[0]->id . '] kill'; + +kill_and_confirm_focus($windows[2]->id, 'window 2 focused after tiling killed'); +kill_and_confirm_focus($windows[3]->id, 'window 3 focused after tiling killed'); + done_testing; From 725ee3ce6269947577deb7a67a0e1d9762c21600 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 30 Mar 2018 21:05:32 +0200 Subject: [PATCH 060/218] move i3 sync code into sync_respond (for following commits) --- Makefile.am | 1 + include/all.h | 1 + include/sync.h | 14 ++++++++++++++ src/handlers.c | 16 +--------------- src/sync.c | 28 ++++++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 15 deletions(-) create mode 100644 include/sync.h create mode 100644 src/sync.c diff --git a/Makefile.am b/Makefile.am index 184b0734..557c65a5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -562,6 +562,7 @@ i3_SOURCES = \ src/sd-daemon.c \ src/sighandler.c \ src/startup.c \ + src/sync.c \ src/tree.c \ src/util.c \ src/version.c \ diff --git a/include/all.h b/include/all.h index ecc875d0..e93b066b 100644 --- a/include/all.h +++ b/include/all.h @@ -82,4 +82,5 @@ #include "fake_outputs.h" #include "display_version.h" #include "restore_layout.h" +#include "sync.h" #include "main.h" diff --git a/include/sync.h b/include/sync.h new file mode 100644 index 00000000..e726f99e --- /dev/null +++ b/include/sync.h @@ -0,0 +1,14 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + * sync.c: i3 sync protocol: https://i3wm.org/docs/testsuite.html#i3_sync + * + */ +#pragma once + +#include + +void sync_respond(xcb_window_t window, uint32_t rnd); diff --git a/src/handlers.c b/src/handlers.c index 50fd8566..d5023b9d 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -800,21 +800,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { } else if (event->type == A_I3_SYNC) { xcb_window_t window = event->data.data32[0]; uint32_t rnd = event->data.data32[1]; - DLOG("[i3 sync protocol] Sending random value %d back to X11 window 0x%08x\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 = A_I3_SYNC; - ev->format = 32; - ev->data.data32[0] = window; - ev->data.data32[1] = rnd; - - xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)ev); - xcb_flush(conn); - free(reply); + sync_respond(window, rnd); } else if (event->type == A__NET_REQUEST_FRAME_EXTENTS) { /* * A client can request an estimate for the frame size which the window diff --git a/src/sync.c b/src/sync.c new file mode 100644 index 00000000..dafbc355 --- /dev/null +++ b/src/sync.c @@ -0,0 +1,28 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + * sync.c: i3 sync protocol: https://i3wm.org/docs/testsuite.html#i3_sync + * + */ +#include "all.h" + +void sync_respond(xcb_window_t window, uint32_t rnd) { + DLOG("[i3 sync protocol] Sending random value %d back to X11 window 0x%08x\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 = A_I3_SYNC; + ev->format = 32; + ev->data.data32[0] = window; + ev->data.data32[1] = rnd; + + xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)ev); + xcb_flush(conn); + free(reply); +} From eca8fae2de2a2f7d6a8953d5ce6e0a76c6dc1264 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 30 Mar 2018 21:06:18 +0200 Subject: [PATCH 061/218] introduce the sync IPC command Sending the sync command via IPC ensures pending IPC messages are handled by i3 before the sync response is read. This is rarely useful for direct IPC connections to i3, but becomes useful when synchronizing with i3bar, which might have pending IPC messages in response to button clicks. --- AnyEvent-I3/lib/AnyEvent/I3.pm | 16 ++++++++- docs/ipc | 13 +++++++ include/i3/ipc.h | 4 +++ src/ipc.c | 62 +++++++++++++++++++++++++++++++++- 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/AnyEvent-I3/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm index 198c41c9..ae9e5bea 100644 --- a/AnyEvent-I3/lib/AnyEvent/I3.pm +++ b/AnyEvent-I3/lib/AnyEvent/I3.pm @@ -100,11 +100,12 @@ 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; +use constant TYPE_SYNC => 11; 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_SEND_TICK) + TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK TYPE_SYNC) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -534,6 +535,19 @@ sub send_tick { $self->message(TYPE_SEND_TICK, $payload); } +=head2 sync + +Sends an i3 sync event. Requires i3 >= 4.16 + +=cut +sub sync { + my ($self, $payload) = @_; + + $self->_ensure_connection; + + $self->message(TYPE_SYNC, $payload); +} + =head2 command($content) Makes i3 execute the given command diff --git a/docs/ipc b/docs/ipc index 8b767ade..f011e773 100644 --- a/docs/ipc +++ b/docs/ipc @@ -65,6 +65,7 @@ to do that). | 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. +| 11 | +SYNC+ | <<_sync_reply,SYNC>> | Sends an i3 sync event with the specified random value to the specified window. |====================================================== So, a typical message could look like this: @@ -654,6 +655,18 @@ events generated prior to the +SEND_TICK+ message (happened-before relation). { "success": true } ------------------- +[[_sync_reply]] +=== SYNC reply + +The reply is a map containing the "success" member. After the reply was +received, the https://i3wm.org/docs/testsuite.html#i3_sync[i3 sync message] was +responded to. + +*Example:* +------------------- +{ "success": true } +------------------- + == Events [[events]] diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 9e0280c9..0c57f7fd 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -63,6 +63,9 @@ typedef struct i3_ipc_header { /** Send a tick event to all subscribers. */ #define I3_IPC_MESSAGE_TYPE_SEND_TICK 10 +/** Trigger an i3 sync protocol message via IPC. */ +#define I3_IPC_MESSAGE_TYPE_SYNC 11 + /* * Messages from i3 to clients * @@ -78,6 +81,7 @@ typedef struct i3_ipc_header { #define I3_IPC_REPLY_TYPE_BINDING_MODES 8 #define I3_IPC_REPLY_TYPE_CONFIG 9 #define I3_IPC_REPLY_TYPE_TICK 10 +#define I3_IPC_REPLY_TYPE_SYNC 11 /* * Events from i3 to clients. Events have the first bit set high. diff --git a/src/ipc.c b/src/ipc.c index 6b6383ec..d422217e 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -1173,9 +1173,68 @@ IPC_HANDLER(send_tick) { DLOG("Sent tick event\n"); } +struct sync_state { + char *last_key; + uint32_t rnd; + xcb_window_t window; +}; + +static int _sync_json_key(void *extra, const unsigned char *val, size_t len) { + struct sync_state *state = extra; + FREE(state->last_key); + state->last_key = scalloc(len + 1, 1); + memcpy(state->last_key, val, len); + return 1; +} + +static int _sync_json_int(void *extra, long long val) { + struct sync_state *state = extra; + if (strcasecmp(state->last_key, "rnd") == 0) { + state->rnd = val; + } else if (strcasecmp(state->last_key, "window") == 0) { + state->window = (xcb_window_t)val; + } + return 1; +} + +IPC_HANDLER(sync) { + yajl_handle p; + yajl_status stat; + + /* Setup the JSON parser */ + static yajl_callbacks callbacks = { + .yajl_map_key = _sync_json_key, + .yajl_integer = _sync_json_int, + }; + + struct sync_state state; + memset(&state, '\0', sizeof(struct sync_state)); + p = yalloc(&callbacks, (void *)&state); + stat = yajl_parse(p, (const unsigned char *)message, message_size); + FREE(state.last_key); + if (stat != yajl_status_ok) { + unsigned char *err; + err = yajl_get_error(p, true, (const unsigned char *)message, + message_size); + ELOG("YAJL parse error: %s\n", err); + yajl_free_error(p, err); + + const char *reply = "{\"success\":false}"; + ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SYNC, (const uint8_t *)reply); + yajl_free(p); + return; + } + yajl_free(p); + + DLOG("received IPC sync request (rnd = %d, window = 0x%08x)\n", state.rnd, state.window); + sync_respond(state.window, state.rnd); + const char *reply = "{\"success\":true}"; + ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SYNC, (const uint8_t *)reply); +} + /* The index of each callback function corresponds to the numeric * value of the message type (see include/i3/ipc.h) */ -handler_t handlers[11] = { +handler_t handlers[12] = { handle_run_command, handle_get_workspaces, handle_subscribe, @@ -1187,6 +1246,7 @@ handler_t handlers[11] = { handle_get_binding_modes, handle_get_config, handle_send_tick, + handle_sync, }; /* From 145ac532aaa09259ff71a8ab2a75c3bf4004a80c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 30 Mar 2018 21:07:48 +0200 Subject: [PATCH 062/218] i3bar: forward the sync request via IPC, not X11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i3bar’s X11 output is not what our testcases are testing — the state manipulations which i3bar triggers via IPC messages to i3 are what we are interested in. --- i3bar/src/ipc.c | 19 ++++++++++++------- i3bar/src/xcb.c | 21 ++++++--------------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index cc3563ec..7a657338 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -114,13 +114,18 @@ void got_bar_config(char *reply) { /* Data structure to easily call the reply handlers later */ handler_t reply_handlers[] = { - &got_command_reply, - &got_workspace_reply, - &got_subscribe_reply, - &got_output_reply, - NULL, - NULL, - &got_bar_config, + &got_command_reply, /* I3_IPC_REPLY_TYPE_COMMAND */ + &got_workspace_reply, /* I3_IPC_REPLY_TYPE_WORKSPACES */ + &got_subscribe_reply, /* I3_IPC_REPLY_TYPE_SUBSCRIBE */ + &got_output_reply, /* I3_IPC_REPLY_TYPE_OUTPUTS */ + NULL, /* I3_IPC_REPLY_TYPE_TREE */ + NULL, /* I3_IPC_REPLY_TYPE_MARKS */ + &got_bar_config, /* I3_IPC_REPLY_TYPE_BAR_CONFIG */ + NULL, /* I3_IPC_REPLY_TYPE_VERSION */ + NULL, /* I3_IPC_REPLY_TYPE_BINDING_MODES */ + NULL, /* I3_IPC_REPLY_TYPE_CONFIG */ + NULL, /* I3_IPC_REPLY_TYPE_TICK */ + NULL, /* I3_IPC_REPLY_TYPE_SYNC */ }; /* diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 542c86c3..8843edbd 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -694,21 +694,12 @@ static void handle_client_message(xcb_client_message_event_t *event) { 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); + /* Forward the request to i3 via the IPC interface so that all pending + * IPC messages are guaranteed to be handled. */ + char *payload = NULL; + sasprintf(&payload, "{\"rnd\":%d, \"window\":%d}", rnd, window); + i3_send_msg(I3_IPC_MESSAGE_TYPE_SYNC, payload); + free(payload); } else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] && event->format == 32) { DLOG("_NET_SYSTEM_TRAY_OPCODE received\n"); From 874151bb09ae7e2b06b4e96c2396972d21a82a07 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 30 Mar 2018 21:08:35 +0200 Subject: [PATCH 063/218] t/525-i3bar-mouse-bindings.t: sync with i3 _and_ i3bar See the comment in the code for rationale. --- testcases/t/525-i3bar-mouse-bindings.t | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t index 87552785..3593ea0b 100644 --- a/testcases/t/525-i3bar-mouse-bindings.t +++ b/testcases/t/525-i3bar-mouse-bindings.t @@ -100,11 +100,19 @@ sub focus_subtest { is_deeply(\@focus, $want, $msg); } +sub sync { + # Ensure XTEST events were sent to i3, which grabs and hence needs to + # forward any events to i3bar: + xtest_sync_with_i3; + # Ensure any pending i3bar IPC messages were handled by i3: + xtest_sync_with($i3bar_window); +} + subtest 'button 1 moves focus left', \&focus_subtest, sub { xtest_button_press(1, 3, 3); xtest_button_release(1, 3, 3); - xtest_sync_with($i3bar_window); + sync; }, [ $left->{id} ], 'button 1 moves focus left'; @@ -113,7 +121,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($i3bar_window); + sync; }, [ $right->{id} ], 'button 2 moves focus right'; @@ -122,7 +130,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($i3bar_window); + sync; }, [ $left->{id} ], 'button 3 moves focus left'; @@ -131,7 +139,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($i3bar_window); + sync; }, [ $right->{id} ], 'button 4 moves focus right'; @@ -140,7 +148,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($i3bar_window); + sync; }, [ $left->{id} ], 'button 5 moves focus left'; @@ -152,7 +160,7 @@ 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); + sync; }, [], 'button 6 does not move focus while pressed'; @@ -161,7 +169,7 @@ 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); + sync; }, [ $right->{id} ], 'button 6 release moves focus right'; @@ -171,7 +179,7 @@ subtest 'button 6 release moves focus right', \&focus_subtest, subtest 'button 7 press moves focus left', \&focus_subtest, sub { xtest_button_press(7, 3, 3); - xtest_sync_with($i3bar_window); + sync; }, [ $left->{id} ], 'button 7 press moves focus left'; @@ -179,7 +187,7 @@ subtest 'button 7 press moves focus left', \&focus_subtest, subtest 'button 7 release moves focus right', \&focus_subtest, sub { xtest_button_release(7, 3, 3); - xtest_sync_with($i3bar_window); + sync; }, [ $right->{id} ], 'button 7 release moves focus right'; From fbce834b20052952d48fea1299343797beab5ae1 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 1 Apr 2018 02:02:50 +0300 Subject: [PATCH 064/218] Window decoration scrolling: don't focus sibling The current behaviour is buggy in the following layout: T [ A* V [ B C ] ], where the focus stack in V is B > C. When the user scrolls down, focus correctly moves to B but if the user scrolls down again the whole vertical container is focused. This happens because 'bool scroll_next_possible' is false but con_activate is called on the tabbed container's sibling - the vertical container. --- src/click.c | 8 +--- testcases/t/297-scroll-tabbed.t | 79 +++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 testcases/t/297-scroll-tabbed.t diff --git a/src/click.c b/src/click.c index b036c5f8..f4727f28 100644 --- a/src/click.c +++ b/src/click.c @@ -234,14 +234,10 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod event->detail == XCB_BUTTON_SCROLL_RIGHT)) { DLOG("Scrolling on a window decoration\n"); orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ); - /* Focus the currently focused container on the same level that the - * user scrolled on. e.g. the tabbed decoration contains - * "urxvt | i3: V[xterm geeqie] | firefox", - * focus is on the xterm, but the user scrolled on urxvt. - * The splitv container will be focused. */ + /* Use the focused child of the tabbed / stacked container, not the + * container the user scrolled on. */ Con *focused = con->parent; focused = TAILQ_FIRST(&(focused->focus_head)); - 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); diff --git a/testcases/t/297-scroll-tabbed.t b/testcases/t/297-scroll-tabbed.t new file mode 100644 index 00000000..6deeaf65 --- /dev/null +++ b/testcases/t/297-scroll-tabbed.t @@ -0,0 +1,79 @@ +#!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 if scrolling the tab bar on a tabbed container works and verifies that +# only one window is focused as a result. +# Ticket: #3215 (PR) +# Bug still in: 4.15-92-g666aa9e0 +use i3test; +use i3test::XTEST; + +sub scroll_down { + # button5 = scroll down + xtest_button_press(5, 3, 3); + xtest_button_release(5, 3, 3); + xtest_sync_with_i3; +} + +sub scroll_up { + # button4 = scroll up + xtest_button_press(4, 3, 3); + xtest_button_release(4, 3, 3); + xtest_sync_with_i3; +} + +# Decoration of top left window. +$x->root->warp_pointer(3, 3); + +# H [ T [ H [ A B ] C D V [ E F ] ] G ] +# Inner horizontal split. +open_window; +cmd 'layout tabbed'; +cmd 'splith'; +my $first = open_window; +cmd 'focus parent'; +# Simple tabs. +open_window; +my $second_last = open_window; +# V-Split container +open_window; +cmd 'splitv'; +my $last = open_window; +# Second child of the outer horizontal split, next to the tabbed one. +open_window; +cmd 'move right, move right'; + +cmd '[id=' . $first->id . '] focus'; + +# Scroll from first to last. +scroll_down; +scroll_down; +is($x->input_focus, $second_last->id, 'Sanity check: scrolling'); +scroll_down; +is($x->input_focus, $last->id, 'Last window focused through scrolling'); +scroll_down; +is($x->input_focus, $last->id, 'Scrolling again doesn\'t leave the tabbed container and doesn\'t focus the whole sibling'); + +# Scroll from last to first. +scroll_up; +is($x->input_focus, $second_last->id, 'Scrolling up works'); +scroll_up; +scroll_up; +is($x->input_focus, $first->id, 'First window focused through scrolling'); +scroll_up; +is($x->input_focus, $first->id, 'Scrolling again doesn\'t focus the whole sibling'); + +done_testing; From 96ee336a0b2096aafeb41a32e8748ab30d8a6e89 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 1 Apr 2018 02:11:29 +0300 Subject: [PATCH 065/218] Use con_orientation instead of ternary operator --- src/click.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/click.c b/src/click.c index f4727f28..656fb8db 100644 --- a/src/click.c +++ b/src/click.c @@ -233,7 +233,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod event->detail == XCB_BUTTON_SCROLL_LEFT || event->detail == XCB_BUTTON_SCROLL_RIGHT)) { DLOG("Scrolling on a window decoration\n"); - orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ); + orientation_t orientation = con_orientation(con->parent); /* Use the focused child of the tabbed / stacked container, not the * container the user scrolled on. */ Con *focused = con->parent; From 393412a204184d90b010dbaef1f9caed040c836e Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 4 Apr 2018 18:09:34 +0300 Subject: [PATCH 066/218] A__NET_REQUEST_FRAME_EXTENTS: use render_font_height() --- src/handlers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index d5023b9d..15c05a8f 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -820,7 +820,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { Rect r = { config.default_border_width, /* left */ config.default_border_width, /* right */ - config.font.height + 5, /* top */ + render_deco_height(), /* top */ config.default_border_width /* bottom */ }; xcb_change_property( From 0aa636b20728dfcacd0f1c28b15bb1dd26de33f7 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 4 Apr 2018 18:12:44 +0300 Subject: [PATCH 067/218] Prefer compiler warnings to assertions for unhandled switch cases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using 'default:' cases can hide logical errors which would lead to i3 crashes for users. With this change the compiler will print a warning when a case is not handled. For example, if I add a new value in the Font.type enum: ../../i3/libi3/font.c: In function ‘draw_text’: ../../i3/libi3/font.c:378:5: warning: enumeration value ‘NEWFONT’ not handled in switch [-Wswitch] switch (savedFont->type) { ^~~~~~ --- libi3/font.c | 14 +------------- src/click.c | 3 --- src/con.c | 12 ++++-------- src/ipc.c | 4 ---- 4 files changed, 5 insertions(+), 28 deletions(-) diff --git a/libi3/font.c b/libi3/font.c index 81091ea7..aedd4b32 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -283,9 +283,6 @@ void free_font(void) { /* Free the font description */ pango_font_description_free(savedFont->specific.pango_desc); break; - default: - assert(false); - break; } savedFont = NULL; @@ -315,9 +312,6 @@ void set_font_colors(xcb_gcontext_t gc, color_t foreground, color_t background) pango_font_green = foreground.green; pango_font_blue = foreground.blue; break; - default: - assert(false); - break; } } @@ -388,8 +382,6 @@ void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc, draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text), drawable, visual, x, y, max_width, i3string_is_markup(text)); return; - default: - assert(false); } } @@ -425,8 +417,6 @@ void draw_text_ascii(const char *text, xcb_drawable_t drawable, draw_text_pango(text, strlen(text), drawable, root_visual_type, x, y, max_width, false); return; - default: - assert(false); } } @@ -519,8 +509,6 @@ int predict_text_width(i3String *text) { /* Calculate extents using Pango */ return predict_text_width_pango(i3string_as_utf8(text), i3string_get_num_bytes(text), i3string_is_markup(text)); - default: - assert(false); - return 0; } + assert(false); } diff --git a/src/click.c b/src/click.c index 656fb8db..c2b38d4d 100644 --- a/src/click.c +++ b/src/click.c @@ -44,9 +44,6 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press case BORDER_BOTTOM: search_direction = D_DOWN; break; - default: - assert(false); - break; } bool res = resize_find_tiling_participants(&first, &second, search_direction, false); diff --git a/src/con.c b/src/con.c index f57f0cb3..2180ad88 100644 --- a/src/con.c +++ b/src/con.c @@ -1413,20 +1413,16 @@ orientation_t con_orientation(Con *con) { return HORIZ; case L_DEFAULT: - DLOG("Someone called con_orientation() on a con with L_DEFAULT, this is a bug in the code.\n"); + ELOG("Someone called con_orientation() on a con with L_DEFAULT, this is a bug in the code.\n"); assert(false); - return HORIZ; case L_DOCKAREA: case L_OUTPUT: - DLOG("con_orientation() called on dockarea/output (%d) container %p\n", con->layout, con); - assert(false); - return HORIZ; - - default: - DLOG("con_orientation() ran into default\n"); + ELOG("con_orientation() called on dockarea/output (%d) container %p\n", con->layout, con); assert(false); } + /* should not be reached */ + assert(false); } /* diff --git a/src/ipc.c b/src/ipc.c index d422217e..25da1617 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -268,10 +268,6 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { case CT_DOCKAREA: ystr("dockarea"); break; - default: - DLOG("About to dump unknown container type=%d. This is a bug.\n", con->type); - assert(false); - break; } /* provided for backwards compatibility only. */ From e4a184e77e735c7bc387c832cae2d6454c4f9da9 Mon Sep 17 00:00:00 2001 From: Oliver Graff Date: Sat, 31 Mar 2018 15:56:59 -0400 Subject: [PATCH 068/218] Workspace renaming: Interpret outputs as nondirectional Currently when renaming outputs, an output assignment of "left" will cause the workspace to move left. Treat this assignment as a proper name instead (even though it is unlikely an output will be named "left"). Move logic for determining output to move to out of `workspace_move_to_output` Add test for ignoring direcionality during rename. Fixes #3208. --- include/workspace.h | 2 +- src/commands.c | 23 +++++++++++++++++++-- src/workspace.c | 10 ++------- testcases/t/522-rename-assigned-workspace.t | 12 +++++++++++ 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index 1cb24939..ae287422 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -207,4 +207,4 @@ Con *workspace_encapsulate(Con *ws); * This returns true if and only if moving the workspace was successful. * */ -bool workspace_move_to_output(Con *ws, const char *output); +bool workspace_move_to_output(Con *ws, Output *output); diff --git a/src/commands.c b/src/commands.c index d2d15618..d6733a30 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1128,7 +1128,21 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) { continue; } - bool success = workspace_move_to_output(ws, name); + Output *current_output = get_output_for_con(ws); + if (current_output == NULL) { + ELOG("Cannot get current output. This is a bug in i3.\n"); + ysuccess(false); + return; + } + + Output *target_output = get_output_from_string(current_output, name); + if (!target_output) { + ELOG("Could not get output from string \"%s\"\n", name); + ysuccess(false); + return; + } + + bool success = workspace_move_to_output(ws, target_output); if (!success) { ELOG("Failed to move workspace to output.\n"); ysuccess(false); @@ -1990,7 +2004,12 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { continue; } - workspace_move_to_output(workspace, assignment->output); + Output *target_output = get_output_by_name(assignment->output, true); + if (!target_output) { + LOG("Could not get output named \"%s\"\n", assignment->output); + continue; + } + workspace_move_to_output(workspace, target_output); if (previously_focused) workspace_show(con_get_workspace(previously_focused)); diff --git a/src/workspace.c b/src/workspace.c index 643b78dd..a16479a5 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -923,8 +923,8 @@ Con *workspace_encapsulate(Con *ws) { * Move the given workspace to the specified output. * This returns true if and only if moving the workspace was successful. */ -bool workspace_move_to_output(Con *ws, const char *name) { - LOG("Trying to move workspace %p / %s to output \"%s\".\n", ws, ws->name, name); +bool workspace_move_to_output(Con *ws, Output *output) { + LOG("Trying to move workspace %p / %s to output %p / \"%s\".\n", ws, ws->name, output, output_primary_name(output)); Output *current_output = get_output_for_con(ws); if (current_output == NULL) { @@ -932,12 +932,6 @@ bool workspace_move_to_output(Con *ws, const char *name) { return false; } - Output *output = get_output_from_string(current_output, name); - if (!output) { - ELOG("Could not get output from string \"%s\"\n", name); - return false; - } - Con *content = output_get_content(output->con); LOG("got output %p with content %p\n", output, content); diff --git a/testcases/t/522-rename-assigned-workspace.t b/testcases/t/522-rename-assigned-workspace.t index 981471f7..8a6edc85 100644 --- a/testcases/t/522-rename-assigned-workspace.t +++ b/testcases/t/522-rename-assigned-workspace.t @@ -28,6 +28,7 @@ workspace 1 output fake-0 workspace 2 output fake-1 workspace 3:foo output fake-1 workspace baz output fake-1 +workspace 5 output left EOT my $i3 = i3(get_socket_path()); @@ -82,4 +83,15 @@ cmd 'rename workspace to baz'; is(get_output_for_workspace('baz'), 'fake-1', 'Renaming the workspace to a number and name should move it to the assigned output'); +########################################################################## +# Renaming a workspace so that it is assigned a directional output does +# not move the workspace or crash +########################################################################## + +cmd 'focus output fake-0'; +cmd 'workspace bar'; +cmd 'rename workspace to 5'; +is(get_output_for_workspace('5'), 'fake-0', + 'Renaming the workspace to a workspace assigned to a directional output should not move the workspace'); + done_testing; From 626af81232e6ca81abea22267f3330c16d804596 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 7 Apr 2018 21:43:29 +0300 Subject: [PATCH 069/218] Fix crash when moving container to marked workspace Was a small typo. This also has the (positive) side-effect of allowing to move all the content of a marked workspace next to the target container, see added tests. --- src/con.c | 2 +- testcases/t/243-move-to-mark.t | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/con.c b/src/con.c index 2180ad88..deb65558 100644 --- a/src/con.c +++ b/src/con.c @@ -1305,7 +1305,7 @@ bool con_move_to_mark(Con *con, const char *mark) { return true; } - if (con->type == CT_WORKSPACE) { + if (target->type == CT_WORKSPACE) { DLOG("target container is a workspace, simply moving the container there.\n"); con_move_to_workspace(con, target, true, false, false); return true; diff --git a/testcases/t/243-move-to-mark.t b/testcases/t/243-move-to-mark.t index 25d13333..c6b67f35 100644 --- a/testcases/t/243-move-to-mark.t +++ b/testcases/t/243-move-to-mark.t @@ -336,6 +336,50 @@ sync_with_i3; does_i3_live; +############################################################################### +# Given 'S' and 'M' where 'M' is a workspace and 'S' is on a different +# workspace, then 'S' ends up as a tiling container on 'M'. +############################################################################### + +fresh_workspace; +$S = open_window; +$target_ws = fresh_workspace; +$M = $target_ws; +cmd 'mark target'; + +cmd '[id="' . $S->{id} . '"] move container to mark target'; +sync_with_i3; + +does_i3_live; + +($nodes, $focus) = get_ws_content($target_ws); +is(@{$nodes}, 1, 'tiling container moved to the target workspace'); + +############################################################################### +# Given 'S' and 'M' where 'S' is a workspace and 'M' is a container on a +# different workspace, then all the contents of workspace 'S' end up in 'M's +# workspace. +############################################################################### + +$S = fresh_workspace; +cmd 'mark S'; +open_window; +open_window; +cmd 'splitv'; +open_window; +open_floating_window; +$target_ws = fresh_workspace; +$M = open_window; +cmd 'mark target'; + +cmd '[con_mark=S] move container to mark target'; +sync_with_i3; + +($nodes, $focus) = get_ws_content($target_ws); +is(@{$nodes}, 2, 'there is a window and a container with the contents of the original workspace'); +is($nodes->[0]->{window}, $M->{id}, 'M remains the first window'); +is(@{get_ws($target_ws)->{floating_nodes}}, 1, 'target workspace has the floating container'); + ############################################################################### done_testing; From e26fd91cf868824726d74db7388d05736e1fd7da Mon Sep 17 00:00:00 2001 From: hwangcc23 Date: Tue, 10 Apr 2018 22:20:42 +0800 Subject: [PATCH 070/218] Add an i3bar flag: --verbose Fix the issue #3220. (https://github.com/i3/i3/issues/3220) --- docs/debugging | 3 ++- i3bar/include/configuration.h | 2 +- i3bar/src/main.c | 7 ++++++- src/main.c | 3 ++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/debugging b/docs/debugging index dd26f98d..562a11f2 100644 --- a/docs/debugging +++ b/docs/debugging @@ -160,7 +160,8 @@ flood kicks. == Debugging i3bar -To debug i3bar problems, add +verbose yes+ to all +bar {}+ blocks in your i3 +To debug i3bar problems, use the +--verbose+ commandline parameter, +or add +verbose yes+ to all +bar {}+ blocks in your i3 config, reload your config and then restart all i3bar instances like this: --------------------------------------------------------------------- diff --git a/i3bar/include/configuration.h b/i3bar/include/configuration.h index e60d7483..27fb518f 100644 --- a/i3bar/include/configuration.h +++ b/i3bar/include/configuration.h @@ -47,7 +47,7 @@ typedef struct config_t { bindings; position_t position; - int verbose; + bool verbose; struct xcb_color_strings_t colors; bool disable_binding_mode_indicator; bool disable_ws; diff --git a/i3bar/src/main.c b/i3bar/src/main.c index 069803d4..f90bb312 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -62,6 +62,7 @@ void print_usage(char *elf_name) { printf("-s, --socket \tConnect to i3 via \n"); printf("-h, --help Display this help message and exit\n"); printf("-v, --version Display version number and exit\n"); + printf("-V, --verbose Enable verbose mode\n"); printf("\n"); printf(" PLEASE NOTE that i3bar will be automatically started by i3\n" " as soon as there is a 'bar' configuration block in your\n" @@ -106,9 +107,10 @@ int main(int argc, char **argv) { {"bar_id", required_argument, 0, 'b'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'}, + {"verbose", no_argument, 0, 'V'}, {NULL, 0, 0, 0}}; - while ((opt = getopt_long(argc, argv, "b:s:hv", long_opt, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "b:s:hvV", long_opt, &option_index)) != -1) { switch (opt) { case 's': socket_path = expand_path(optarg); @@ -120,6 +122,9 @@ int main(int argc, char **argv) { case 'b': config.bar_id = sstrdup(optarg); break; + case 'V': + config.verbose = true; + break; default: print_usage(argv[0]); exit(EXIT_SUCCESS); diff --git a/src/main.c b/src/main.c index 194ef05c..d5d4dcef 100644 --- a/src/main.c +++ b/src/main.c @@ -949,8 +949,9 @@ int main(int argc, char *argv[]) { Barconfig *barconfig; TAILQ_FOREACH(barconfig, &barconfigs, configs) { char *command = NULL; - sasprintf(&command, "%s --bar_id=%s --socket=\"%s\"", + sasprintf(&command, "%s %s --bar_id=%s --socket=\"%s\"", barconfig->i3bar_command ? barconfig->i3bar_command : "i3bar", + barconfig->verbose ? "-V" : "", barconfig->id, current_socketpath); LOG("Starting bar process: %s\n", command); start_application(command, true); From 1f551052de1e5a46d00d18fe5f895da9ee4fbf52 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sun, 15 Apr 2018 15:33:55 -0400 Subject: [PATCH 071/218] man: Fix title markers The title marker lines have to be aligned with the previous lines. The error was caught by asciidoctor, which tends to be picker than asciidoc. Signed-off-by: Takashi Iwai --- man/i3-input.man | 2 +- man/i3-sensible-editor.man | 2 +- man/i3-sensible-pager.man | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/man/i3-input.man b/man/i3-input.man index 07a91783..dc145914 100644 --- a/man/i3-input.man +++ b/man/i3-input.man @@ -1,5 +1,5 @@ i3-input(1) -========= +=========== Michael Stapelberg v4.1.2, April 2012 diff --git a/man/i3-sensible-editor.man b/man/i3-sensible-editor.man index 4f16d6c1..31e82ca3 100644 --- a/man/i3-sensible-editor.man +++ b/man/i3-sensible-editor.man @@ -1,5 +1,5 @@ i3-sensible-editor(1) -=================== +===================== Michael Stapelberg v4.1, November 2011 diff --git a/man/i3-sensible-pager.man b/man/i3-sensible-pager.man index 22754c0b..2339bef0 100644 --- a/man/i3-sensible-pager.man +++ b/man/i3-sensible-pager.man @@ -1,5 +1,5 @@ i3-sensible-pager(1) -=================== +==================== Michael Stapelberg v4.1, November 2011 From 0a72f2d5352732e67074a7b87f5b511bcf3eabc1 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 16 Apr 2018 01:37:18 +0300 Subject: [PATCH 072/218] i3bar: don't reset verbosity when parsing config values When i3bar is called with the -V flag but there is no 'verbose yes' directive in the bar {} config, the verbosity config value is reset. This will introduce the opposite, negligible issue: you can't disable i3bar's verbosity by deleting the 'verbose yes' directive in the bar {} config. To fix this we would need an enum for config.verbose. Closes #3220. --- i3bar/src/config.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 59a44aee..ccbe6169 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -304,8 +304,10 @@ static int config_boolean_cb(void *params_, int val) { } if (!strcmp(cur_key, "verbose")) { - DLOG("verbose = %d\n", val); - config.verbose = val; + if (!config.verbose) { + DLOG("verbose = %d\n", val); + config.verbose = val; + } return 1; } From fe6b3b74748c711be27459ad61dfe6f1909b4af2 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 16 Apr 2018 21:10:10 +0300 Subject: [PATCH 073/218] i3bar: make modifier behave like floating_modifier Pressed modifiers are determined like in click.c:handle_button_press. Fixes #3234. --- i3bar/include/configuration.h | 2 +- i3bar/src/config.c | 7 +++++ i3bar/src/xcb.c | 50 ++++++----------------------------- include/config_directives.h | 2 +- include/configuration.h | 11 +------- parser-specs/config.spec | 10 +++++-- src/config_directives.c | 23 +++------------- src/ipc.c | 27 +------------------ 8 files changed, 30 insertions(+), 102 deletions(-) diff --git a/i3bar/include/configuration.h b/i3bar/include/configuration.h index 27fb518f..b86da2e0 100644 --- a/i3bar/include/configuration.h +++ b/i3bar/include/configuration.h @@ -41,7 +41,7 @@ typedef struct tray_output_t { } tray_output_t; typedef struct config_t { - int modifier; + uint32_t modifier; TAILQ_HEAD(bindings_head, binding_t) bindings; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 59a44aee..5e57e6d9 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -119,6 +119,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len return 1; } + /* Kept for backwards compatibility. */ if (!strcmp(cur_key, "modifier")) { DLOG("modifier = %.*s\n", len, val); if (len == strlen("none") && !strncmp((const char *)val, "none", strlen("none"))) { @@ -336,6 +337,12 @@ static int config_integer_cb(void *params_, long long val) { return 1; } + if (!strcmp(cur_key, "modifier")) { + DLOG("modifier = %lld\n", val); + config.modifier = (uint32_t)val; + return 1; + } + return 0; } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 8843edbd..7bfeb12e 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -79,7 +79,7 @@ int bar_height; /* These are only relevant for XKB, which we only need for grabbing modifiers */ int xkb_base; -int mod_pressed = 0; +bool mod_pressed = 0; /* Event watchers, to interact with the user */ ev_prepare *xcb_prep; @@ -1108,49 +1108,15 @@ void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) { DLOG("received an xkb event\n"); xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event; + const uint32_t mod = (config.modifier & 0xFFFF); + mod_pressed = (mod != 0 && (state->mods & mod) == mod); if (state->xkbType == XCB_XKB_STATE_NOTIFY && config.modifier != XCB_NONE) { - int modstate = state->mods & config.modifier; - -#define DLOGMOD(modmask, status) \ - do { \ - switch (modmask) { \ - case ShiftMask: \ - DLOG("ShiftMask got " #status "!\n"); \ - break; \ - case ControlMask: \ - DLOG("ControlMask got " #status "!\n"); \ - break; \ - case Mod1Mask: \ - DLOG("Mod1Mask got " #status "!\n"); \ - break; \ - case Mod2Mask: \ - DLOG("Mod2Mask got " #status "!\n"); \ - break; \ - case Mod3Mask: \ - DLOG("Mod3Mask got " #status "!\n"); \ - break; \ - case Mod4Mask: \ - DLOG("Mod4Mask got " #status "!\n"); \ - break; \ - case Mod5Mask: \ - DLOG("Mod5Mask got " #status "!\n"); \ - break; \ - } \ - } while (0) - - if (modstate != mod_pressed) { - if (modstate == 0) { - DLOGMOD(config.modifier, released); - if (!activated_mode) - hide_bars(); - } else { - DLOGMOD(config.modifier, pressed); - activated_mode = false; - unhide_bars(); - } - mod_pressed = modstate; + if (mod_pressed) { + activated_mode = false; + unhide_bars(); + } else if (!activated_mode) { + hide_bars(); } -#undef DLOGMOD } free(event); diff --git a/include/config_directives.h b/include/config_directives.h index 852325ba..f21ad8e1 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -81,7 +81,7 @@ CFGFUN(bar_hidden_state, const char *hidden_state); CFGFUN(bar_id, const char *bar_id); CFGFUN(bar_output, const char *output); CFGFUN(bar_verbose, const char *verbose); -CFGFUN(bar_modifier, const char *modifier); +CFGFUN(bar_modifier, const char *modifiers); CFGFUN(bar_wheel_up_cmd, const char *command); CFGFUN(bar_wheel_down_cmd, const char *command); CFGFUN(bar_bindsym, const char *button, const char *release, const char *command); diff --git a/include/configuration.h b/include/configuration.h index 87897aaf..3eccca4c 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -289,16 +289,7 @@ struct Barconfig { S_SHOW = 1 } hidden_state; /** Bar modifier (to show bar when in hide mode). */ - enum { - M_NONE = 0, - M_CONTROL = 1, - M_SHIFT = 2, - M_MOD1 = 3, - M_MOD2 = 4, - M_MOD3 = 5, - M_MOD4 = 6, - M_MOD5 = 7 - } modifier; + uint32_t modifier; TAILQ_HEAD(bar_bindings_head, Barbinding) bar_bindings; diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 9cbc782b..c5c4651c 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -491,8 +491,14 @@ state BAR_ID: -> call cfg_bar_id($bar_id); BAR state BAR_MODIFIER: - modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift', 'none', 'off' - -> call cfg_bar_modifier($modifier); BAR + 'off', 'none' + -> call cfg_bar_modifier(NULL); BAR + modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl' + -> + '+' + -> + end + -> call cfg_bar_modifier($modifiers); BAR state BAR_WHEEL_UP_CMD: command = string diff --git a/src/config_directives.c b/src/config_directives.c index 491c840a..4a31f79e 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -479,25 +479,8 @@ CFGFUN(bar_verbose, const char *verbose) { current_bar->verbose = eval_boolstr(verbose); } -CFGFUN(bar_modifier, const char *modifier) { - if (strcmp(modifier, "Mod1") == 0) - current_bar->modifier = M_MOD1; - else if (strcmp(modifier, "Mod2") == 0) - current_bar->modifier = M_MOD2; - else if (strcmp(modifier, "Mod3") == 0) - current_bar->modifier = M_MOD3; - else if (strcmp(modifier, "Mod4") == 0) - current_bar->modifier = M_MOD4; - else if (strcmp(modifier, "Mod5") == 0) - current_bar->modifier = M_MOD5; - else if (strcmp(modifier, "Control") == 0 || - strcmp(modifier, "Ctrl") == 0) - current_bar->modifier = M_CONTROL; - else if (strcmp(modifier, "Shift") == 0) - current_bar->modifier = M_SHIFT; - else if (strcmp(modifier, "none") == 0 || - strcmp(modifier, "off") == 0) - current_bar->modifier = M_NONE; +CFGFUN(bar_modifier, const char *modifiers) { + current_bar->modifier = modifiers ? event_state_from_str(modifiers) : XCB_NONE; } static void bar_configure_binding(const char *button, const char *release, const char *command) { @@ -633,7 +616,7 @@ CFGFUN(bar_start) { TAILQ_INIT(&(current_bar->bar_bindings)); TAILQ_INIT(&(current_bar->tray_outputs)); current_bar->tray_padding = 2; - current_bar->modifier = M_MOD4; + current_bar->modifier = XCB_KEY_BUT_MASK_MOD_4; } CFGFUN(bar_finish) { diff --git a/src/ipc.c b/src/ipc.c index 25da1617..eebb576e 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -656,32 +656,7 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { } ystr("modifier"); - switch (config->modifier) { - case M_NONE: - ystr("none"); - break; - case M_CONTROL: - ystr("ctrl"); - break; - case M_SHIFT: - ystr("shift"); - break; - case M_MOD1: - ystr("Mod1"); - break; - case M_MOD2: - ystr("Mod2"); - break; - case M_MOD3: - ystr("Mod3"); - break; - case M_MOD5: - ystr("Mod5"); - break; - default: - ystr("Mod4"); - break; - } + y(integer, config->modifier); dump_bar_bindings(gen, config); From 7381ae3e20dc68ec4f6100f73861bf98efecd34e Mon Sep 17 00:00:00 2001 From: Adrian Cybulski Date: Wed, 18 Apr 2018 01:46:59 +0100 Subject: [PATCH 074/218] docs/ipc: update tree node with window_properties --- docs/ipc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/ipc b/docs/ipc index f011e773..e44ffd7c 100644 --- a/docs/ipc +++ b/docs/ipc @@ -328,6 +328,8 @@ window (integer):: This field is set to null for split containers or otherwise empty containers. This ID corresponds to what xwininfo(1) and other X11-related tools display (usually in hex). +window_properties (map):: + X11 window properties title, instance, class, window_role and transient_for. urgent (bool):: Whether this container (window, split container, floating container or workspace) has the urgency hint set, directly or indirectly. All parent @@ -423,6 +425,12 @@ JSON dump: "width": 1280, "height": 782 }, + "window_properties": { + "class": "Evince", + "instance": "evince", + "title": "Properties", + "transient_for": 52428808 + }, "floating_nodes": [], "nodes": [ From 0c3f5f343c8697ac20c6aca78d1661afad68734f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 19 Apr 2018 20:10:54 +0200 Subject: [PATCH 075/218] Makefile.am: add forgotten include/sync.h --- Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.am b/Makefile.am index 557c65a5..f8ae7a1b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -521,6 +521,7 @@ i3_SOURCES = \ include/shmlog.h \ include/sighandler.h \ include/startup.h \ + include/sync.h \ include/tree.h \ include/util.h \ include/window.h \ From 0254228861258084a73c38727a8b575d00e6d055 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 20 Apr 2018 06:13:40 +0300 Subject: [PATCH 076/218] Remove obsolete macro FOR_TABLE Was added in 38c8541807d50e18bf5ea61995ec6b3ab3e8a068, should have been removed in c145f7e5297ef06aaf84689762a736d5bc8cbb83. --- include/util.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/util.h b/include/util.h index 3547d8d7..d58e21b7 100644 --- a/include/util.h +++ b/include/util.h @@ -25,9 +25,6 @@ #define STARTS_WITH(string, needle) (strncasecmp((string), (needle), strlen((needle))) == 0) #define CIRCLEQ_NEXT_OR_NULL(head, elm, field) (CIRCLEQ_NEXT(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_NEXT(elm, field) : NULL) #define CIRCLEQ_PREV_OR_NULL(head, elm, field) (CIRCLEQ_PREV(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_PREV(elm, field) : NULL) -#define FOR_TABLE(workspace) \ - for (int cols = 0; cols < (workspace)->cols; cols++) \ - for (int rows = 0; rows < (workspace)->rows; rows++) #define NODES_FOREACH(head) \ for (Con *child = (Con *)-1; (child == (Con *)-1) && ((child = 0), true);) \ From 8fba5437198e007323a24c97db195b5f654d92dc Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 20 Apr 2018 13:00:44 +0300 Subject: [PATCH 077/218] Remove obsolete macro REQUIRED_OPTION Was used for the removed option 'terminal' and for 'font'. 'font' is no longer this aggressive and doesn't use the macro. Killing i3 when an option is missing would be super backwards incompatible so I doubt we are going to use this ever again. --- src/config.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/config.c b/src/config.c index 95b7ec98..de149ce7 100644 --- a/src/config.c +++ b/src/config.c @@ -191,10 +191,6 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, bindings = default_mode->bindings; -#define REQUIRED_OPTION(name) \ - if (config.name == NULL) \ - die("You did not specify required configuration option " #name "\n"); - /* Clear the old config or initialize the data structure */ memset(&config, 0, sizeof(config)); From 4869becfeee229b02b5b19b21b119794365f01e7 Mon Sep 17 00:00:00 2001 From: hwangcc23 Date: Sat, 7 Apr 2018 00:00:38 +0800 Subject: [PATCH 078/218] Make "scratchpad show" return correct info Fix the issue #3227(https://github.com/i3/i3/issues/3227). 1).Make cmd_scratchpad_show() use the information coming from scratchpad_show(). 2).Add a test case 298-scratchpad-show.t. --- include/scratchpad.h | 2 +- src/commands.c | 9 +++---- src/scratchpad.c | 16 +++++++------ testcases/t/185-scratchpad.t | 46 ++++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/include/scratchpad.h b/include/scratchpad.h index 241653c0..b24ffc08 100644 --- a/include/scratchpad.h +++ b/include/scratchpad.h @@ -29,7 +29,7 @@ void scratchpad_move(Con *con); * can press the same key to quickly look something up). * */ -void scratchpad_show(Con *con); +bool scratchpad_show(Con *con); /** * When starting i3 initially (and after each change to the connected outputs), diff --git a/src/commands.c b/src/commands.c index d6733a30..4c03ba32 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1825,19 +1825,20 @@ void cmd_move_scratchpad(I3_CMD) { void cmd_scratchpad_show(I3_CMD) { DLOG("should show scratchpad window\n"); owindow *current; + bool result = false; if (match_is_empty(current_match)) { - scratchpad_show(NULL); + result = scratchpad_show(NULL); } else { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); - scratchpad_show(current->con); + result |= scratchpad_show(current->con); } } cmd_output->needs_tree_render = true; - // XXX: default reply for now, make this a better reply - ysuccess(true); + + ysuccess(result); } /* diff --git a/src/scratchpad.c b/src/scratchpad.c index 95154014..2774396f 100644 --- a/src/scratchpad.c +++ b/src/scratchpad.c @@ -84,7 +84,7 @@ void scratchpad_move(Con *con) { * can press the same key to quickly look something up). * */ -void scratchpad_show(Con *con) { +bool scratchpad_show(Con *con) { DLOG("should show scratchpad window %p\n", con); Con *__i3_scratch = workspace_get("__i3_scratch", NULL); Con *floating; @@ -97,7 +97,7 @@ void scratchpad_show(Con *con) { floating->scratchpad_state != SCRATCHPAD_NONE) { DLOG("Focused window is a scratchpad window, hiding it.\n"); scratchpad_move(focused); - return; + return true; } /* If the current con or any of its parents are in fullscreen mode, we @@ -124,7 +124,7 @@ void scratchpad_show(Con *con) { * window inside this scratch container in order to * keep the focus the same within this container */ con_activate(con_descend_tiling_focused(walk_con)); - return; + return true; } } @@ -141,7 +141,7 @@ void scratchpad_show(Con *con) { DLOG("Found a visible scratchpad window on another workspace,\n"); DLOG("moving it to this workspace: con = %p\n", walk_con); con_move_to_workspace(walk_con, focused_ws, true, false, false); - return; + return true; } } @@ -149,7 +149,7 @@ void scratchpad_show(Con *con) { * is actually in the scratchpad */ if (con && con->parent->scratchpad_state == SCRATCHPAD_NONE) { DLOG("Window is not in the scratchpad, doing nothing.\n"); - return; + return false; } /* If this was 'scratchpad show' with criteria, we check if it matches a @@ -165,7 +165,7 @@ void scratchpad_show(Con *con) { if (current == active) { DLOG("Window is a scratchpad window, hiding it.\n"); scratchpad_move(con); - return; + return true; } } @@ -178,7 +178,7 @@ void scratchpad_show(Con *con) { if (!con) { LOG("You don't have any scratchpad windows yet.\n"); LOG("Use 'move scratchpad' to move a window to the scratchpad.\n"); - return; + return false; } } else { /* We used a criterion, so we need to do what follows (moving, @@ -206,6 +206,8 @@ void scratchpad_show(Con *con) { } con_activate(con_descend_focused(con)); + + return true; } /* diff --git a/testcases/t/185-scratchpad.t b/testcases/t/185-scratchpad.t index 147890e1..efb5d002 100644 --- a/testcases/t/185-scratchpad.t +++ b/testcases/t/185-scratchpad.t @@ -471,4 +471,50 @@ is(scalar @$nodes, 0, 'no window on current ws anymore'); is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed'); +################################################################################ +# 15: Verify that 'scratchpad show' returns correct info. +################################################################################ + +kill_all_windows; + +my $result = cmd 'scratchpad show'; +is($result->[0]->{success}, 0, 'no scratchpad window and call to scratchpad failed'); + +open_window; +cmd 'move scratchpad'; +$result = cmd 'scratchpad show'; +is($result->[0]->{success}, 1, 'call to scratchpad succeeded'); +$result = cmd 'scratchpad show'; +is($result->[0]->{success}, 1, 'call to scratchpad succeeded'); + +kill_all_windows; +$result = cmd 'scratchpad show'; +is($result->[0]->{success}, 0, 'call to scratchpad failed'); + +################################################################################ +# 16: Verify that 'scratchpad show' with the criteria returns correct info. +################################################################################ + +open_window(name => "scratch-match"); +cmd 'move scratchpad'; + +$result = cmd '[title="scratch-match"] scratchpad show'; +is($result->[0]->{success}, 1, 'call to scratchpad with the criteria succeeded'); + +$result = cmd '[title="nomatch"] scratchpad show'; +is($result->[0]->{success}, 0, 'call to scratchpad with non-matching criteria failed'); + +################################################################################ +# 17: Open a scratchpad window on a workspace, switch to another workspace and +# call 'scratchpad show' again. Verify that it returns correct info. +################################################################################ + +fresh_workspace; +open_window; +cmd 'move scratchpad'; + +fresh_workspace; +$result = cmd 'scratchpad show'; +is($result->[0]->{success}, 1, 'call to scratchpad in another workspace succeeded'); + done_testing; From bd7a5ee48a4e6acba2a885508f0b26c6f4d963c3 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 21 Apr 2018 15:21:04 +0300 Subject: [PATCH 079/218] cmd_border: improve width selection - 'border toggle' now accepts an optional pixel argument which will be ignored when switching to BS_NONE. - 'border pixel' now defaults to 1 pixel instead of 2. - Calling 'border normal' or 'border pixel' will use the configured default_border_width if one exists. Also applies to floating windows. --- docs/userguide | 8 +++-- parser-specs/commands.spec | 10 +++--- src/commands.c | 58 +++++++++++++++++++-------------- testcases/t/169-border-toggle.t | 19 +++++++++-- 4 files changed, 60 insertions(+), 35 deletions(-) diff --git a/docs/userguide b/docs/userguide index 5fc36585..e8240390 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2476,7 +2476,9 @@ To change the border of the current client, you can use +border normal+ to use t border (including window title), +border pixel 1+ to use a 1-pixel border (no window title) and +border none+ to make the client borderless. -There is also +border toggle+ which will toggle the different border styles. +There is also +border toggle+ which will toggle the different border styles. The +optional pixel argument can be used to specify the border width when switching +to the normal and pixel styles. Note that "pixel" refers to logical pixel. On HiDPI displays, a logical pixel may be represented by multiple physical pixels, so +pixel 1+ might not @@ -2484,8 +2486,8 @@ necessarily translate into a single pixel row wide border. *Syntax*: ----------------------------------------------- -border normal|pixel [] -border none|toggle +border normal|pixel|toggle [] +border none # legacy syntax, equivalent to "border pixel 1" border 1pixel diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 4048768e..106dac99 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -86,16 +86,16 @@ state DEBUGLOG: # border normal|pixel [] # border none|1pixel|toggle state BORDER: - border_style = 'normal', 'pixel' + border_style = 'normal', 'pixel', 'toggle' -> BORDER_WIDTH - border_style = 'none', 'toggle' + border_style = 'none' -> call cmd_border($border_style, 0) - border_style = '1pixel' - -> call cmd_border($border_style, 1) + '1pixel' + -> call cmd_border("pixel", 1) state BORDER_WIDTH: end - -> call cmd_border($border_style, 2) + -> call cmd_border($border_style, -1) border_width = number -> call cmd_border($border_style, &border_width) diff --git a/src/commands.c b/src/commands.c index d6733a30..e023cd8f 100644 --- a/src/commands.c +++ b/src/commands.c @@ -718,6 +718,26 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c ysuccess(success); } +static int border_width_from_style(border_style_t border_style, long border_width, Con *con) { + if (border_style == BS_NONE) { + return 0; + } + if (border_width >= 0) { + return logical_px(border_width); + } + + const bool is_floating = con_inside_floating(con) != NULL; + /* Load the configured defaults. */ + if (is_floating && border_style == config.default_floating_border) { + return config.default_floating_border_width; + } else if (!is_floating && border_style == config.default_border) { + return config.default_border_width; + } else { + /* Use some hardcoded values. */ + return logical_px(border_style == BS_NORMAL ? 2 : 1); + } +} + /* * Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'. * @@ -730,36 +750,24 @@ void cmd_border(I3_CMD, const char *border_style_str, long border_width) { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); - int border_style = current->con->border_style; - int con_border_width = border_width; + border_style_t border_style; if (strcmp(border_style_str, "toggle") == 0) { - border_style++; - border_style %= 3; - if (border_style == BS_NORMAL) - con_border_width = 2; - else if (border_style == BS_NONE) - con_border_width = 0; - else if (border_style == BS_PIXEL) - con_border_width = 1; + border_style = (current->con->border_style + 1) % 3; + } else if (strcmp(border_style_str, "normal") == 0) { + border_style = BS_NORMAL; + } else if (strcmp(border_style_str, "pixel") == 0) { + border_style = BS_PIXEL; + } else if (strcmp(border_style_str, "none") == 0) { + border_style = BS_NONE; } else { - if (strcmp(border_style_str, "normal") == 0) { - border_style = BS_NORMAL; - } else if (strcmp(border_style_str, "pixel") == 0) { - border_style = BS_PIXEL; - } else if (strcmp(border_style_str, "1pixel") == 0) { - border_style = BS_PIXEL; - con_border_width = 1; - } else if (strcmp(border_style_str, "none") == 0) { - border_style = BS_NONE; - } else { - ELOG("BUG: called with border_style=%s\n", border_style_str); - ysuccess(false); - return; - } + ELOG("BUG: called with border_style=%s\n", border_style_str); + ysuccess(false); + return; } - con_set_border_style(current->con, border_style, logical_px(con_border_width)); + const int con_border_width = border_width_from_style(border_style, border_width, current->con); + con_set_border_style(current->con, border_style, con_border_width); } cmd_output->needs_tree_render = true; diff --git a/testcases/t/169-border-toggle.t b/testcases/t/169-border-toggle.t index 51219ba6..4146fd79 100644 --- a/testcases/t/169-border-toggle.t +++ b/testcases/t/169-border-toggle.t @@ -28,7 +28,7 @@ is($nodes[0]->{border}, 'normal', 'border style normal'); cmd 'border 1pixel'; @nodes = @{get_ws_content($tmp)}; -is($nodes[0]->{border}, 'pixel', 'border style 1pixel'); +is($nodes[0]->{border}, 'pixel', 'border style pixel'); is($nodes[0]->{current_border_width}, 1, 'border width = 1px'); cmd 'border none'; @@ -48,7 +48,7 @@ is($nodes[0]->{current_border_width}, 0, 'border width = 0px'); cmd 'border toggle'; @nodes = @{get_ws_content($tmp)}; -is($nodes[0]->{border}, 'pixel', 'border style 1pixel'); +is($nodes[0]->{border}, 'pixel', 'border style pixel'); is($nodes[0]->{current_border_width}, 1, 'border width = 1px'); cmd 'border toggle'; @@ -56,4 +56,19 @@ cmd 'border toggle'; is($nodes[0]->{border}, 'normal', 'border style back to normal'); is($nodes[0]->{current_border_width}, 2, 'border width = 2px'); +cmd 'border toggle 10'; +@nodes = @{get_ws_content($tmp)}; +is($nodes[0]->{border}, 'none', 'border style back to none even with width argument'); +is($nodes[0]->{current_border_width}, 0, 'border width = 0px'); + +cmd 'border toggle 10'; +@nodes = @{get_ws_content($tmp)}; +is($nodes[0]->{border}, 'pixel', 'border style pixel'); +is($nodes[0]->{current_border_width}, 10, 'border width = 10px'); + +cmd 'border toggle 10'; +@nodes = @{get_ws_content($tmp)}; +is($nodes[0]->{border}, 'normal', 'border style back to normal'); +is($nodes[0]->{current_border_width}, 10, 'border width = 10px'); + done_testing; From 799e3951a2fbf71ae6d8a584e13295e7495cc03b Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 22 Apr 2018 03:48:50 +0300 Subject: [PATCH 080/218] con_swap: exit when first _con_move_to_con fails This is enough to fix the crash discussed in #3259 even though the next commit can fix it independently. This commit is useful because it generally makes sense to abort the command when the first call to _con_move_to_con fails. --- src/con.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/con.c b/src/con.c index deb65558..b7168d41 100644 --- a/src/con.c +++ b/src/con.c @@ -2373,6 +2373,10 @@ bool con_swap(Con *first, Con *second) { /* Move first to second. */ result &= _con_move_to_con(first, second, false, false, false, true, false); + /* If swapping the containers didn't work we don't need to mess with the focus. */ + if (!result) { + goto swap_end; + } /* If we moved the container holding the focused window to another * workspace we need to ensure the visible workspace has the focused @@ -2385,8 +2389,6 @@ bool con_swap(Con *first, Con *second) { /* Move second to where first has been originally. */ result &= _con_move_to_con(second, fake, false, false, false, true, false); - - /* If swapping the containers didn't work we don't need to mess with the focus. */ if (!result) { goto swap_end; } From b5f887287a9d350910305bdc06f32218c86dd2ef Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 22 Apr 2018 03:26:51 +0300 Subject: [PATCH 081/218] Don't call con_fullscreen_permits_focusing with ignore_focus When we don't modify the focus we aren't risking giving focus to a container behind the current fullscreen one. _con_move_to_con can with ignore_focus is called through the swap command or through con_move_to_workspace for floating containers. This change shouldn't break the expectations of the callers there. Fixes issue #3259. --- src/con.c | 2 +- testcases/t/291-swap.t | 31 ++++++++++++++++++------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/con.c b/src/con.c index b7168d41..f20f7c35 100644 --- a/src/con.c +++ b/src/con.c @@ -1097,7 +1097,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* Prevent moving if this would violate the fullscreen focus restrictions. */ Con *target_ws = con_get_workspace(target); - if (!con_fullscreen_permits_focusing(target_ws)) { + if (!ignore_focus && !con_fullscreen_permits_focusing(target_ws)) { LOG("Cannot move out of a fullscreen container.\n"); return false; } diff --git a/testcases/t/291-swap.t b/testcases/t/291-swap.t index 3b61fdab..da2d564d 100644 --- a/testcases/t/291-swap.t +++ b/testcases/t/291-swap.t @@ -372,26 +372,31 @@ for my $fullscreen (@fullscreen_permutations){ # +---+---+ Layout: H2[ B, F ] # | B | F | Focus Stacks: # +---+---+ H2: F, B +# +# See issue: #3259 ############################################################################### -$ws1 = fresh_workspace; -$A = open_window(wm_class => 'mark_A'); +for my $fullscreen (0..1){ + $ws1 = fresh_workspace; + $A = open_window(wm_class => 'mark_A'); -$ws2 = fresh_workspace; -$B = open_window(wm_class => 'mark_B'); -open_window; -$expected_focus = get_focused($ws2); + $ws2 = fresh_workspace; + $B = open_window(wm_class => 'mark_B'); + open_window; + cmd 'fullscreen enable' if $fullscreen; + $expected_focus = get_focused($ws2); -cmd '[con_mark=B] swap container with mark A'; + cmd '[con_mark=B] swap container with mark A'; -$nodes = get_ws_content($ws1); -is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace'); + $nodes = get_ws_content($ws1); + is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace'); -$nodes = get_ws_content($ws2); -is($nodes->[0]->{window}, $A->{id}, 'A is on the left of the second workspace'); -is(get_focused($ws2), $expected_focus, 'F is still focused'); + $nodes = get_ws_content($ws2); + is($nodes->[0]->{window}, $A->{id}, 'A is on the left of the second workspace'); + is(get_focused($ws2), $expected_focus, 'F is still focused'); -kill_all_windows; + kill_all_windows; +} ############################################################################### # 1. A container cannot be swapped with its parent. From 2f2053284e8e2747f9b8980f5d5d991de1eec578 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Thu, 26 Apr 2018 23:47:45 +0300 Subject: [PATCH 082/218] cmd_shmlog: use parse_long() --- src/commands.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/commands.c b/src/commands.c index 6083c651..98e10c1d 100644 --- a/src/commands.c +++ b/src/commands.c @@ -2164,21 +2164,22 @@ void cmd_shmlog(I3_CMD, const char *argument) { else if (!strcmp(argument, "off")) shmlog_size = 0; else { + long new_size = 0; + if (!parse_long(argument, &new_size, 0)) { + yerror("Failed to parse %s into a shmlog size.\n", argument); + return; + } /* If shm logging now, restart logging with the new size. */ if (shmlog_size > 0) { shmlog_size = 0; LOG("Restarting shm logging...\n"); init_logging(); } - shmlog_size = atoi(argument); - /* Make a weakly attempt at ensuring the argument is valid. */ - if (shmlog_size <= 0) - shmlog_size = default_shmlog_size; + shmlog_size = (int)new_size; } LOG("%s shm logging\n", shmlog_size > 0 ? "Enabling" : "Disabling"); init_logging(); update_shmlog_atom(); - // XXX: default reply for now, make this a better reply ysuccess(true); } From aca7790217f6bac58e4588a5b80fcd41de781c8e Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Thu, 26 Apr 2018 23:58:42 +0300 Subject: [PATCH 083/218] Fix redundant return statements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … at the end of a function with a void return type. Found using clang-tidy's readability-redundant-control-flow. --- i3-config-wizard/main.c | 2 -- i3bar/src/xcb.c | 1 - src/handlers.c | 9 --------- src/manage.c | 1 - 4 files changed, 13 deletions(-) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index 7f9f2d64..f3c33034 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -631,8 +631,6 @@ static void handle_button_press(xcb_button_press_event_t *event) { modifier = MOD_Mod1; handle_expose(); } - - return; } /* diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 7bfeb12e..800c05d4 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -2067,5 +2067,4 @@ void set_current_mode(struct mode *current) { I3STRING_FREE(binding.name); binding = *current; activated_mode = binding.name != NULL; - return; } diff --git a/src/handlers.c b/src/handlers.c index 15c05a8f..38c2caf1 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -184,8 +184,6 @@ static void handle_enter_notify(xcb_enter_notify_event_t *event) { focused_id = XCB_NONE; con_focus(con_descend_focused(con)); tree_render(); - - return; } /* @@ -249,8 +247,6 @@ static void handle_mapping_notify(xcb_mapping_notify_event_t *event) { ungrab_all_keys(conn); translate_keysyms(); grab_all_keys(conn); - - return; } /* @@ -266,7 +262,6 @@ static void handle_map_request(xcb_map_request_event_t *event) { add_ignore_event(event->sequence, -1); manage_window(event->window, cookie, false); - return; } /* @@ -474,8 +469,6 @@ static void handle_screen_change(xcb_generic_event_t *e) { scratchpad_fix_resolution(); ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}"); - - return; } /* @@ -659,7 +652,6 @@ static void handle_expose_event(xcb_expose_event_t *event) { draw_util_copy_surface(&(parent->frame_buffer), &(parent->frame), 0, 0, 0, 0, parent->rect.width, parent->rect.height); xcb_flush(conn); - return; } #define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 @@ -1243,7 +1235,6 @@ static void handle_focus_in(xcb_focus_in_event_t *event) { /* We update focused_id because we don’t need to set focus again */ focused_id = event->event; tree_render(); - return; } /* diff --git a/src/manage.c b/src/manage.c index 9dcc93f5..d591df15 100644 --- a/src/manage.c +++ b/src/manage.c @@ -664,5 +664,4 @@ geom_out: free(geom); out: free(attr); - return; } From 0b5a2092a08f7e4463b5d52e11d84750af84e73c Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 27 Apr 2018 00:08:58 +0300 Subject: [PATCH 084/218] Fix redundant casts to the same type Found using clang-tidy's google-readability-casting. --- i3-nagbar/main.c | 2 +- libi3/ucs2_conversion.c | 3 +-- src/startup.c | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index e4628e30..3e5f43e3 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -115,7 +115,7 @@ static void start_application(const char *command) { setsid(); if (fork() == 0) { /* This is the child */ - execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void *)NULL); + execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL); /* not reached */ } exit(0); diff --git a/libi3/ucs2_conversion.c b/libi3/ucs2_conversion.c index 75cff78a..398c1ae5 100644 --- a/libi3/ucs2_conversion.c +++ b/libi3/ucs2_conversion.c @@ -83,8 +83,7 @@ xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen) { } /* Do the conversion */ - size_t rc = iconv(ucs2_conversion_descriptor, (char **)&input, - &input_size, (char **)&output, &output_size); + size_t rc = iconv(ucs2_conversion_descriptor, &input, &input_size, (char **)&output, &output_size); if (rc == (size_t)-1) { perror("Converting to UCS-2 failed"); free(buffer); diff --git a/src/startup.c b/src/startup.c index 1e733fcb..b1a2f602 100644 --- a/src/startup.c +++ b/src/startup.c @@ -191,7 +191,7 @@ void start_application(const char *command, bool no_startup_id) { if (!no_startup_id) sn_launcher_context_setup_child_process(context); - execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void *)NULL); + execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL); /* not reached */ } _exit(0); From 16f8fe28d920ac7b69a002983e4404703b4d3f00 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 27 Apr 2018 01:16:42 +0300 Subject: [PATCH 085/218] main.c: remove redundant 'focused' declaration Previously declared in tree.h:17. --- src/main.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main.c b/src/main.c index d5d4dcef..de54dbce 100644 --- a/src/main.c +++ b/src/main.c @@ -39,8 +39,6 @@ int listen_fds; * temporarily for drag_pointer(). */ static struct ev_prepare *xcb_prepare; -extern Con *focused; - char **start_argv; xcb_connection_t *conn; From 1681ab4496c6053a651e93119638336776466f6d Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 28 Apr 2018 12:21:39 +0300 Subject: [PATCH 086/218] Define ADD_TRANSLATED_KEY once --- src/bindings.c | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 1ec41920..697c1e61 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -361,6 +361,14 @@ struct resolve { struct xkb_state *xkb_state_numlock_no_shift; }; +#define ADD_TRANSLATED_KEY(code, mods) \ + do { \ + struct Binding_Keycode *binding_keycode = smalloc(sizeof(struct Binding_Keycode)); \ + binding_keycode->modifiers = (mods); \ + binding_keycode->keycode = (code); \ + TAILQ_INSERT_TAIL(&(bind->keycodes_head), binding_keycode, keycodes); \ + } while (0) + /* * add_keycode_if_matches is called for each keycode in the keymap and will add * the keycode to |data->bind| if the keycode can result in the keysym @@ -390,18 +398,10 @@ static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key, } Binding *bind = resolving->bind; -#define ADD_TRANSLATED_KEY(mods) \ - do { \ - struct Binding_Keycode *binding_keycode = smalloc(sizeof(struct Binding_Keycode)); \ - binding_keycode->modifiers = (mods); \ - binding_keycode->keycode = key; \ - TAILQ_INSERT_TAIL(&(bind->keycodes_head), binding_keycode, keycodes); \ - } while (0) - - ADD_TRANSLATED_KEY(bind->event_state_mask); + ADD_TRANSLATED_KEY(key, bind->event_state_mask); /* Also bind the key with active CapsLock */ - ADD_TRANSLATED_KEY(bind->event_state_mask | XCB_MOD_MASK_LOCK); + ADD_TRANSLATED_KEY(key, bind->event_state_mask | XCB_MOD_MASK_LOCK); /* If this binding is not explicitly for NumLock, check whether we need to * add a fallback. */ @@ -413,17 +413,15 @@ static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key, xkb_keysym_t sym_numlock = xkb_state_key_get_one_sym(numlock_state, key); if (sym_numlock == resolving->keysym) { /* Also bind the key with active NumLock */ - ADD_TRANSLATED_KEY(bind->event_state_mask | xcb_numlock_mask); + ADD_TRANSLATED_KEY(key, bind->event_state_mask | xcb_numlock_mask); /* Also bind the key with active NumLock+CapsLock */ - ADD_TRANSLATED_KEY(bind->event_state_mask | xcb_numlock_mask | XCB_MOD_MASK_LOCK); + ADD_TRANSLATED_KEY(key, bind->event_state_mask | xcb_numlock_mask | XCB_MOD_MASK_LOCK); } else { DLOG("Skipping automatic numlock fallback, key %d resolves to 0x%x with numlock\n", key, sym_numlock); } } - -#undef ADD_TRANSLATED_KEY } /* @@ -447,14 +445,6 @@ void translate_keysyms(void) { Binding *bind; TAILQ_FOREACH(bind, bindings, bindings) { -#define ADD_TRANSLATED_KEY(code, mods) \ - do { \ - struct Binding_Keycode *binding_keycode = smalloc(sizeof(struct Binding_Keycode)); \ - binding_keycode->modifiers = (mods); \ - binding_keycode->keycode = (code); \ - TAILQ_INSERT_TAIL(&(bind->keycodes_head), binding_keycode, keycodes); \ - } while (0) - if (bind->input_type == B_MOUSE) { long button; if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) { @@ -605,8 +595,6 @@ void translate_keysyms(void) { DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n", bind->event_state_mask, bind->symbol, keysym, keycodes, num_keycodes); free(keycodes); - -#undef ADD_TRANSLATED_KEY } out: @@ -620,6 +608,8 @@ out: } } +#undef ADD_TRANSLATED_KEY + /* * Switches the key bindings to the given mode, if the mode exists * From 6f11b6fa4a784e85a1b74cb7ff74058023ce30aa Mon Sep 17 00:00:00 2001 From: Orestis Date: Sat, 28 Apr 2018 15:47:28 +0300 Subject: [PATCH 087/218] send_tick: set "first" field (#3271) According to the docs, the tick event should return: { "first": false, "payload": "arbitrary string" } --- src/ipc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index eebb576e..2ac09d7d 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -1127,6 +1127,9 @@ IPC_HANDLER(send_tick) { y(map_open); + ystr("first"); + y(bool, false); + ystr("payload"); yajl_gen_string(gen, (unsigned char *)message, message_size); From 1f74f8d2c188a39c2c8c46b01921e6569cb4fc81 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 30 Apr 2018 03:54:38 +0300 Subject: [PATCH 088/218] Fix: ConfigureNotify can crash i3 with fake-outputs handle_screen_change() and handle_configure_notify() call randr_query_outputs() where root_output is not initialized because randr_init() is never called when config.fake_outputs is not NULL. --- src/randr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/randr.c b/src/randr.c index d7e1acca..d4d7402a 100644 --- a/src/randr.c +++ b/src/randr.c @@ -856,8 +856,9 @@ void randr_query_outputs(void) { /* If there's no randr output, enable the output covering the root window. */ if (any_randr_output_active()) { DLOG("Active RandR output found. Disabling root output.\n"); - if (root_output->active) + if (root_output && root_output->active) { root_output->to_be_disabled = true; + } } else { DLOG("No active RandR output found. Enabling root output.\n"); root_output->active = true; From 252db3b8cfad6aa3c9cf22f51eb2a585ae69c791 Mon Sep 17 00:00:00 2001 From: Oliver Graff Date: Tue, 1 May 2018 04:25:13 -0400 Subject: [PATCH 089/218] Don't refocus a workspace cleaned up by `workspace_show` during rename When moving a workspace to the current output by way of a rename, if the current workspace is empty, it will be removed by `workspace_show`. Attempting to restore focus to this removed workspace causes a crash. Follow the pattern in workspace.c:996 to only restore the original focus if the original workspace still exists. Add a test to ensure that the renamed workspace moves to its appropriate output and that a crash does not occur. Fixes #3228 --- src/commands.c | 22 +++++++++++++++++---- testcases/t/522-rename-assigned-workspace.t | 12 +++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/commands.c b/src/commands.c index 98e10c1d..0c5b8bcb 100644 --- a/src/commands.c +++ b/src/commands.c @@ -2000,6 +2000,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { /* By re-attaching, the sort order will be correct afterwards. */ Con *previously_focused = focused; + Con *previously_focused_content = focused->type == CT_WORKSPACE ? focused->parent : NULL; Con *parent = workspace->parent; con_detach(workspace); con_attach(workspace, parent, false); @@ -2020,15 +2021,28 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { } workspace_move_to_output(workspace, target_output); - if (previously_focused) + bool can_restore_focus = previously_focused != NULL; + /* NB: If previously_focused is a workspace we can't + * work directly with it since it might have been cleaned up by + * workspace_show() already, depending on the + * focus order/number of other workspaces on the output. + * Instead, we loop through the available workspaces and only focus + * previously_focused if we still find it. */ + if (previously_focused_content) { + Con *workspace = NULL; + GREP_FIRST(workspace, previously_focused_content, child == previously_focused); + can_restore_focus &= (workspace != NULL); + } + + if (can_restore_focus) { + /* Restore the previous focus since con_attach messes with the focus. */ workspace_show(con_get_workspace(previously_focused)); + con_activate(previously_focused); + } break; } - /* Restore the previous focus since con_attach messes with the focus. */ - con_activate(previously_focused); - cmd_output->needs_tree_render = true; ysuccess(true); diff --git a/testcases/t/522-rename-assigned-workspace.t b/testcases/t/522-rename-assigned-workspace.t index 8a6edc85..5c9f2ff3 100644 --- a/testcases/t/522-rename-assigned-workspace.t +++ b/testcases/t/522-rename-assigned-workspace.t @@ -94,4 +94,16 @@ cmd 'rename workspace to 5'; is(get_output_for_workspace('5'), 'fake-0', 'Renaming the workspace to a workspace assigned to a directional output should not move the workspace'); +########################################################################## +# Renaming a workspace, so that it becomes assigned to the focused +# output's workspace (and the focused output is empty) should +# result in the original workspace replacing the originally +# focused workspace. +########################################################################## + +cmd 'workspace baz'; +cmd 'rename workspace 5 to 2'; +is(get_output_for_workspace('2'), 'fake-1', + 'Renaming a workspace so that it moves to the focused output which contains only an empty workspace should replace the empty workspace'); + done_testing; From 37e3663c77d68a23f0dc8b6a6b26fb0a29bef809 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 1 May 2018 13:49:55 +0300 Subject: [PATCH 090/218] Fix DEPENDS table - Align right border - Add a missing '/' in libsn's and util-xrm's link for consistency - Replace wrong character for border next to pango's min version - Correct the Pod::Simple link --- DEPENDS | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/DEPENDS b/DEPENDS index 1e26afa2..9a92f99e 100644 --- a/DEPENDS +++ b/DEPENDS @@ -4,29 +4,29 @@ "min" means minimum required version "lkgv" means last known good version -┌──────────────┬────────┬────────┬───────────────────────────────────────────────────────────┐ -│ dependency │ min. │ lkgv │ URL │ -├──────────────┼────────┼────────┼───────────────────────────────────────────────────────────┤ -│ pkg-config │ 0.25 │ 0.29 │ https://pkgconfig.freedesktop.org/ │ -│ libxcb │ 1.1.93 │ 1.12 │ https://xcb.freedesktop.org/dist/ │ -│ xcb-util │ 0.3.3 │ 0.4.1 │ https://xcb.freedesktop.org/dist/ │ -│ xkbcommon │ 0.4.0 │ 0.6.1 │ https://xkbcommon.org/ │ -│ xkbcommon-x11│ 0.4.0 │ 0.6.1 │ https://xkbcommon.org/ │ -│ util-cursor³⁴│ 0.0.99 │ 0.1.3 │ https://xcb.freedesktop.org/dist/ │ -│ util-wm⁴ │ 0.3.8 │ 0.3.8 │ https://xcb.freedesktop.org/dist/ │ -│ util-keysyms⁴│ 0.3.8 │ 0.4.0 │ https://xcb.freedesktop.org/dist/ │ -│ util-xrm⁴ │ 1.0.0 │ 1.0.0 │ https://github.com/Airblader/xcb-util-xrm │ -│ libev │ 4.0 │ 4.19 │ http://libev.schmorp.de/ │ -│ yajl │ 2.0.1 │ 2.1.0 │ https://lloyd.github.com/yajl/ │ -│ asciidoc │ 8.3.0 │ 8.6.9 │ http://www.methods.co.nz/asciidoc/ │ -│ xmlto │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/ │ -│ Pod::Simple² │ 3.22 │ 3.22 │ http://search.cpan.org/~dwheeler/Pod-Simple-3.23/ │ -│ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │ -│ PCRE │ 8.12 │ 8.38 │ https://www.pcre.org/ │ -│ libsn¹ │ 0.10 │ 0.12 │ https://freedesktop.org/wiki/Software/startup-notification │ -│ pango │ 1.30.0 | 1.40.1 │ http://www.pango.org/ │ -│ cairo │ 1.14.4 │ 1.14.6 │ https://cairographics.org/ │ -└──────────────┴────────┴────────┴───────────────────────────────────────────────────────────┘ +┌──────────────┬────────┬────────┬─────────────────────────────────────────────────────────────┐ +│ dependency │ min. │ lkgv │ URL │ +├──────────────┼────────┼────────┼─────────────────────────────────────────────────────────────┤ +│ pkg-config │ 0.25 │ 0.29 │ https://pkgconfig.freedesktop.org/ │ +│ libxcb │ 1.1.93 │ 1.12 │ https://xcb.freedesktop.org/dist/ │ +│ xcb-util │ 0.3.3 │ 0.4.1 │ https://xcb.freedesktop.org/dist/ │ +│ xkbcommon │ 0.4.0 │ 0.6.1 │ https://xkbcommon.org/ │ +│ xkbcommon-x11│ 0.4.0 │ 0.6.1 │ https://xkbcommon.org/ │ +│ util-cursor³⁴│ 0.0.99 │ 0.1.3 │ https://xcb.freedesktop.org/dist/ │ +│ util-wm⁴ │ 0.3.8 │ 0.3.8 │ https://xcb.freedesktop.org/dist/ │ +│ util-keysyms⁴│ 0.3.8 │ 0.4.0 │ https://xcb.freedesktop.org/dist/ │ +│ util-xrm⁴ │ 1.0.0 │ 1.0.0 │ https://github.com/Airblader/xcb-util-xrm/ │ +│ libev │ 4.0 │ 4.19 │ http://libev.schmorp.de/ │ +│ yajl │ 2.0.1 │ 2.1.0 │ https://lloyd.github.com/yajl/ │ +│ asciidoc │ 8.3.0 │ 8.6.9 │ http://www.methods.co.nz/asciidoc/ │ +│ xmlto │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/ │ +│ Pod::Simple² │ 3.22 │ 3.22 │ http://search.cpan.org/dist/Pod-Simple/ │ +│ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │ +│ PCRE │ 8.12 │ 8.38 │ https://www.pcre.org/ │ +│ libsn¹ │ 0.10 │ 0.12 │ https://freedesktop.org/wiki/Software/startup-notification/ │ +│ pango │ 1.30.0 │ 1.40.1 │ http://www.pango.org/ │ +│ cairo │ 1.14.4 │ 1.14.6 │ https://cairographics.org/ │ +└──────────────┴────────┴────────┴─────────────────────────────────────────────────────────────┘ ¹ libsn = libstartup-notification ² Pod::Simple is a Perl module required for converting the testsuite documentation to HTML. See https://michael.stapelberg.de/cpan/#Pod::Simple From 64b8b4b7663246f14daa862355b4367251eb5e53 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 2 May 2018 18:06:48 +0300 Subject: [PATCH 091/218] 252-floating-size.t: Reduce code duplication --- testcases/t/252-floating-size.t | 77 +++++++++++++-------------------- 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/testcases/t/252-floating-size.t b/testcases/t/252-floating-size.t index 2c8edf39..d3c2269b 100644 --- a/testcases/t/252-floating-size.t +++ b/testcases/t/252-floating-size.t @@ -26,76 +26,57 @@ workspace ws output fake-0 EOT ################################################################################ -# Check that setting floating windows size works +# Init variables used for all tests. ################################################################################ my $tmp = fresh_workspace; - open_floating_window; - my @content = @{get_ws($tmp)->{floating_nodes}}; is(@content, 1, 'one floating node on this ws'); - my $oldrect = $content[0]->{rect}; -cmd 'resize set 100 px 250 px'; +sub do_test { + my ($width, $height) = @_; -@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}, '==', 100, 'width changed to 100 px'); -cmp_ok($content[0]->{rect}->{height}, '==', 250, 'height changed to 250 px'); + cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x unchanged'); + cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y unchanged'); + + @content = @{get_ws($tmp)->{floating_nodes}}; + if ($width) { + cmp_ok($content[0]->{rect}->{width}, '==', $width, "width changed to $width px"); + } else { + cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'width unchanged'); + } + if ($height) { + cmp_ok($content[0]->{rect}->{height}, '==', $height, "height changed to $height px"); + } else { + cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height}, 'height unchanged'); + } + $oldrect = $content[0]->{rect}; +} + +################################################################################ +# Check that setting floating windows size works +################################################################################ + +cmd 'resize set 100 px 250 px'; +do_test(100, 250); ################################################################################ # 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"); +do_test(int(0.33 * 1333), int(0.2 * 999)); ################################################################################ # Mix ppt and px in a single resize set command ################################################################################ cmd 'resize set 44 ppt 111 px'; -$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'); -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"); +do_test(int(0.44 * 1333), 111); cmd 'resize set 222 px 100 ppt'; -$expected_width = 222; -$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"); +do_test(222, 999); done_testing; From b901fc94649a8004caf45f721714bd804f5969b1 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 2 May 2018 17:43:43 +0300 Subject: [PATCH 092/218] resize set for floating: interpret 0 as 'no change' Fixes #3276 --- src/commands.c | 4 ++-- testcases/t/252-floating-size.t | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/commands.c b/src/commands.c index 0c5b8bcb..2d5ef362 100644 --- a/src/commands.c +++ b/src/commands.c @@ -644,12 +644,12 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c if ((floating_con = con_inside_floating(current->con))) { Con *output = con_get_output(floating_con); if (cwidth == 0) { - cwidth = output->rect.width; + cwidth = floating_con->rect.width; } else if (mode_width && strcmp(mode_width, "ppt") == 0) { cwidth = output->rect.width * ((double)cwidth / 100.0); } if (cheight == 0) { - cheight = output->rect.height; + cheight = floating_con->rect.height; } else if (mode_height && strcmp(mode_height, "ppt") == 0) { cheight = output->rect.height * ((double)cheight / 100.0); } diff --git a/testcases/t/252-floating-size.t b/testcases/t/252-floating-size.t index d3c2269b..7875cb07 100644 --- a/testcases/t/252-floating-size.t +++ b/testcases/t/252-floating-size.t @@ -79,4 +79,21 @@ do_test(int(0.44 * 1333), 111); cmd 'resize set 222 px 100 ppt'; do_test(222, 999); +################################################################################ +# Zero is interpreted as no change. +# See issue: #3276. +################################################################################ + +cmd 'resize set 0 px 333 px'; +do_test(0, 333); + +cmd 'resize set 333 px 0 ppt'; +do_test(333, 0); + +cmd 'resize set 0 px 0 ppt'; +do_test(0, 0); + +cmd 'resize set 100 ppt 0 px'; +do_test(1333, 0); + done_testing; From c50bf50f09cb428c94ffacd5e37f7a5a47c01248 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 2 May 2018 18:59:17 +0300 Subject: [PATCH 093/218] resize set: accept 'width' and 'height' keywords Fixes #3275 --- docs/userguide | 4 +++- parser-specs/commands.spec | 12 ++++++++++++ src/commands.c | 2 +- testcases/t/252-floating-size.t | 18 ++++++++++++++++++ testcases/t/541-resize-set-tiling.t | 15 +++++++++++++++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index e8240390..a8b35f3e 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2320,7 +2320,9 @@ If you want to resize containers/windows using your keyboard, you can use the *Syntax*: ------------------------------------------------------- resize grow|shrink [ px [or ppt]] -resize set [px | ppt] [px | ppt] +resize set [width] [px | ppt] +resize set height [px | ppt] +resize set [width] [px | ppt] [height] [px | ppt] ------------------------------------------------------- Direction can either be one of +up+, +down+, +left+ or +right+. Or you can be diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 106dac99..c0c32933 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -254,12 +254,24 @@ state RESIZE_TILING_FINAL: -> call cmd_resize($way, $direction, &resize_px, &resize_ppt) state RESIZE_SET: + 'height' + -> RESIZE_HEIGHT_GET_NUMBER + 'width' + -> width = number -> RESIZE_WIDTH state RESIZE_WIDTH: mode_width = 'px', 'ppt' -> + end + -> call cmd_resize_set(&width, $mode_width, 0, 0) + 'height' + -> RESIZE_HEIGHT_GET_NUMBER + height = number + -> RESIZE_HEIGHT + +state RESIZE_HEIGHT_GET_NUMBER: height = number -> RESIZE_HEIGHT diff --git a/src/commands.c b/src/commands.c index 2d5ef362..c7b57ab3 100644 --- a/src/commands.c +++ b/src/commands.c @@ -686,7 +686,7 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c } } - if (cheight > 0 && mode_width && strcmp(mode_width, "ppt") == 0) { + if (cheight > 0 && mode_height && strcmp(mode_height, "ppt") == 0) { /* get the appropriate current container (skip stacked/tabbed cons) */ Con *target = current->con; Con *dummy; diff --git a/testcases/t/252-floating-size.t b/testcases/t/252-floating-size.t index 7875cb07..5c746de9 100644 --- a/testcases/t/252-floating-size.t +++ b/testcases/t/252-floating-size.t @@ -96,4 +96,22 @@ do_test(0, 0); cmd 'resize set 100 ppt 0 px'; do_test(1333, 0); +################################################################################ +# Use 'width' and 'height' keywords. +# See issue: #3275. +################################################################################ + +cmd 'resize set width 200 px'; +do_test(200, 0); + +cmd 'resize set height 200 px'; +do_test(0, 200); + +cmd 'resize set width 300 px height 300 px'; +do_test(300, 300); + +# ppt + keyword used only for height +cmd 'resize set 100 ppt height 100 px'; +do_test(1333, 100); + done_testing; diff --git a/testcases/t/541-resize-set-tiling.t b/testcases/t/541-resize-set-tiling.t index 82267baf..fcf3267a 100644 --- a/testcases/t/541-resize-set-tiling.t +++ b/testcases/t/541-resize-set-tiling.t @@ -39,6 +39,14 @@ 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%'); +# Same but use the 'width' keyword. +cmd 'resize set width 80 ppt'; + +($nodes, $focus) = get_ws_content($tmp); + +cmp_float($nodes->[0]->{percent}, 0.20, 'left window got 20%'); +cmp_float($nodes->[1]->{percent}, 0.80, 'right window got 80%'); + ############################################################ # resize vertically ############################################################ @@ -61,6 +69,13 @@ 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%'); +# Same but use the 'height' keyword. +cmd 'resize set height 80 ppt'; + +($nodes, $focus) = get_ws_content($tmp); + +cmp_float($nodes->[0]->{percent}, 0.20, 'top window got 20%'); +cmp_float($nodes->[1]->{percent}, 0.80, 'bottom window got 80%'); ############################################################ # resize horizontally and vertically From 94bc40168014b50c4f72e7e0224d959296749e70 Mon Sep 17 00:00:00 2001 From: Dan Elkouby Date: Sat, 5 May 2018 13:30:48 +0300 Subject: [PATCH 094/218] Activate the focused child when scrolling over tab/stack decorations fbce834b introduced a bug where scrolling over the decoration while another container is focused would not focus the tabbed/stacked container itself, but would instead move focus through the currently focused container. --- src/click.c | 1 + testcases/t/297-scroll-tabbed.t | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/click.c b/src/click.c index c2b38d4d..81e95dfa 100644 --- a/src/click.c +++ b/src/click.c @@ -235,6 +235,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod * container the user scrolled on. */ Con *focused = con->parent; focused = TAILQ_FIRST(&(focused->focus_head)); + con_activate(con_descend_focused(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); diff --git a/testcases/t/297-scroll-tabbed.t b/testcases/t/297-scroll-tabbed.t index 6deeaf65..b535d0cd 100644 --- a/testcases/t/297-scroll-tabbed.t +++ b/testcases/t/297-scroll-tabbed.t @@ -53,7 +53,7 @@ open_window; cmd 'splitv'; my $last = open_window; # Second child of the outer horizontal split, next to the tabbed one. -open_window; +my $outside = open_window; cmd 'move right, move right'; cmd '[id=' . $first->id . '] focus'; @@ -76,4 +76,9 @@ is($x->input_focus, $first->id, 'First window focused through scrolling'); scroll_up; is($x->input_focus, $first->id, 'Scrolling again doesn\'t focus the whole sibling'); +# Try scrolling with another window focused +cmd '[id=' . $outside->id . '] focus'; +scroll_up; +is($x->input_focus, $first->id, 'Scrolling from outside the tabbed container works'); + done_testing; From f4981f97bcd1c28fcc64f002ac89e5011870582f Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 8 May 2018 17:05:46 +0300 Subject: [PATCH 095/218] Replace strncpy call with memcpy when result is not NUL-terminated This fixes a new warning from GCC 8.1, -Wstringop-truncation: https://gcc.gnu.org/gcc-8/changes.html https://gcc.gnu.org/onlinedocs/gcc-8.1.0/gcc/Warning-Options.html#index-Wstringop-truncation Replacing with memcpy is what gcc suggests: > As another example, the following call to strncpy results in copying > to d just the characters preceding the terminating NUL, without > appending the NUL to the end. Assuming the result of strncpy is > necessarily a NUL-terminated string is a common mistake, and so the > call is diagnosed. To avoid the warning when the result is not > expected to be NUL-terminated, call memcpy instead. > void copy (char *d, const char *s) > { > strncpy (d, s, strlen (s)); > } --- i3bar/src/ipc.c | 2 +- i3bar/src/xcb.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 7a657338..56fe2798 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -309,7 +309,7 @@ int i3_send_msg(uint32_t type, const char *payload) { char *buffer = smalloc(to_write); char *walk = buffer; - strncpy(buffer, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)); + memcpy(buffer, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)); walk += strlen(I3_IPC_MAGIC); memcpy(walk, &len, sizeof(uint32_t)); walk += sizeof(uint32_t); diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 800c05d4..ae6c0abc 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -604,7 +604,7 @@ void handle_button(xcb_button_press_event_t *event) { const size_t len = namelen + strlen("workspace \"\"") + 1; char *buffer = scalloc(len + num_quotes, 1); - strncpy(buffer, "workspace \"", strlen("workspace \"")); + memcpy(buffer, "workspace \"", strlen("workspace \"")); size_t inpos, outpos; for (inpos = 0, outpos = strlen("workspace \""); inpos < namelen; From 26f50898fd72c22a27e5b12db83808161bc3d9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 20 May 2018 16:51:26 +0200 Subject: [PATCH 096/218] Updated ISSUE_TEMPLATE.md (#3295) --- .github/ISSUE_TEMPLATE.md | 64 +++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index a9cfbd47..e5f81e3c 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,18 +1,62 @@ -Output of `i3 --moreversion 2>&- || i3 --version`: + -_REPLACE: i3 version output_ +## I'm submitting a… + +
+[ ] Bug
+[ ] Feature Request
+[ ] Documentation Request
+[ ] Other (Please describe in detail)
+
-URL to a logfile as per https://i3wm.org/docs/debugging.html: +## Current Behavior + -_REPLACE: URL to logfile_ +## Expected Behavior + -**What I did:** +## Reproduction Instructions + -**What I saw:** +## Environment + +Output of `i3 --moreversion 2>&-`: +
+i3 version: 
+
-_REPLACE: e.g. "i3 changed focus to the window ABOVE the current window"_ + +
+
+ + +
+Logfile URL:
+
+ + +
+- Linux Distribution & Version:
+- Are you using a compositor (e.g., xcompmgr or compton):
+
From 7ac37d8ae463d749c9f5aeb9428ed3523a5a53fb Mon Sep 17 00:00:00 2001 From: Dan Elkouby Date: Fri, 1 Jun 2018 18:55:35 +0300 Subject: [PATCH 097/218] Reframe swallowed windows if depth doesn't match X will not allow a window with ParentRelative background to be created or reparented under a window with mismatching color depth. Deal with this by destroying the container frame and creating a new one with the right depth upon swallowing. Defer destruction of the frame window until after the updated tree has been rendered to avoid some distracting flickering. Fixes #3297 --- include/x.h | 6 ++++++ src/manage.c | 14 ++++++++++++++ src/x.c | 25 +++++++++++++++++++------ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/include/x.h b/include/x.h index 3e81bc36..8b7664f2 100644 --- a/include/x.h +++ b/include/x.h @@ -49,6 +49,12 @@ void x_reinit(Con *con); */ void x_con_kill(Con *con); +/* + * Completely reinitializes the container's frame, without destroying the old window. + * + */ +void x_con_reframe(Con *con); + /** * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW) * diff --git a/src/manage.c b/src/manage.c index d591df15..b4d0af95 100644 --- a/src/manage.c +++ b/src/manage.c @@ -355,8 +355,16 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki } } } + xcb_window_t old_frame = XCB_NONE; if (nc->window != cwindow && nc->window != NULL) { window_free(nc->window); + /* Match frame and window depth. This is needed because X will refuse to reparent a + * window whose background is ParentRelative under a window with a different depth. */ + if (nc->depth != cwindow->depth) { + old_frame = nc->frame.id; + nc->depth = cwindow->depth; + x_con_reframe(nc); + } } nc->window = cwindow; x_reinit(nc); @@ -647,6 +655,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki tree_render(); + /* Destroy the old frame if we had to reframe the container. This needs to be done + * after rendering in order to prevent the background from flickering in its place. */ + if (old_frame != XCB_NONE) { + xcb_destroy_window(conn, old_frame); + } + /* Windows might get managed with the urgency hint already set (Pidgin is * known to do that), so check for that and handle the hint accordingly. * This code needs to be in this part of manage_window() because the window diff --git a/src/x.c b/src/x.c index 629520d4..25f341b6 100644 --- a/src/x.c +++ b/src/x.c @@ -249,11 +249,7 @@ void x_move_win(Con *src, Con *dest) { } } -/* - * Kills the window decoration associated with the given container. - * - */ -void x_con_kill(Con *con) { +static void _x_con_kill(Con *con) { con_state *state; if (con->colormap != XCB_NONE) { @@ -262,7 +258,6 @@ void x_con_kill(Con *con) { draw_util_surface_free(conn, &(con->frame)); draw_util_surface_free(conn, &(con->frame_buffer)); - xcb_destroy_window(conn, con->frame.id); xcb_free_pixmap(conn, con->frame_buffer.id); state = state_for_frame(con->frame.id); CIRCLEQ_REMOVE(&state_head, state, state); @@ -275,6 +270,24 @@ void x_con_kill(Con *con) { focused_id = last_focused = XCB_NONE; } +/* + * Kills the window decoration associated with the given container. + * + */ +void x_con_kill(Con *con) { + _x_con_kill(con); + xcb_destroy_window(conn, con->frame.id); +} + +/* + * Completely reinitializes the container's frame, without destroying the old window. + * + */ +void x_con_reframe(Con *con) { + _x_con_kill(con); + x_con_init(con); +} + /* * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW) * From 97536f04df6ab728ef5576709603f173f80a2cb2 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 2 Jun 2018 17:58:10 +0300 Subject: [PATCH 098/218] docs: link workspace_auto_back_and_forth from workspace command The current text is confusing. '--no-auto-back-and-forth' doesn't disable the 'workspace back_and_forth' command, the flag is not even valid for that command. --- docs/userguide | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index a8b35f3e..d944bb39 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1118,6 +1118,7 @@ force_xinerama yes Also note that your output names are not descriptive (like +HDMI1+) when using Xinerama, instead they are counted up, starting at 0: +xinerama-0+, +xinerama-1+, … +[[workspace_auto_back_and_forth]] === Automatic back-and-forth when switching to the current workspace This configuration directive enables automatic +workspace back_and_forth+ (see @@ -2121,8 +2122,8 @@ for_window [instance=notepad] sticky enable To change to a specific workspace, use the +workspace+ command, followed by the number or name of the workspace. Pass the optional flag -+--no-auto-back-and-forth+ to disable <> for this specific call -only. ++--no-auto-back-and-forth+ to disable <> for this +specific call only. To move containers to specific workspaces, use +move container to workspace+. From 5ab8c766c4c2d88e05815f7203782b691921ecb1 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Thu, 7 Jun 2018 23:07:16 +0300 Subject: [PATCH 099/218] Fix link Equivalent from the same version: https://cgit.freedesktop.org/xorg/xserver/tree/xkb/xkbEvents.c?h=xorg-server-1.17.2#n927 --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index de54dbce..eb60bc8b 100644 --- a/src/main.c +++ b/src/main.c @@ -641,7 +641,7 @@ int main(int argc, char *argv[]) { /* Setting both, XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE and * XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED, will lead to the * X server sending us the full XKB state in KeyPress and KeyRelease: - * https://sources.debian.net/src/xorg-server/2:1.17.2-1.1/xkb/xkbEvents.c/?hl=927#L927 + * https://cgit.freedesktop.org/xorg/xserver/tree/xkb/xkbEvents.c?h=xorg-server-1.20.0#n927 */ xcb_xkb_per_client_flags_reply_t *pcf_reply; /* The last three parameters are unset because they are only relevant From b87bc70cd6184f3fd066587df960a628a34e17fd Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 12 Jun 2018 23:50:09 +0300 Subject: [PATCH 100/218] Enable detectable autorepeat https://www.x.org/releases/current/doc/kbproto/xkbproto.html#Detectable_Autorepeat Detectable autorepeat should only affect --release bindings. Currently, when a user keeps a key pressed, we get multiple KeyPress and KeyRelease events. With this change, we still get multiple KeyPress events, which means that you can still keep a key pressed to repeatedly execute a normal binding, but only one KeyRelease event when the key is physically released. Unfortunately, this change is not currently testable because detectable autorepeat doesn't seem to work under Xephyr. AwesomeWM experienced the same problem: https://github.com/awesomeWM/awesome/commit/6f2424e90170be4acaa1d140f966ab6bb8a4d217 Fixes #3306 --- src/main.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/main.c b/src/main.c index eb60bc8b..eeeb3419 100644 --- a/src/main.c +++ b/src/main.c @@ -642,7 +642,15 @@ int main(int argc, char *argv[]) { * XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED, will lead to the * X server sending us the full XKB state in KeyPress and KeyRelease: * https://cgit.freedesktop.org/xorg/xserver/tree/xkb/xkbEvents.c?h=xorg-server-1.20.0#n927 + * + * XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT enable detectable autorepeat: + * https://www.x.org/releases/current/doc/kbproto/xkbproto.html#Detectable_Autorepeat + * This affects bindings using the --release flag: instead of getting multiple KeyRelease + * events we get only one event when the key is physically released by the user. */ + const uint32_t mask = XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | + XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED | + XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT; xcb_xkb_per_client_flags_reply_t *pcf_reply; /* The last three parameters are unset because they are only relevant * when using a feature called “automatic reset of boolean controls”: @@ -653,20 +661,24 @@ int main(int argc, char *argv[]) { xcb_xkb_per_client_flags( conn, XCB_XKB_ID_USE_CORE_KBD, - XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED, - XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED, + mask, + mask, 0 /* uint32_t ctrlsToChange */, 0 /* uint32_t autoCtrls */, 0 /* uint32_t autoCtrlsValues */), NULL); - if (pcf_reply == NULL || - !(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE)) { - ELOG("Could not set XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE\n"); - } - if (pcf_reply == NULL || - !(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED)) { - ELOG("Could not set XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED\n"); - } + +#define PCF_REPLY_ERROR(_value) \ + do { \ + if (pcf_reply == NULL || !(pcf_reply->value & (_value))) { \ + ELOG("Could not set " #_value "\n"); \ + } \ + } while (0) + + PCF_REPLY_ERROR(XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE); + PCF_REPLY_ERROR(XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED); + PCF_REPLY_ERROR(XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT); + free(pcf_reply); xkb_base = extreply->first_event; } From 5debba0d1c25cf3d7b410e1e03e49181b8cebb3b Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 15 Jun 2018 14:37:14 +0200 Subject: [PATCH 101/218] Update i3-msg.man: added get_config and send_tick Added get_config and send_tick which are mentioned [here](https://i3wm.org/docs/ipc.html). --- man/i3-msg.man | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/man/i3-msg.man b/man/i3-msg.man index 7f050f59..04c71900 100644 --- a/man/i3-msg.man +++ b/man/i3-msg.man @@ -69,6 +69,12 @@ get_version:: Gets the version of i3. The reply will be a JSON-encoded dictionary with the major, minor, patch and human-readable version. +get_config:: +Gets the currently loaded i3 configuration. + +send_tick:: +Sends a tick to all IPC connections which subscribe to tick events. + == DESCRIPTION i3-msg is a sample implementation for a client using the unix socket IPC From 606050a700802d1acbc42bd8961ff7ec3ee74e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Adamczak?= Date: Fri, 22 Jun 2018 12:34:11 +0200 Subject: [PATCH 102/218] Consider rect changed when its position changes --- src/x.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/x.c b/src/x.c index 25f341b6..1f54db83 100644 --- a/src/x.c +++ b/src/x.c @@ -796,11 +796,8 @@ void x_push_node(Con *con) { * background and only afterwards change the window size. This reduces * flickering. */ - /* As the pixmap only depends on the size and not on the position, it - * is enough to check if width/height have changed. Also, we don’t - * create a pixmap at all when the window is actually not visible - * (height == 0) or when it is not needed. */ - bool has_rect_changed = (state->rect.width != rect.width || state->rect.height != rect.height); + bool has_rect_changed = (state->rect.x != rect.x || state->rect.y != rect.y || + state->rect.width != rect.width || state->rect.height != rect.height); /* Check if the container has an unneeded pixmap left over from * previously having a border or titlebar. */ From bc439de755d23b7f0f7cad76aed23f975aa0c722 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 28 Mar 2018 03:35:40 +0300 Subject: [PATCH 103/218] Introduce get_assigned_output This also replaces code in create_workspace_on_output() that is theoretically more efficient but: 1. It isn't a huge difference since it depends on the number of outputs, that shouldn't be high. 2. get_assigned_output will be modified and used for #555, then its logic should be followed in create_workspace_on_output() too. Another note for create_workspace_on_output: if assigned is not NULL the condition (assigned != output->con) should never be false, ie if there is an assigned output to this name, it isn't the current one. This happens because the current callers check for assignments before calling create_workspace_on_output(). --- src/workspace.c | 59 ++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index a16479a5..9441042e 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -67,6 +67,33 @@ static void _workspace_apply_default_orientation(Con *ws) { } } +/* + * Returns the first output that is assigned to a workspace specified by the + * given name or number or NULL if no such output exists. If there is a + * workspace with a matching name and another workspace with a matching number, + * the output assigned to the first one is returned. + * If 'name' is NULL it will be ignored. + * If 'parsed_num' is -1 it will be ignored. + * + */ +static Con *get_assigned_output(const char *name, long parsed_num) { + Con *output = NULL; + + struct Workspace_Assignment *assignment; + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { + if (name && strcmp(assignment->name, name) == 0) { + DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output); + GREP_FIRST(output, croot, !strcmp(child->name, assignment->output)); + break; + } else if (parsed_num != -1 && name_is_digits(assignment->name) && ws_name_to_number(assignment->name) == parsed_num) { + DLOG("Found workspace number assignment to output \"%s\"\n", assignment->output); + GREP_FIRST(output, croot, !strcmp(child->name, assignment->output)); + } + } + + return output; +} + /* * Returns a pointer to the workspace with the given number (starting at 0), * creating the workspace if necessary (by allocating the necessary amount of @@ -78,25 +105,16 @@ Con *workspace_get(const char *num, bool *created) { if (workspace == NULL) { LOG("Creating new workspace \"%s\"\n", num); - /* unless an assignment is found, we will create this workspace on the current output */ - Con *output = con_get_output(focused); - /* look for assignments */ - struct Workspace_Assignment *assignment; /* We set workspace->num to the number if this workspace’s name begins * with a positive number. Otherwise it’s a named ws and num will be * -1. */ long parsed_num = ws_name_to_number(num); - TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { - if (strcmp(assignment->name, num) == 0) { - DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output); - GREP_FIRST(output, croot, !strcmp(child->name, assignment->output)); - break; - } else if (parsed_num != -1 && name_is_digits(assignment->name) && ws_name_to_number(assignment->name) == parsed_num) { - DLOG("Found workspace number assignment to output \"%s\"\n", assignment->output); - GREP_FIRST(output, croot, !strcmp(child->name, assignment->output)); - } + Con *output = get_assigned_output(num, parsed_num); + /* if an assignment is not found, we create this workspace on the current output */ + if (!output) { + output = con_get_output(focused); } Con *content = output_get_content(output); @@ -208,19 +226,10 @@ Con *create_workspace_on_output(Output *output, Con *content) { /* Ensure that this workspace is not assigned to a different output — * otherwise we would create it, then move it over to its output, then * find a new workspace, etc… */ - bool assigned = false; - struct Workspace_Assignment *assignment; - TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { - if (strcmp(assignment->name, target_name) != 0 || - strcmp(assignment->output, output_primary_name(output)) == 0) - continue; - - assigned = true; - break; - } - - if (assigned) + Con *assigned = get_assigned_output(target_name, -1); + if (assigned && assigned != output->con) { continue; + } exists = (get_existing_workspace_by_name(target_name) != NULL); if (!exists) { From 1d5b43c18f5de8e6282c4e78bb9db7988dab169d Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 28 Mar 2018 01:57:54 +0300 Subject: [PATCH 104/218] Move get_output_for_workspace() to i3test --- testcases/lib/i3test.pm.in | 24 +++++++++++++++++++ testcases/t/518-interpret-workspace-numbers.t | 15 ------------ testcases/t/522-rename-assigned-workspace.t | 15 ------------ 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index 68ac1ee5..5734eca7 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -26,6 +26,7 @@ use Data::Dumper (); use Exporter (); our @EXPORT = qw( get_workspace_names + get_output_for_workspace get_unused_workspace fresh_workspace get_ws_content @@ -402,6 +403,29 @@ sub get_workspace_names { [ map { $_->{name} } @cons ] } +=head2 get_output_for_workspace() + +Returns the name of the output on which this workspace resides + + cmd 'focus output fake-1'; + cmd 'workspace 1'; + is(get_output_for_workspace('1', 'fake-0', 'Workspace 1 in output fake-0'); + +=cut +sub get_output_for_workspace { + my $ws_name = shift @_; + my $i3 = i3(get_socket_path()); + my $tree = $i3->get_tree->recv; + my @outputs = @{$tree->{nodes}}; + + foreach (grep { not $_->{name} =~ /^__/ } @outputs) { + my $output = $_->{name}; + foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) { + return $output if $_->{nodes}[0]->{name} =~ $ws_name; + } + } +} + =head2 get_unused_workspace Returns a workspace name which has not yet been used. See also diff --git a/testcases/t/518-interpret-workspace-numbers.t b/testcases/t/518-interpret-workspace-numbers.t index e9ea76c4..7fd86d72 100644 --- a/testcases/t/518-interpret-workspace-numbers.t +++ b/testcases/t/518-interpret-workspace-numbers.t @@ -30,21 +30,6 @@ workspace 2:override output fake-1 fake-outputs 1024x768+0+0,1024x768+1024+0 EOT -my $i3 = i3(get_socket_path()); -$i3->connect->recv; - -# Returns the name of the output on which this workspace resides -sub get_output_for_workspace { - my $ws_name = shift @_; - - foreach (grep { not $_->{name} =~ /^__/ } @{$i3->get_tree->recv->{nodes}}) { - my $output = $_->{name}; - foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) { - return $output if $_->{nodes}[0]->{name} =~ $ws_name; - } - } -} - ################################################################################ # Workspace assignments with bare numbers should be interpreted as `workspace # number` config directives. Any workspace beginning with that number should be diff --git a/testcases/t/522-rename-assigned-workspace.t b/testcases/t/522-rename-assigned-workspace.t index 5c9f2ff3..f6319729 100644 --- a/testcases/t/522-rename-assigned-workspace.t +++ b/testcases/t/522-rename-assigned-workspace.t @@ -31,21 +31,6 @@ workspace baz output fake-1 workspace 5 output left EOT -my $i3 = i3(get_socket_path()); -$i3->connect->recv; - -# Returns the name of the output on which this workspace resides -sub get_output_for_workspace { - my $ws_name = shift @_; - - foreach (grep { not $_->{name} =~ /^__/ } @{$i3->get_tree->recv->{nodes}}) { - my $output = $_->{name}; - foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) { - return $output if $_->{nodes}[0]->{name} =~ $ws_name; - } - } -} - ########################################################################## # Renaming the workspace to an unassigned name does not move the workspace # (regression test) From d525eb80aefcd596c5c40750eb9fa3570e5c55ed Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 28 Mar 2018 04:05:48 +0300 Subject: [PATCH 105/218] Use get_assigned_output for numbers This prohibits the usage of workspaces assigned to other outputs in create_workspace_on_output. Eg, with config: workspace 1 output fake-0 workspace 2 output fake-0 and 2 screens workspace 2 would be used for the second screen even though it is assigned to the first one. Also introduces a test for workspace assignments that includes the case described above and some tests that don't fail in the next branch. --- src/workspace.c | 4 +- testcases/t/297-assign-workspace-to-output.t | 60 ++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 testcases/t/297-assign-workspace-to-output.t diff --git a/src/workspace.c b/src/workspace.c index 9441042e..a27c6f4b 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -248,7 +248,9 @@ Con *create_workspace_on_output(Output *output, Con *content) { DLOG("Getting next unused workspace by number\n"); int c = 0; while (exists) { - exists = (get_existing_workspace_by_num(++c) != NULL); + c++; + Con *assigned = get_assigned_output(NULL, c); + exists = (get_existing_workspace_by_num(c) || (assigned && assigned != output->con)); DLOG("result for ws %d: exists = %d\n", c, exists); } ws->num = c; diff --git a/testcases/t/297-assign-workspace-to-output.t b/testcases/t/297-assign-workspace-to-output.t new file mode 100644 index 00000000..650efa12 --- /dev/null +++ b/testcases/t/297-assign-workspace-to-output.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) +# +# Test assignments of workspaces to outputs. +use i3test i3_autostart => 0; + +################################################################################ +# Test initial workspaces. +################################################################################ + +my $config = < Date: Tue, 10 Jul 2018 05:04:34 +0300 Subject: [PATCH 106/218] Correct XDG paths precedence for config files Fixes #3323 --- include/libi3.h | 10 ++++---- libi3/get_config_path.c | 53 +++++++++++++++++++++++------------------ man/i3.man | 8 +++---- src/config.c | 3 ++- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/include/libi3.h b/include/libi3.h index b7a1e2aa..ebddee96 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -506,11 +506,11 @@ int logical_px(const int logical); char *resolve_tilde(const char *path); /** - * Get the path of the first configuration file found. If override_configpath - * is specified, that path is returned and saved for further calls. Otherwise, - * checks the home directory first, then the system directory first, always - * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME, - * $XDG_CONFIG_DIRS) + * Get the path of the first configuration file found. If override_configpath is + * specified, that path is returned and saved for further calls. Otherwise, + * checks the home directory first, then the system directory, always taking + * into account the XDG Base Directory Specification ($XDG_CONFIG_HOME, + * $XDG_CONFIG_DIRS). * */ char *get_config_path(const char *override_configpath, bool use_system_paths); diff --git a/libi3/get_config_path.c b/libi3/get_config_path.c index efece5cd..4909e116 100644 --- a/libi3/get_config_path.c +++ b/libi3/get_config_path.c @@ -21,11 +21,11 @@ static bool path_exists(const char *path) { } /* - * Get the path of the first configuration file found. If override_configpath - * is specified, that path is returned and saved for further calls. Otherwise, - * checks the home directory first, then the system directory first, always - * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME, - * $XDG_CONFIG_DIRS) + * Get the path of the first configuration file found. If override_configpath is + * specified, that path is returned and saved for further calls. Otherwise, + * checks the home directory first, then the system directory, always taking + * into account the XDG Base Directory Specification ($XDG_CONFIG_HOME, + * $XDG_CONFIG_DIRS). * */ char *get_config_path(const char *override_configpath, bool use_system_paths) { @@ -38,40 +38,41 @@ char *get_config_path(const char *override_configpath, bool use_system_paths) { return sstrdup(saved_configpath); } - if (saved_configpath != NULL) + if (saved_configpath != NULL) { return sstrdup(saved_configpath); + } - /* 1: check the traditional path under the home directory */ - config_path = resolve_tilde("~/.i3/config"); - if (path_exists(config_path)) - return config_path; - free(config_path); - - /* 2: check for $XDG_CONFIG_HOME/i3/config */ - if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) + /* 1: check for $XDG_CONFIG_HOME/i3/config */ + if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) { xdg_config_home = "~/.config"; + } xdg_config_home = resolve_tilde(xdg_config_home); sasprintf(&config_path, "%s/i3/config", xdg_config_home); free(xdg_config_home); - if (path_exists(config_path)) + if (path_exists(config_path)) { return config_path; + } + free(config_path); + + /* 2: check the traditional path under the home directory */ + config_path = resolve_tilde("~/.i3/config"); + if (path_exists(config_path)) { + return config_path; + } free(config_path); /* The below paths are considered system-level, and can be skipped if the * caller only wants user-level configs. */ - if (!use_system_paths) + if (!use_system_paths) { return NULL; + } - /* 3: check the traditional path under /etc */ - config_path = SYSCONFDIR "/i3/config"; - if (path_exists(config_path)) - return sstrdup(config_path); - - /* 4: check for $XDG_CONFIG_DIRS/i3/config */ - if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL) + /* 3: check for $XDG_CONFIG_DIRS/i3/config */ + if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL) { xdg_config_dirs = SYSCONFDIR "/xdg"; + } char *buf = sstrdup(xdg_config_dirs); char *tok = strtok(buf, ":"); @@ -88,5 +89,11 @@ char *get_config_path(const char *override_configpath, bool use_system_paths) { } free(buf); + /* 4: check the traditional path under /etc */ + config_path = SYSCONFDIR "/i3/config"; + if (path_exists(config_path)) { + return sstrdup(config_path); + } + return NULL; } diff --git a/man/i3.man b/man/i3.man index 1f595ce8..640b5ac8 100644 --- a/man/i3.man +++ b/man/i3.man @@ -170,10 +170,10 @@ Exits i3. When starting, i3 looks for configuration files in the following order: -1. ~/.i3/config -2. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set) -3. /etc/i3/config -4. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set) +1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set) +2. ~/.i3/config +3. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set) +4. /etc/i3/config You can specify a custom path using the -c option. diff --git a/src/config.c b/src/config.c index de149ce7..ab19bae3 100644 --- a/src/config.c +++ b/src/config.c @@ -49,7 +49,8 @@ bool parse_configuration(const char *override_configpath, bool use_nagbar) { char *path = get_config_path(override_configpath, true); if (path == NULL) { die("Unable to find the configuration file (looked at " - "~/.i3/config, $XDG_CONFIG_HOME/i3/config, " SYSCONFDIR "/i3/config and $XDG_CONFIG_DIRS/i3/config)"); + "$XDG_CONFIG_HOME/i3/config, ~/.i3/config, $XDG_CONFIG_DIRS/i3/config " + "and " SYSCONFDIR "/i3/config)"); } LOG("Parsing configfile %s\n", path); From 37ea56c221fa932d03315f575569cabaebf4ba5f Mon Sep 17 00:00:00 2001 From: Felix Buehler Date: Mon, 2 Jul 2018 00:03:13 +0200 Subject: [PATCH 107/218] make format_placeholders case-sensitive --- libi3/format_placeholders.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libi3/format_placeholders.c b/libi3/format_placeholders.c index 59e94781..770e383d 100644 --- a/libi3/format_placeholders.c +++ b/libi3/format_placeholders.c @@ -11,8 +11,8 @@ #include #include -#ifndef STARTS_WITH -#define STARTS_WITH(string, needle) (strncasecmp((string), (needle), strlen((needle))) == 0) +#ifndef CS_STARTS_WITH +#define CS_STARTS_WITH(string, needle) (strncmp((string), (needle), strlen((needle))) == 0) #endif /* @@ -28,7 +28,7 @@ char *format_placeholders(char *format, placeholder_t *placeholders, int num) { int buffer_len = strlen(format) + 1; for (char *walk = format; *walk != '\0'; walk++) { for (int i = 0; i < num; i++) { - if (!STARTS_WITH(walk, placeholders[i].name)) + if (!CS_STARTS_WITH(walk, placeholders[i].name)) continue; buffer_len = buffer_len - strlen(placeholders[i].name) + strlen(placeholders[i].value); @@ -48,7 +48,7 @@ char *format_placeholders(char *format, placeholder_t *placeholders, int num) { bool matched = false; for (int i = 0; i < num; i++) { - if (!STARTS_WITH(walk, placeholders[i].name)) { + if (!CS_STARTS_WITH(walk, placeholders[i].name)) { continue; } From d1652ca7cd156b91fbea6cdc89ac7a5f6fad5f30 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Thu, 12 Jul 2018 00:12:27 +0300 Subject: [PATCH 108/218] cmd_rename_workspace: always call con_focus This was introduced in 252db3b8c (#3245). That commit moved the con_activate line inside the loop, meaning it wouldn't always be called. When the mouse moved after a rename with focus_follows_mouse enabled, check_crossing_screen_boundary (src/handlers.c:111) called con_descend_focused that used the wrong focus order. I also change con_activate to con_focus since we don't really want to raise floating containers there. Fixes #3325 --- src/commands.c | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/commands.c b/src/commands.c index c7b57ab3..fb7b08bf 100644 --- a/src/commands.c +++ b/src/commands.c @@ -2021,28 +2021,27 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { } workspace_move_to_output(workspace, target_output); - bool can_restore_focus = previously_focused != NULL; - /* NB: If previously_focused is a workspace we can't - * work directly with it since it might have been cleaned up by - * workspace_show() already, depending on the - * focus order/number of other workspaces on the output. - * Instead, we loop through the available workspaces and only focus - * previously_focused if we still find it. */ - if (previously_focused_content) { - Con *workspace = NULL; - GREP_FIRST(workspace, previously_focused_content, child == previously_focused); - can_restore_focus &= (workspace != NULL); - } - - if (can_restore_focus) { - /* Restore the previous focus since con_attach messes with the focus. */ - workspace_show(con_get_workspace(previously_focused)); - con_activate(previously_focused); - } - break; } + bool can_restore_focus = previously_focused != NULL; + /* NB: If previously_focused is a workspace we can't work directly with it + * since it might have been cleaned up by workspace_show() already, + * depending on the focus order/number of other workspaces on the output. + * Instead, we loop through the available workspaces and only focus + * previously_focused if we still find it. */ + if (previously_focused_content) { + Con *workspace = NULL; + GREP_FIRST(workspace, previously_focused_content, child == previously_focused); + can_restore_focus &= (workspace != NULL); + } + + if (can_restore_focus) { + /* Restore the previous focus since con_attach messes with the focus. */ + workspace_show(con_get_workspace(previously_focused)); + con_focus(previously_focused); + } + cmd_output->needs_tree_render = true; ysuccess(true); From 4cd223f80a92850a26ec79e5d0faef360d4c0497 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Thu, 12 Jul 2018 03:49:07 +0300 Subject: [PATCH 109/218] Fix ISSUE_TEMPLATE typo --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index e5f81e3c..96f68d90 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -54,7 +54,7 @@ Logfile URL:
 - Linux Distribution & Version:

From bce088679a3a51d9a6ce2bcef6ddc7938368d312 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Wed, 28 Mar 2018 00:55:20 +0300
Subject: [PATCH 110/218] Allow multiple assignments of workspaces to outputs

Also makes get_assigned_output work with the primary output:
    workspace X output primary
will now work.

Fixes #555.
---
 docs/userguide                                |  7 +++-
 include/workspace.h                           |  7 ++++
 parser-specs/config.spec                      |  2 +-
 src/commands.c                                |  3 ++
 src/config_directives.c                       | 18 +++++---
 src/randr.c                                   |  7 ++--
 src/workspace.c                               | 32 +++++++++++---
 testcases/t/297-assign-workspace-to-output.t  | 42 +++++++++++++++++++
 testcases/t/518-interpret-workspace-numbers.t |  8 ++++
 testcases/t/522-rename-assigned-workspace.t   | 22 ++++++++++
 10 files changed, 130 insertions(+), 18 deletions(-)

diff --git a/docs/userguide b/docs/userguide
index d944bb39..2746f24e 100644
--- a/docs/userguide
+++ b/docs/userguide
@@ -888,7 +888,7 @@ the second screen and so on).
 
 *Syntax*:
 -------------------------------------
-workspace  output 
+workspace  output  [output2]…
 -------------------------------------
 
 The 'output' is the name of the RandR output you attach your screen to. On a
@@ -907,12 +907,15 @@ monitor name is “Dell UP2414Q”.
 entire monitor, i3 will still use the entire area of the containing monitor
 rather than that of just the output's.)
 
+You can specify multiple outputs. The first available will be used.
+
 If you use named workspaces, they must be quoted:
 
 *Examples*:
 ---------------------------
 workspace 1 output LVDS1
-workspace 5 output VGA1
+workspace 2 output primary
+workspace 5 output VGA1 LVDS1
 workspace "2: vim" output VGA1
 ---------------------------
 
diff --git a/include/workspace.h b/include/workspace.h
index ae287422..28d9eb66 100644
--- a/include/workspace.h
+++ b/include/workspace.h
@@ -38,6 +38,13 @@ Con *get_existing_workspace_by_name(const char *name);
  */
 Con *get_existing_workspace_by_num(int num);
 
+/**
+ * Returns true if the first output assigned to a workspace with the given
+ * workspace assignment is the same as the given output.
+ *
+ */
+bool output_triggers_assignment(Output *output, struct Workspace_Assignment *assignment);
+
 /**
  * Returns a pointer to the workspace with the given number (starting at 0),
  * creating the workspace if necessary (by allocating the necessary amount of
diff --git a/parser-specs/config.spec b/parser-specs/config.spec
index c5c4651c..b15c9a80 100644
--- a/parser-specs/config.spec
+++ b/parser-specs/config.spec
@@ -273,7 +273,7 @@ state WORKSPACE_OUTPUT:
       -> WORKSPACE_OUTPUT_STR
 
 state WORKSPACE_OUTPUT_STR:
-  output = word
+  output = string
       -> call cfg_workspace($workspace, $output)
 
 # ipc-socket 
diff --git a/src/commands.c b/src/commands.c
index c7b57ab3..a133daa6 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -2019,6 +2019,9 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
             LOG("Could not get output named \"%s\"\n", assignment->output);
             continue;
         }
+        if (!output_triggers_assignment(target_output, assignment)) {
+            continue;
+        }
         workspace_move_to_output(workspace, target_output);
 
         bool can_restore_focus = previously_focused != NULL;
diff --git a/src/config_directives.c b/src/config_directives.c
index 4a31f79e..17e25cde 100644
--- a/src/config_directives.c
+++ b/src/config_directives.c
@@ -322,8 +322,8 @@ CFGFUN(show_marks, const char *value) {
     config.show_marks = eval_boolstr(value);
 }
 
-CFGFUN(workspace, const char *workspace, const char *output) {
-    DLOG("Assigning workspace \"%s\" to output \"%s\"\n", workspace, output);
+CFGFUN(workspace, const char *workspace, const char *outputs) {
+    DLOG("Assigning workspace \"%s\" to outputs \"%s\"\n", workspace, outputs);
     /* Check for earlier assignments of the same workspace so that we
      * don’t have assignments of a single workspace to different
      * outputs */
@@ -336,10 +336,16 @@ CFGFUN(workspace, const char *workspace, const char *output) {
         }
     }
 
-    assignment = scalloc(1, sizeof(struct Workspace_Assignment));
-    assignment->name = sstrdup(workspace);
-    assignment->output = sstrdup(output);
-    TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
+    char *buf = sstrdup(outputs);
+    char *output = strtok(buf, " ");
+    while (output != NULL) {
+        assignment = scalloc(1, sizeof(struct Workspace_Assignment));
+        assignment->name = sstrdup(workspace);
+        assignment->output = sstrdup(output);
+        TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
+        output = strtok(NULL, " ");
+    }
+    free(buf);
 }
 
 CFGFUN(ipc_socket, const char *path) {
diff --git a/src/randr.c b/src/randr.c
index d4d7402a..38f1ee97 100644
--- a/src/randr.c
+++ b/src/randr.c
@@ -424,9 +424,9 @@ void init_ws_for_output(Output *output, Con *content) {
     /* go through all assignments and move the existing workspaces to this output */
     struct Workspace_Assignment *assignment;
     TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-        if (strcmp(assignment->output, output_primary_name(output)) != 0)
+        if (!output_triggers_assignment(output, assignment)) {
             continue;
-
+        }
         Con *workspace = get_existing_workspace_by_name(assignment->name);
         if (workspace == NULL)
             continue;
@@ -501,8 +501,9 @@ void init_ws_for_output(Output *output, Con *content) {
 
     /* otherwise, we create the first assigned ws for this output */
     TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-        if (strcmp(assignment->output, output_primary_name(output)) != 0)
+        if (!output_triggers_assignment(output, assignment)) {
             continue;
+        }
 
         LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
             assignment->name, assignment->output);
diff --git a/src/workspace.c b/src/workspace.c
index a27c6f4b..e92aaa5f 100644
--- a/src/workspace.c
+++ b/src/workspace.c
@@ -72,28 +72,47 @@ static void _workspace_apply_default_orientation(Con *ws) {
  * given name or number or NULL if no such output exists. If there is a
  * workspace with a matching name and another workspace with a matching number,
  * the output assigned to the first one is returned.
+ * The order of the 'ws_assignments' queue is respected: if multiple assignments
+ * match the specified workspace, the first one is returned.
  * If 'name' is NULL it will be ignored.
  * If 'parsed_num' is -1 it will be ignored.
  *
  */
 static Con *get_assigned_output(const char *name, long parsed_num) {
     Con *output = NULL;
-
     struct Workspace_Assignment *assignment;
     TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
         if (name && strcmp(assignment->name, name) == 0) {
             DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output);
-            GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
-            break;
-        } else if (parsed_num != -1 && name_is_digits(assignment->name) && ws_name_to_number(assignment->name) == parsed_num) {
+            Output *assigned_by_name = get_output_by_name(assignment->output, true);
+            if (assigned_by_name) {
+                /* When the name matches exactly, skip numbered assignments. */
+                return assigned_by_name->con;
+            }
+        } else if (!output && /* Only keep the first numbered assignment. */
+                   parsed_num != -1 &&
+                   name_is_digits(assignment->name) &&
+                   ws_name_to_number(assignment->name) == parsed_num) {
             DLOG("Found workspace number assignment to output \"%s\"\n", assignment->output);
-            GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
+            Output *assigned_by_num = get_output_by_name(assignment->output, true);
+            if (assigned_by_num) {
+                output = assigned_by_num->con;
+            }
         }
     }
 
     return output;
 }
 
+/*
+ * Returns true if the first output assigned to a workspace with the given
+ * workspace assignment is the same as the given output.
+ */
+bool output_triggers_assignment(Output *output, struct Workspace_Assignment *assignment) {
+    Con *assigned = get_assigned_output(assignment->name, -1);
+    return assigned && assigned == output->con;
+}
+
 /*
  * Returns a pointer to the workspace with the given number (starting at 0),
  * creating the workspace if necessary (by allocating the necessary amount of
@@ -957,8 +976,9 @@ bool workspace_move_to_output(Con *ws, Output *output) {
         bool used_assignment = false;
         struct Workspace_Assignment *assignment;
         TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-            if (assignment->output == NULL || strcmp(assignment->output, output_primary_name(current_output)) != 0)
+            if (!output_triggers_assignment(current_output, assignment)) {
                 continue;
+            }
             /* check if this workspace is already attached to the tree */
             if (get_existing_workspace_by_name(assignment->name) != NULL) {
                 continue;
diff --git a/testcases/t/297-assign-workspace-to-output.t b/testcases/t/297-assign-workspace-to-output.t
index 650efa12..a7b75be9 100644
--- a/testcases/t/297-assign-workspace-to-output.t
+++ b/testcases/t/297-assign-workspace-to-output.t
@@ -54,7 +54,49 @@ check_output('4', 'fake-3', 'First available number that is not assigned to exis
 check_output('dontusethisname', '', 'Named workspace that is assigned to output that does not exist is not used');
 check_output('donotoverride', '', 'Named workspace assigned to already occupied output is not used');
 
+exit_gracefully($pid);
 
+################################################################################
+# Test multiple assignments
+################################################################################
+
+sub do_test {
+    my ($workspace, $output, $msg) = @_;
+    cmd 'focus output ' . ($output eq 'fake-3' ? 'fake-0' : 'fake-3');
+
+    cmd "workspace $workspace";
+    check_output($workspace, $output, $msg)
+}
+
+$config = <
Date: Mon, 16 Jul 2018 08:54:08 -0500
Subject: [PATCH 111/218] docs/i3bar-protocol: fix typo

---
 docs/i3bar-protocol | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol
index cf86531c..8b7d2af7 100644
--- a/docs/i3bar-protocol
+++ b/docs/i3bar-protocol
@@ -107,7 +107,7 @@ stop_signal::
 	processing.
 	The default value (if none is specified) is SIGSTOP.
 cont_signal::
-	Specify to i3bar the signal (as an integer)to send to continue your
+	Specify to i3bar the signal (as an integer) to send to continue your
 	processing.
 	The default value (if none is specified) is SIGCONT.
 click_events::

From ad236dbaec4fc8fa0d35f80b1d0be640b68ae6ce Mon Sep 17 00:00:00 2001
From: Cassandra Fox 
Date: Sat, 28 Jul 2018 19:31:32 -0700
Subject: [PATCH 112/218] fix height offset calculation in pango text drawing

---
 libi3/font.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libi3/font.c b/libi3/font.c
index aedd4b32..3dca8124 100644
--- a/libi3/font.c
+++ b/libi3/font.c
@@ -111,7 +111,7 @@ static void draw_text_pango(const char *text, size_t text_len,
     pango_layout_get_pixel_size(layout, NULL, &height);
     /* Center the piece of text vertically if its height is smaller than the
      * cached font height, and just let "high" symbols fall out otherwise. */
-    int yoffset = (height < savedFont->height ? 0.5 : 1) * (height - savedFont->height);
+    int yoffset = abs(height - savedFont->height) / 2;
     cairo_move_to(cr, x, y - yoffset);
     pango_cairo_show_layout(cr, layout);
 

From f9084033765793b6ed04a75e8b3e5e425df6c783 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Mon, 30 Jul 2018 01:56:51 +0300
Subject: [PATCH 113/218] Simplify tree_close_internal

This commit makes multiple changes in tree_close_internal. I didn't
split them because they are not completely independent.

- Remove force_set_focus parameter
This parameter was always set to `false` throughout the code base except
for one case where it was set to `(con == focused)`, when killing a
floating con's parent (the one with type CT_FLOATING_CON). But this case
is not needed anymore since the special handling of CT_FLOATING_CONs in
con_next_focused was removed in #2941.
- Assume that con_next_focused does not returned a container of type
CT_DOCKAREA. This is reasonable since con_next_focused uses the
focus_head stack and has special handling of CT_DOCKAREA containers.
- Remove is_mapped
This variable was only used in the if block towards the end of
tree_close_internal. Ignoring the, now removed, dockarea code and the
use of force_set_focus this block performed only one useful action:
focus the `next` container when `con == focused`. `con == focused` was a
necessary and sufficient condition for the con_activate call:
if `con != focused` we could reach the inner if blocks because of the
other conditions but would never focus another container. If `con ==
focused` then all other conditions would be irrelevant.
- Remove special handling of floating containers
Since the `next` focused container is calculated through the parent for
floating containers, I moved this code to con_next_focused.
Also, because of the removal of force_set_focus, it appears that we can
call con_on_remove_child for floating containers as well.
---
 include/tree.h  |  2 +-
 src/con.c       | 11 ++++---
 src/floating.c  |  4 +--
 src/handlers.c  |  4 +--
 src/randr.c     |  4 +--
 src/tree.c      | 78 ++++++-------------------------------------------
 src/workspace.c |  2 +-
 7 files changed, 24 insertions(+), 81 deletions(-)

diff --git a/include/tree.h b/include/tree.h
index 62461def..41a63036 100644
--- a/include/tree.h
+++ b/include/tree.h
@@ -78,7 +78,7 @@ void tree_next(char way, orientation_t orientation);
  * container) and focus should be set there.
  *
  */
-bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus);
+bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent);
 
 /**
  * Loads tree from ~/.i3/_restart.json (used for in-place restarts).
diff --git a/src/con.c b/src/con.c
index f20f7c35..9e026cd1 100644
--- a/src/con.c
+++ b/src/con.c
@@ -286,14 +286,14 @@ void con_close(Con *con, kill_window_t kill_window) {
         for (child = TAILQ_FIRST(&(con->focus_head)); child;) {
             nextchild = TAILQ_NEXT(child, focused);
             DLOG("killing child = %p.\n", child);
-            tree_close_internal(child, kill_window, false, false);
+            tree_close_internal(child, kill_window, false);
             child = nextchild;
         }
 
         return;
     }
 
-    tree_close_internal(con, kill_window, false, false);
+    tree_close_internal(con, kill_window, false);
 }
 
 /*
@@ -1437,6 +1437,9 @@ Con *con_next_focused(Con *con) {
         DLOG("selecting workspace for dock client\n");
         return con_descend_focused(output_get_content(con->parent->parent));
     }
+    if (con_is_floating(con)) {
+        con = con->parent;
+    }
 
     /* if 'con' is not the first entry in the focus stack, use the first one as
      * it’s currently focused already */
@@ -1955,7 +1958,7 @@ static void con_on_remove_child(Con *con) {
         if (TAILQ_EMPTY(&(con->focus_head)) && !workspace_is_visible(con)) {
             LOG("Closing old workspace (%p / %s), it is empty\n", con, con->name);
             yajl_gen gen = ipc_marshal_workspace_event("empty", con, NULL);
-            tree_close_internal(con, DONT_KILL_WINDOW, false, false);
+            tree_close_internal(con, DONT_KILL_WINDOW, false);
 
             const unsigned char *payload;
             ylength length;
@@ -1976,7 +1979,7 @@ static void con_on_remove_child(Con *con) {
     int children = con_num_children(con);
     if (children == 0) {
         DLOG("Container empty, closing\n");
-        tree_close_internal(con, DONT_KILL_WINDOW, false, false);
+        tree_close_internal(con, DONT_KILL_WINDOW, false);
         return;
     }
 }
diff --git a/src/floating.c b/src/floating.c
index 9c15bb10..8e7d4a54 100644
--- a/src/floating.c
+++ b/src/floating.c
@@ -246,7 +246,7 @@ void floating_enable(Con *con, bool automatic) {
         Con *parent = con->parent;
         /* clear the pointer before calling tree_close_internal in which the memory is freed */
         con->parent = NULL;
-        tree_close_internal(parent, DONT_KILL_WINDOW, false, false);
+        tree_close_internal(parent, DONT_KILL_WINDOW, false);
     }
 
     char *name;
@@ -372,7 +372,7 @@ void floating_disable(Con *con, bool automatic) {
         Con *parent = con->parent;
         con_detach(con);
         con->parent = NULL;
-        tree_close_internal(parent, DONT_KILL_WINDOW, true, false);
+        tree_close_internal(parent, DONT_KILL_WINDOW, true);
         con_attach(con, tiling_focused, false);
         con->percent = 0.0;
         con_fix_percent(con->parent);
diff --git a/src/handlers.c b/src/handlers.c
index 38c2caf1..3972659b 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -511,7 +511,7 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) {
     xcb_delete_property(conn, event->window, A__NET_WM_DESKTOP);
     xcb_delete_property(conn, event->window, A__NET_WM_STATE);
 
-    tree_close_internal(con, DONT_KILL_WINDOW, false, false);
+    tree_close_internal(con, DONT_KILL_WINDOW, false);
     tree_render();
 
 ignore_end:
@@ -884,7 +884,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             if (event->data.data32[0])
                 last_timestamp = event->data.data32[0];
 
-            tree_close_internal(con, KILL_WINDOW, false, false);
+            tree_close_internal(con, KILL_WINDOW, false);
             tree_render();
         } else {
             DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window);
diff --git a/src/randr.c b/src/randr.c
index 38f1ee97..3bc1629e 100644
--- a/src/randr.c
+++ b/src/randr.c
@@ -991,7 +991,7 @@ void randr_disable_output(Output *output) {
             if (current != next && TAILQ_EMPTY(&(current->focus_head))) {
                 /* the workspace is empty and not focused, get rid of it */
                 DLOG("Getting rid of current = %p / %s (empty, unfocused)\n", current, current->name);
-                tree_close_internal(current, DONT_KILL_WINDOW, false, false);
+                tree_close_internal(current, DONT_KILL_WINDOW, false);
                 continue;
             }
             DLOG("Detaching current = %p / %s\n", current, current->name);
@@ -1037,7 +1037,7 @@ void randr_disable_output(Output *output) {
         Con *con = output->con;
         /* clear the pointer before calling tree_close_internal in which the memory is freed */
         output->con = NULL;
-        tree_close_internal(con, DONT_KILL_WINDOW, true, false);
+        tree_close_internal(con, DONT_KILL_WINDOW, true);
         DLOG("Done. Should be fine now\n");
     }
 
diff --git a/src/tree.c b/src/tree.c
index 96766b6a..6b66dd2e 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -175,16 +175,6 @@ Con *tree_open_con(Con *con, i3Window *window) {
     return new;
 }
 
-static bool _is_con_mapped(Con *con) {
-    Con *child;
-
-    TAILQ_FOREACH(child, &(con->nodes_head), nodes)
-    if (_is_con_mapped(child))
-        return true;
-
-    return con->mapped;
-}
-
 /*
  * Closes the given container including all children.
  * Returns true if the container was killed or false if just WM_DELETE was sent
@@ -193,22 +183,10 @@ static bool _is_con_mapped(Con *con) {
  * The dont_kill_parent flag is specified when the function calls itself
  * recursively while deleting a containers children.
  *
- * The force_set_focus flag is specified in the case of killing a floating
- * window: tree_close_internal() will be invoked for the CT_FLOATINGCON (the parent
- * container) and focus should be set there.
- *
  */
-bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus) {
-    bool was_mapped = con->mapped;
+bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent) {
     Con *parent = con->parent;
 
-    if (!was_mapped) {
-        /* Even if the container itself is not mapped, its children may be
-         * mapped (for example split containers don't have a mapped window on
-         * their own but usually contain mapped children). */
-        was_mapped = _is_con_mapped(con);
-    }
-
     /* remove the urgency hint of the workspace (if set) */
     if (con->urgent) {
         con_set_urgency(con, false);
@@ -216,10 +194,6 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
         workspace_update_urgent_flag(con_get_workspace(con));
     }
 
-    /* Get the container which is next focused */
-    Con *next = con_next_focused(con);
-    DLOG("next = %p, focused = %p\n", next, focused);
-
     DLOG("closing %p, kill_window = %d\n", con, kill_window);
     Con *child, *nextchild;
     bool abort_kill = false;
@@ -228,8 +202,9 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
     for (child = TAILQ_FIRST(&(con->nodes_head)); child;) {
         nextchild = TAILQ_NEXT(child, nodes);
         DLOG("killing child=%p\n", child);
-        if (!tree_close_internal(child, kill_window, true, false))
+        if (!tree_close_internal(child, kill_window, true)) {
             abort_kill = true;
+        }
         child = nextchild;
     }
 
@@ -281,17 +256,8 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
     Con *ws = con_get_workspace(con);
 
     /* Figure out which container to focus next before detaching 'con'. */
-    if (con_is_floating(con)) {
-        if (con == focused) {
-            DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws);
-            next = con_next_focused(parent);
-
-            dont_kill_parent = true;
-            DLOG("Alright, focusing %p\n", next);
-        } else {
-            next = NULL;
-        }
-    }
+    Con *next = (con == focused) ? con_next_focused(con) : NULL;
+    DLOG("next = %p, focused = %p\n", next, focused);
 
     /* Detach the container so that it will not be rendered anymore. */
     con_detach(con);
@@ -324,12 +290,6 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
     /* kill the X11 part of this container */
     x_con_kill(con);
 
-    if (con_is_floating(con)) {
-        DLOG("Container was floating, killing floating container\n");
-        tree_close_internal(parent, DONT_KILL_WINDOW, false, (con == focused));
-        DLOG("parent container killed\n");
-    }
-
     if (ws == con) {
         DLOG("Closing a workspace container, updating EWMH atoms\n");
         ewmh_update_number_of_desktops();
@@ -339,30 +299,10 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
 
     con_free(con);
 
-    /* in the case of floating windows, we already focused another container
-     * when closing the parent, so we can exit now. */
-    if (!next) {
-        DLOG("No next container, i will just exit now\n");
-        return true;
-    }
-
-    if (was_mapped || con == focused) {
-        if ((kill_window != DONT_KILL_WINDOW) || !dont_kill_parent || con == focused) {
-            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_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_activate(next);
-            }
-        } else {
-            DLOG("not focusing because we're not killing anybody\n");
-        }
+    if (next) {
+        con_activate(next);
     } else {
-        DLOG("not focusing, was not mapped\n");
+        DLOG("not changing focus, the container was not focused before\n");
     }
 
     /* check if the parent container is empty now and close it */
@@ -755,7 +695,7 @@ void tree_flatten(Con *con) {
 
     /* 4: close the redundant cons */
     DLOG("closing redundant cons\n");
-    tree_close_internal(con, DONT_KILL_WINDOW, true, false);
+    tree_close_internal(con, DONT_KILL_WINDOW, true);
 
     /* Well, we got to abort the recursion here because we destroyed the
      * container. However, if tree_flatten() is called sufficiently often,
diff --git a/src/workspace.c b/src/workspace.c
index e92aaa5f..5f5c8d4f 100644
--- a/src/workspace.c
+++ b/src/workspace.c
@@ -496,7 +496,7 @@ void workspace_show(Con *workspace) {
         if (!workspace_is_visible(old)) {
             LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
             yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL);
-            tree_close_internal(old, DONT_KILL_WINDOW, false, false);
+            tree_close_internal(old, DONT_KILL_WINDOW, false);
 
             const unsigned char *payload;
             ylength length;

From b0bbe53d0404d600b29aa40671aadc2874c4ea50 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Mon, 23 Apr 2018 01:02:44 +0300
Subject: [PATCH 114/218] Introduce free_ipc_client

---
 src/ipc.c | 37 +++++++++++++++++++------------------
 1 file changed, 19 insertions(+), 18 deletions(-)

diff --git a/src/ipc.c b/src/ipc.c
index 2ac09d7d..240119bc 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -38,6 +38,16 @@ static void set_nonblock(int sockfd) {
         err(-1, "Could not set O_NONBLOCK");
 }
 
+static void free_ipc_client(ipc_client *client) {
+    close(client->fd);
+    for (int i = 0; i < client->num_events; i++){
+        free(client->events[i]);
+    }
+    free(client->events);
+    TAILQ_REMOVE(&all_clients, client, clients);
+    free(client);
+}
+
 /*
  * Sends the specified event to all IPC clients which are currently connected
  * and subscribed to this kind of event.
@@ -99,12 +109,7 @@ void ipc_shutdown(shutdown_reason_t reason) {
     while (!TAILQ_EMPTY(&all_clients)) {
         current = TAILQ_FIRST(&all_clients);
         shutdown(current->fd, SHUT_RDWR);
-        close(current->fd);
-        for (int i = 0; i < current->num_events; i++)
-            free(current->events[i]);
-        free(current->events);
-        TAILQ_REMOVE(&all_clients, current, clients);
-        free(current);
+        free_ipc_client(current);
     }
 }
 
@@ -1247,25 +1252,21 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
             return;
         }
 
-        /* If not, there was some kind of error. We don’t bother
-         * and close the connection */
-        close(w->fd);
-
-        /* Delete the client from the list of clients */
+        /* If not, there was some kind of error. We don’t bother and close the
+         * connection. Delete the client from the list of clients. */
+        bool closed = false;
         ipc_client *current;
         TAILQ_FOREACH(current, &all_clients, clients) {
             if (current->fd != w->fd)
                 continue;
 
-            for (int i = 0; i < current->num_events; i++)
-                free(current->events[i]);
-            free(current->events);
-            /* We can call TAILQ_REMOVE because we break out of the
-             * TAILQ_FOREACH afterwards */
-            TAILQ_REMOVE(&all_clients, current, clients);
-            free(current);
+            free_ipc_client(current);
+            closed = true;
             break;
         }
+        if (!closed) {
+            close(w->fd);
+        }
 
         ev_io_stop(EV_A_ w);
         free(w);

From 42c5feb22f230a16871059ef4403841c78fc4c12 Mon Sep 17 00:00:00 2001
From: downzer0 
Date: Tue, 31 Jul 2018 17:35:05 -0500
Subject: [PATCH 115/218] enhancement: adds hyper as a sensible terminal option
 - hyper.is

---
 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 6638098c..c3a2e4e0 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 guake tilda alacritty; 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 alacritty hyper; 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 fc956eef..bda5a723 100644
--- a/man/i3-sensible-terminal.man
+++ b/man/i3-sensible-terminal.man
@@ -48,6 +48,7 @@ It tries to start one of the following (in that order):
 * guake
 * tilda
 * alacritty
+* hyper
 
 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 3ea3935e6a64f38429c0e32522e14055aa0bccfa Mon Sep 17 00:00:00 2001
From: Harry Lawrence 
Date: Sat, 4 Aug 2018 18:27:25 +0100
Subject: [PATCH 116/218] Added libiconv to search libs for OpenBSD patch
 (#3336)

---
 configure.ac | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/configure.ac b/configure.ac
index 8dce4f9f..39c228e3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -83,7 +83,8 @@ AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_ru
 
 AC_SEARCH_LIBS([shm_open], [rt])
 
-AC_SEARCH_LIBS([iconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])])
+AC_SEARCH_LIBS([iconv_open], [iconv], ,
+AC_SEARCH_LIBS([libiconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])]))
 
 AX_PTHREAD
 

From 37d0105c8328f718740c2ae9bae0dff87287a467 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Mon, 23 Apr 2018 12:20:05 +0300
Subject: [PATCH 117/218] Kill misbehaving subscribed clients instead of
 hanging

This change only affects clients that are subscribed to events, which
should be the main cause of our problems.

In the common case (no buffered data) the behaviour doesn't change at
all: the message is sent directly, no ev_io / ev_timeout callback is
enabled. Once a write to a client's socket is not completed fully
(returns with EAGAIN error), we put the message in the tail of a queue
and init an ev_io callback and a corresponding timer. If the timer is
triggered first, the socket is closed and the client connection is
removed. If the socket becomes writeable before the timeout we either
reset the timer if we couldn't push all the buffered data or completely
remove it if everything was pushed.

We could also replace ipc_send_message() for all client connections in
i3, not just those subscribed to events.

Furthermore, we could limit the amount of messages stored and increase
the timeout (or use multiple timeouts): eg it's ok if a client is not
reading for 10 seconds and we are only holding 5KB of messages for them
but it is not ok if they are inactive for 5 seconds and we have 30MB of
messages held.

Closes #2999
Closes #2539
---
 docs/ipc                                     |   8 +
 include/config_directives.h                  |   1 +
 include/ipc.h                                |  11 ++
 include/libi3.h                              |   8 +
 libi3/safewrappers.c                         |  22 ++-
 parser-specs/config.spec                     |   6 +
 src/config_directives.c                      |   4 +
 src/ipc.c                                    | 162 ++++++++++++++++++-
 testcases/t/201-config-parser.t              |   1 +
 testcases/t/298-ipc-misbehaving-connection.t |  69 ++++++++
 10 files changed, 287 insertions(+), 5 deletions(-)
 create mode 100644 testcases/t/298-ipc-misbehaving-connection.t

diff --git a/docs/ipc b/docs/ipc
index e44ffd7c..bcf8df1a 100644
--- a/docs/ipc
+++ b/docs/ipc
@@ -693,6 +693,14 @@ program does not want to cope which such kinds of race conditions (an
 event based library may not have a problem here), I suggest you create a
 separate connection to receive events.
 
+If an event message needs to be sent and the socket is not writeable (write
+returns EAGAIN, happens when the socket doesn't have enough buffer space for
+writing new data) then i3 uses a queue system to store outgoing messages for
+each client. This is combined with a timer: if the message queue for a client is
+not empty and no data where successfully written in the past 10 seconds, the
+connection is killed. Practically, this means that your client should try to
+always read events from the socket to avoid having its connection closed.
+
 === Subscribing to events
 
 By sending a message of type SUBSCRIBE with a JSON-encoded array as payload
diff --git a/include/config_directives.h b/include/config_directives.h
index f21ad8e1..4a20a1f5 100644
--- a/include/config_directives.h
+++ b/include/config_directives.h
@@ -62,6 +62,7 @@ CFGFUN(assign_output, const char *output);
 CFGFUN(assign, const char *workspace, bool is_number);
 CFGFUN(no_focus);
 CFGFUN(ipc_socket, const char *path);
+CFGFUN(ipc_kill_timeout, const long timeout_ms);
 CFGFUN(restart_state, const char *path);
 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);
diff --git a/include/ipc.h b/include/ipc.h
index c6ad35c7..a1caea82 100644
--- a/include/ipc.h
+++ b/include/ipc.h
@@ -35,6 +35,11 @@ typedef struct ipc_client {
      * event has been sent by i3. */
     bool first_tick_sent;
 
+    struct ev_io *callback;
+    struct ev_timer *timeout;
+    uint8_t *buffer;
+    size_t buffer_size;
+
     TAILQ_ENTRY(ipc_client)
     clients;
 } ipc_client;
@@ -124,3 +129,9 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig);
  * For the binding events, we send the serialized binding struct.
  */
 void ipc_send_binding_event(const char *event_type, Binding *bind);
+
+/**
+  * Set the maximum duration that we allow for a connection with an unwriteable
+  * socket.
+  */
+void ipc_set_kill_timeout(ev_tstamp new);
diff --git a/include/libi3.h b/include/libi3.h
index ebddee96..d3b40796 100644
--- a/include/libi3.h
+++ b/include/libi3.h
@@ -166,6 +166,14 @@ int sasprintf(char **strp, const char *fmt, ...);
  */
 ssize_t writeall(int fd, const void *buf, size_t count);
 
+/**
+ * Like writeall, but instead of retrying upon EAGAIN (returned when a write
+ * would block), the function stops and returns the total number of bytes
+ * written so far.
+ *
+ */
+ssize_t writeall_nonblock(int fd, const void *buf, size_t count);
+
 /**
  * Safe-wrapper around writeall which exits if it returns -1 (meaning that
  * write failed)
diff --git a/libi3/safewrappers.c b/libi3/safewrappers.c
index 94ad4ee6..04bbda44 100644
--- a/libi3/safewrappers.c
+++ b/libi3/safewrappers.c
@@ -68,10 +68,9 @@ int sasprintf(char **strp, const char *fmt, ...) {
 
 ssize_t writeall(int fd, const void *buf, size_t count) {
     size_t written = 0;
-    ssize_t n = 0;
 
     while (written < count) {
-        n = write(fd, buf + written, count - written);
+        const ssize_t n = write(fd, buf + written, count - written);
         if (n == -1) {
             if (errno == EINTR || errno == EAGAIN)
                 continue;
@@ -83,6 +82,25 @@ ssize_t writeall(int fd, const void *buf, size_t count) {
     return written;
 }
 
+ssize_t writeall_nonblock(int fd, const void *buf, size_t count) {
+    size_t written = 0;
+
+    while (written < count) {
+        const ssize_t n = write(fd, buf + written, count - written);
+        if (n == -1) {
+            if (errno == EAGAIN) {
+                return written;
+            } else if (errno == EINTR) {
+                continue;
+            } else {
+                return n;
+            }
+        }
+        written += (size_t)n;
+    }
+    return written;
+}
+
 ssize_t swrite(int fd, const void *buf, size_t count) {
     ssize_t n;
 
diff --git a/parser-specs/config.spec b/parser-specs/config.spec
index b15c9a80..5cdb7c32 100644
--- a/parser-specs/config.spec
+++ b/parser-specs/config.spec
@@ -48,6 +48,7 @@ state INITIAL:
   'show_marks'                             -> SHOW_MARKS
   'workspace'                              -> WORKSPACE
   'ipc_socket', 'ipc-socket'               -> IPC_SOCKET
+  'ipc_kill_timeout'                       -> IPC_KILL_TIMEOUT
   'restart_state'                          -> RESTART_STATE
   'popup_during_fullscreen'                -> POPUP_DURING_FULLSCREEN
   exectype = 'exec_always', 'exec'         -> EXEC
@@ -281,6 +282,11 @@ state IPC_SOCKET:
   path = string
       -> call cfg_ipc_socket($path)
 
+# ipc_kill_timeout
+state IPC_KILL_TIMEOUT:
+  timeout = number
+      -> call cfg_ipc_kill_timeout(&timeout)
+
 # restart_state  (for testcases)
 state RESTART_STATE:
   path = string
diff --git a/src/config_directives.c b/src/config_directives.c
index 17e25cde..dfbb52d8 100644
--- a/src/config_directives.c
+++ b/src/config_directives.c
@@ -446,6 +446,10 @@ CFGFUN(no_focus) {
     TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
 }
 
+CFGFUN(ipc_kill_timeout, const long timeout_ms) {
+    ipc_set_kill_timeout(timeout_ms / 1000.0);
+}
+
 /*******************************************************************************
  * Bar configuration (i3bar)
  ******************************************************************************/
diff --git a/src/ipc.c b/src/ipc.c
index 240119bc..977fe377 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -38,9 +38,39 @@ static void set_nonblock(int sockfd) {
         err(-1, "Could not set O_NONBLOCK");
 }
 
+/*
+ * Given a message and a message type, create the corresponding header, merge it
+ * with the message and append it to the given client's output buffer.
+ *
+ */
+static void append_payload(ipc_client *client, uint32_t message_type, const char *payload) {
+    const size_t size = strlen(payload);
+    const i3_ipc_header_t header = {
+        .magic = {'i', '3', '-', 'i', 'p', 'c'},
+        .size = size,
+        .type = message_type};
+    const size_t header_size = sizeof(i3_ipc_header_t);
+    const size_t message_size = header_size + size;
+
+    client->buffer = srealloc(client->buffer, client->buffer_size + message_size);
+    memcpy(client->buffer + client->buffer_size, ((void *)&header), header_size);
+    memcpy(client->buffer + client->buffer_size + header_size, payload, size);
+    client->buffer_size += message_size;
+}
+
 static void free_ipc_client(ipc_client *client) {
     close(client->fd);
-    for (int i = 0; i < client->num_events; i++){
+
+    ev_io_stop(main_loop, client->callback);
+    FREE(client->callback);
+    if (client->timeout) {
+        ev_timer_stop(main_loop, client->timeout);
+        FREE(client->timeout);
+    }
+
+    free(client->buffer);
+
+    for (int i = 0; i < client->num_events; i++) {
         free(client->events[i]);
     }
     free(client->events);
@@ -48,6 +78,68 @@ static void free_ipc_client(ipc_client *client) {
     free(client);
 }
 
+static void ipc_client_timeout(EV_P_ ev_timer *w, int revents);
+static void ipc_socket_writeable_cb(EV_P_ struct ev_io *w, int revents);
+
+static ev_tstamp kill_timeout = 10.0;
+
+void ipc_set_kill_timeout(ev_tstamp new) {
+    kill_timeout = new;
+}
+
+/*
+ * Try to write the contents of the pending buffer to the client's subscription
+ * socket. Will set, reset or clear the timeout and io callbacks depending on
+ * the result of the write operation.
+ *
+ */
+static void ipc_push_pending(ipc_client *client) {
+    const ssize_t result = writeall_nonblock(client->fd, client->buffer, client->buffer_size);
+    if (result < 0) {
+        return;
+    }
+
+    if ((size_t)result == client->buffer_size) {
+        /* Everything was written successfully: clear the timer and stop the io
+         * callback. */
+        FREE(client->buffer);
+        client->buffer_size = 0;
+        if (client->timeout) {
+            ev_timer_stop(main_loop, client->timeout);
+            FREE(client->timeout);
+        }
+        ev_io_stop(main_loop, client->callback);
+        return;
+    }
+
+    /* Otherwise, make sure that the io callback is enabled and create a new
+     * timer if needed. */
+    ev_io_start(main_loop, client->callback);
+
+    if (!client->timeout) {
+        struct ev_timer *timeout = scalloc(1, sizeof(struct ev_timer));
+        ev_timer_init(timeout, ipc_client_timeout, kill_timeout, 0.);
+        timeout->data = client;
+        client->timeout = timeout;
+        ev_set_priority(timeout, EV_MINPRI);
+        ev_timer_start(main_loop, client->timeout);
+    } else if (result > 0) {
+        /* Keep the old timeout when nothing is written. Otherwise, we would
+         * keep a dead connection by continuously renewing its timeouts. */
+        ev_timer_stop(main_loop, client->timeout);
+        ev_timer_set(client->timeout, kill_timeout, 0.0);
+        ev_timer_start(main_loop, client->timeout);
+    }
+    if (result == 0) {
+        return;
+    }
+
+    /* Shift the buffer to the left and reduce the allocated space. */
+    client->buffer_size -= (size_t)result;
+    memmove(client->buffer, client->buffer + result, client->buffer_size);
+    client->buffer = srealloc(client->buffer, client->buffer_size);
+}
+
 /*
  * Sends the specified event to all IPC clients which are currently connected
  * and subscribed to this kind of event.
@@ -67,7 +159,11 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa
         if (!interested)
             continue;
 
-        ipc_send_message(current->fd, strlen(payload), message_type, (const uint8_t *)payload);
+        const bool push_now = (current->buffer_size == 0);
+        append_payload(current, message_type, payload);
+        if (push_now) {
+            ipc_push_pending(current);
+        }
     }
 }
 
@@ -1286,6 +1382,60 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
     FREE(message);
 }
 
+static void ipc_client_timeout(EV_P_ ev_timer *w, int revents) {
+    /* No need to be polite and check for writeability, the other callback would
+     * have been called by now. */
+    ipc_client *client = (ipc_client *)w->data;
+
+    char *cmdline = NULL;
+#if defined(__linux__) && defined(SO_PEERCRED)
+    struct ucred peercred;
+    socklen_t so_len = sizeof(peercred);
+    if (getsockopt(client->fd, SOL_SOCKET, SO_PEERCRED, &peercred, &so_len) != 0) {
+        goto end;
+    }
+    char *exepath;
+    sasprintf(&exepath, "/proc/%d/cmdline", peercred.pid);
+
+    int fd = open(exepath, O_RDONLY);
+    free(exepath);
+    if (fd == -1) {
+        goto end;
+    }
+    char buf[512] = {'\0'}; /* cut off cmdline for the error message. */
+    const ssize_t n = read(fd, buf, sizeof(buf));
+    close(fd);
+    if (n < 0) {
+        goto end;
+    }
+    for (char *walk = buf; walk < buf + n - 1; walk++) {
+        if (*walk == '\0') {
+            *walk = ' ';
+        }
+    }
+    cmdline = buf;
+#endif
+
+end:
+    if (cmdline) {
+        ELOG("client %p with pid %d and cmdline '%s' on fd %d timed out, killing\n", client, peercred.pid, cmdline, client->fd);
+    } else {
+        ELOG("client %p on fd %d timed out, killing\n", client, client->fd);
+    }
+
+    free_ipc_client(client);
+}
+
+static void ipc_socket_writeable_cb(EV_P_ ev_io *w, int revents) {
+    DLOG("fd %d writeable\n", w->fd);
+    ipc_client *client = (ipc_client *)w->data;
+
+    /* If this callback is called then there should be a corresponding active
+     * timer. */
+    assert(client->timeout != NULL);
+    ipc_push_pending(client);
+}
+
 /*
  * Handler for activity on the listening socket, meaning that a new client
  * has just connected and we should accept() him. Sets up the event handler
@@ -1314,10 +1464,16 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
     ev_io_init(package, ipc_receive_message, client, EV_READ);
     ev_io_start(EV_A_ package);
 
+    ipc_client *new = scalloc(1, sizeof(ipc_client));
+
+    package = scalloc(1, sizeof(struct ev_io));
+    package->data = new;
+    ev_io_init(package, ipc_socket_writeable_cb, client, EV_WRITE);
+
     DLOG("IPC: new client connected on fd %d\n", w->fd);
 
-    ipc_client *new = scalloc(1, sizeof(ipc_client));
     new->fd = client;
+    new->callback = package;
 
     TAILQ_INSERT_TAIL(&all_clients, new, clients);
 }
diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t
index 1e7ebfc4..a58f33c1 100644
--- a/testcases/t/201-config-parser.t
+++ b/testcases/t/201-config-parser.t
@@ -501,6 +501,7 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: , '
         workspace
         ipc_socket
         ipc-socket
+        ipc_kill_timeout
         restart_state
         popup_during_fullscreen
         exec_always
diff --git a/testcases/t/298-ipc-misbehaving-connection.t b/testcases/t/298-ipc-misbehaving-connection.t
new file mode 100644
index 00000000..d53ee92d
--- /dev/null
+++ b/testcases/t/298-ipc-misbehaving-connection.t
@@ -0,0 +1,69 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Test that i3 will not hang if a connected client stops reading from its
+# subscription socket and that the client is killed after a delay.
+# Ticket: #2999
+# Bug still in: 4.15-180-g715cea61
+use i3test i3_config => <new(Peer => get_socket_path());
+my $magic = "i3-ipc";
+my $payload = '["workspace"]';
+my $message = $magic . pack("LL", length($payload), 2) . $payload;
+print $sock $message;
+
+# Constantly switch between 2 workspaces to generate events.
+fresh_workspace;
+open_window;
+fresh_workspace;
+open_window;
+
+eval {
+    local $SIG{ALRM} = sub { die "Timeout\n" };
+    # 500 is an arbitrarily large number to make sure that the socket becomes
+    # non-writeable.
+    for (my $i = 0; $i < 500; $i++) {
+        alarm 1;
+        cmd 'workspace back_and_forth';
+        alarm 0;
+    }
+};
+ok(!$@, 'i3 didn\'t hang');
+
+# Wait for connection timeout
+sleep 1;
+
+use IO::Select;
+my $s = IO::Select->new($sock);
+my $reached_eof = 0;
+while ($s->can_read(0.05)) {
+    if (read($sock, my $buffer, 100) == 0) {
+        $reached_eof = 1;
+        last;
+    }
+}
+ok($reached_eof, 'socket connection closed');
+
+close $sock;
+done_testing;

From 01e971b51e220d63ec460a91d4039d7a0e265f5d Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 9 Aug 2018 17:28:05 +0300
Subject: [PATCH 118/218] Free ws_assignments on reload

This fixes an unreported bug where deleting a workspace assignment and
reloading the config file would keep the assignment.
---
 src/config.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/config.c b/src/config.c
index ab19bae3..5be1fd52 100644
--- a/src/config.c
+++ b/src/config.c
@@ -97,9 +97,8 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
             FREE(mode);
         }
 
-        struct Assignment *assign;
         while (!TAILQ_EMPTY(&assignments)) {
-            assign = TAILQ_FIRST(&assignments);
+            struct Assignment *assign = TAILQ_FIRST(&assignments);
             if (assign->type == A_TO_WORKSPACE || assign->type == A_TO_WORKSPACE_NUMBER)
                 FREE(assign->dest.workspace);
             else if (assign->type == A_COMMAND)
@@ -111,6 +110,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
             FREE(assign);
         }
 
+        while (!TAILQ_EMPTY(&ws_assignments)) {
+            struct Workspace_Assignment *assign = TAILQ_FIRST(&ws_assignments);
+            FREE(assign->name);
+            FREE(assign->output);
+            TAILQ_REMOVE(&ws_assignments, assign, ws_assignments);
+            FREE(assign);
+        }
+
         /* Clear bar configs */
         Barconfig *barconfig;
         while (!TAILQ_EMPTY(&barconfigs)) {

From cac28b69d46668aded8c9977ba3dab37e966ed50 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 17 Aug 2018 12:34:20 +0300
Subject: [PATCH 119/218] Call con_activate after moving scratchpad window to
 current ws

Fixes #3361
---
 src/scratchpad.c                           |  1 +
 testcases/t/299-regress-scratchpad-focus.t | 33 ++++++++++++++++++++++
 2 files changed, 34 insertions(+)
 create mode 100644 testcases/t/299-regress-scratchpad-focus.t

diff --git a/src/scratchpad.c b/src/scratchpad.c
index 2774396f..d564bf32 100644
--- a/src/scratchpad.c
+++ b/src/scratchpad.c
@@ -141,6 +141,7 @@ bool scratchpad_show(Con *con) {
             DLOG("Found a visible scratchpad window on another workspace,\n");
             DLOG("moving it to this workspace: con = %p\n", walk_con);
             con_move_to_workspace(walk_con, focused_ws, true, false, false);
+            con_activate(con_descend_focused(walk_con));
             return true;
         }
     }
diff --git a/testcases/t/299-regress-scratchpad-focus.t b/testcases/t/299-regress-scratchpad-focus.t
new file mode 100644
index 00000000..504ecd32
--- /dev/null
+++ b/testcases/t/299-regress-scratchpad-focus.t
@@ -0,0 +1,33 @@
+#!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)
+#
+# Regression test: verify that a scratchpad container that was open in another
+# workspace and is moved to the current workspace after a 'scratchpad show' is
+# focused.
+# Ticket: #3361
+# Bug still in: 4.15-190-g4b3ff9cd
+use i3test;
+
+my $expected_focus = open_window;
+cmd 'move to scratchpad';
+cmd 'scratchpad show';
+my $ws = fresh_workspace;
+open_window;
+cmd 'scratchpad show';
+sync_with_i3;
+is($x->input_focus, $expected_focus->id, 'scratchpad window brought from other workspace is focused');
+
+done_testing;

From 7010644964a84666453cb8ddae1ee6e5b56562ed Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 17 Aug 2018 06:06:56 +0300
Subject: [PATCH 120/218] randr_query_outputs: con_activate -> workspace_show

workspace_show will call x_set_warp_to if needed.

Fixes #3333
---
 src/randr.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/randr.c b/src/randr.c
index 38f1ee97..44071a53 100644
--- a/src/randr.c
+++ b/src/randr.c
@@ -946,7 +946,9 @@ void randr_query_outputs(void) {
             continue;
 
         DLOG("Focusing primary output %s\n", output_primary_name(output));
-        con_activate(con_descend_focused(output->con));
+        Con *content = output_get_content(output->con);
+        Con *ws = TAILQ_FIRST(&(content)->focus_head);
+        workspace_show(ws);
     }
 
     /* render_layout flushes */

From ba29290123762399a8497ac5c798a60e172c8a46 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 17 Aug 2018 06:45:20 +0300
Subject: [PATCH 121/218] Typo: output -> content

---
 src/output.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/output.c b/src/output.c
index 571c01cf..19a7c4af 100644
--- a/src/output.c
+++ b/src/output.c
@@ -10,7 +10,7 @@
 #include "all.h"
 
 /*
- * Returns the output container below the given output container.
+ * Returns the content container below the given output container.
  *
  */
 Con *output_get_content(Con *output) {

From 789a09a6e73ff6c692fa04c36a4a27d2fc8359fd Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 17 Aug 2018 06:45:25 +0300
Subject: [PATCH 122/218] Use con_focus instead of con_activate for workspace

---
 src/randr.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/randr.c b/src/randr.c
index 44071a53..b1a9e8cb 100644
--- a/src/randr.c
+++ b/src/randr.c
@@ -517,7 +517,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_activate(ws);
+    con_focus(ws);
 }
 
 /*

From b1aa2fb1c441d7c6d8e28d3a53bd0b61374d4c25 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Tue, 21 Aug 2018 21:04:46 +0300
Subject: [PATCH 123/218] property_notify: use NUM_HANDLERS

---
 src/handlers.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/handlers.c b/src/handlers.c
index 38c2caf1..2c81939c 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -1441,7 +1441,7 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom)
     struct property_handler_t *handler = NULL;
     xcb_get_property_reply_t *propr = NULL;
 
-    for (size_t c = 0; c < sizeof(property_handlers) / sizeof(struct property_handler_t); c++) {
+    for (size_t c = 0; c < NUM_HANDLERS; c++) {
         if (property_handlers[c].atom != atom)
             continue;
 

From 5d89bd344fb325c2a74cf962f3f931ac274c128e Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Tue, 21 Aug 2018 21:06:00 +0300
Subject: [PATCH 124/218] floating_drag_window: return on DRAG_REVERT

Right now tree_render() is called twice on DRAG_REVERT since
floating_reposition calls it.
Also, on DRAG_REVERT the scratchpad state shouldn't change since the
user canceled the action.
---
 src/floating.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/floating.c b/src/floating.c
index 9c15bb10..7ba7c9fb 100644
--- a/src/floating.c
+++ b/src/floating.c
@@ -537,8 +537,10 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
     }
 
     /* If the user cancelled, undo the changes. */
-    if (drag_result == DRAG_REVERT)
+    if (drag_result == DRAG_REVERT) {
         floating_reposition(con, initial_rect);
+        return;
+    }
 
     /* If this is a scratchpad window, don't auto center it from now on. */
     if (con->scratchpad_state == SCRATCHPAD_FRESH)

From 2c78f2458c17c9de08cacf255e82abd53adbe45a Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Tue, 21 Aug 2018 21:10:02 +0300
Subject: [PATCH 125/218] Call dragloop callback on DRAG_SUCCESS

A race condition is possible. For example, if we first receive a
XCB_MOTION_NOTIFY event and then, while drain_drag_events is still
running, a XCB_BUTTON_RELEASE event the first event is never handled
because we return.

This fixes the flakiness of the tests in #3085.
---
 src/floating.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/floating.c b/src/floating.c
index 9c15bb10..279d4c99 100644
--- a/src/floating.c
+++ b/src/floating.c
@@ -742,9 +742,14 @@ static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
             free(event);
 
         if (dragloop->result != DRAGGING) {
-            free(last_motion_notify);
             ev_break(EV_A_ EVBREAK_ONE);
-            return true;
+            if (dragloop->result == DRAG_SUCCESS) {
+                /* Ensure motion notify events are handled. */
+                break;
+            } else {
+                free(last_motion_notify);
+                return true;
+            }
         }
     }
 
@@ -766,7 +771,7 @@ static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
     FREE(last_motion_notify);
 
     xcb_flush(conn);
-    return false;
+    return dragloop->result != DRAGGING;
 }
 
 static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {

From b3e69ed12a5c62b74501b9f375ddf742fb9fb44c Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Wed, 22 Aug 2018 03:40:51 +0300
Subject: [PATCH 126/218] render_root: fix popup_during_fullscreen logic

The first issue is that there seems to be a typo: fullscreen->window
should have been child->window. The corrected check is redundant since
the while loop checks if the transient_con has a window.

The second issue is that popup_during_fullscreen is never checked even
though the behaviour should be exclusive to the "smart" option.
---
 src/render.c | 23 ++++++++++-------------
 1 file changed, 10 insertions(+), 13 deletions(-)

diff --git a/src/render.c b/src/render.c
index 0125b89d..f54094bb 100644
--- a/src/render.c
+++ b/src/render.c
@@ -235,22 +235,19 @@ static void render_root(Con *con, Con *fullscreen) {
         Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
         Con *child;
         TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) {
-            /* Don’t render floating windows when there is a fullscreen window
-             * on that workspace. Necessary to make floating fullscreen work
-             * correctly (ticket #564). */
-            /* If there is no fullscreen->window, this cannot be a
-             * transient window, so we _know_ we need to skip it. This
-             * happens during restarts where the container already exists,
-             * but the window was not yet associated. */
-            if (fullscreen != NULL && fullscreen->window == NULL)
-                continue;
-            if (fullscreen != NULL && fullscreen->window != NULL) {
+            if (fullscreen != NULL) {
+                /* Don’t render floating windows when there is a fullscreen
+                 * window on that workspace. Necessary to make floating
+                 * fullscreen work correctly (ticket #564). Exception to the
+                 * above rule: smart popup_during_fullscreen handling (popups
+                 * belonging to the fullscreen app will be rendered). */
+                if (config.popup_during_fullscreen != PDF_SMART) {
+                    continue;
+                }
+
                 Con *floating_child = con_descend_focused(child);
                 Con *transient_con = floating_child;
                 bool is_transient_for = false;
-                /* Exception to the above rule: smart
-                 * popup_during_fullscreen handling (popups belonging to
-                 * the fullscreen app will be rendered). */
                 while (transient_con != NULL &&
                        transient_con->window != NULL &&
                        transient_con->window->transient_for != XCB_NONE) {

From efc78de4eee704672c09b5652afce4739e1e54f1 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Wed, 22 Aug 2018 14:02:27 +0300
Subject: [PATCH 127/218] Introduce con_get_fullscreen_covering_ws

This commit will also fix the following bugs:
1. click.c: Users could drag global fullscreen floating containers.
2. render.c: Floating containers would get rendered with a global fullscreen container in another
workspace.
---
 include/con.h  |  8 ++++++++
 src/click.c    |  2 +-
 src/commands.c |  2 +-
 src/con.c      | 17 +++++++++++++++++
 src/handlers.c | 12 ++----------
 src/manage.c   |  4 +---
 src/render.c   |  2 +-
 7 files changed, 31 insertions(+), 16 deletions(-)

diff --git a/include/con.h b/include/con.h
index 58123a87..672d80ac 100644
--- a/include/con.h
+++ b/include/con.h
@@ -120,6 +120,14 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation);
  */
 Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode);
 
+/**
+ * Returns the fullscreen node that covers the given workspace if it exists.
+ * This is either a CF_GLOBAL fullscreen container anywhere or a CF_OUTPUT
+ * fullscreen container in the workspace.
+ *
+ */
+Con *con_get_fullscreen_covering_ws(Con *ws);
+
 /**
  * Returns true if the container is internal, such as __i3_scratch
  *
diff --git a/src/click.c b/src/click.c
index 81e95dfa..1218a4c2 100644
--- a/src/click.c
+++ b/src/click.c
@@ -254,7 +254,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
 
     /* 3: For floating containers, we also want to raise them on click.
      * We will skip handling events on floating cons in fullscreen mode */
-    Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
+    Con *fs = con_get_fullscreen_covering_ws(ws);
     if (floatingcon != NULL && fs != con) {
         /* 4: floating_modifier plus left mouse button drags */
         if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) {
diff --git a/src/commands.c b/src/commands.c
index ad7d9770..22ec6c3f 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -1282,7 +1282,7 @@ void cmd_focus_direction(I3_CMD, const char *direction) {
 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);
+    Con *fullscreen_on_ws = con_get_fullscreen_covering_ws(ws);
     if (fullscreen_on_ws && fullscreen_on_ws != con && !con_has_parent(con, fullscreen_on_ws)) {
         con_disable_fullscreen(fullscreen_on_ws);
     }
diff --git a/src/con.c b/src/con.c
index f20f7c35..844d9b68 100644
--- a/src/con.c
+++ b/src/con.c
@@ -509,6 +509,23 @@ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) {
     return NULL;
 }
 
+/*
+ * Returns the fullscreen node that covers the given workspace if it exists.
+ * This is either a CF_GLOBAL fullscreen container anywhere or a CF_OUTPUT
+ * fullscreen container in the workspace.
+ *
+ */
+Con *con_get_fullscreen_covering_ws(Con *ws) {
+    if (!ws) {
+        return NULL;
+    }
+    Con *fs = con_get_fullscreen_con(croot, CF_GLOBAL);
+    if (!fs) {
+        return con_get_fullscreen_con(ws, CF_OUTPUT);
+    }
+    return fs;
+}
+
 /**
  * Returns true if the container is internal, such as __i3_scratch
  *
diff --git a/src/handlers.c b/src/handlers.c
index 2c81939c..fe10d4b9 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -310,16 +310,8 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
 
     DLOG("Configure request!\n");
 
-    Con *workspace = con_get_workspace(con),
-        *fullscreen = NULL;
-
-    /* There might not be a corresponding workspace for dock cons, therefore we
-     * have to be careful here. */
-    if (workspace) {
-        fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
-        if (!fullscreen)
-            fullscreen = con_get_fullscreen_con(workspace, CF_GLOBAL);
-    }
+    Con *workspace = con_get_workspace(con);
+    Con *fullscreen = con_get_fullscreen_covering_ws(workspace);
 
     if (fullscreen != con && con_is_floating(con) && con_is_leaf(con)) {
         /* find the height for the decorations */
diff --git a/src/manage.c b/src/manage.c
index b4d0af95..f97ecc62 100644
--- a/src/manage.c
+++ b/src/manage.c
@@ -378,9 +378,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
     /* handle fullscreen containers */
     Con *ws = con_get_workspace(nc);
-    Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
-    if (fs == NULL)
-        fs = con_get_fullscreen_con(croot, CF_GLOBAL);
+    Con *fs = con_get_fullscreen_covering_ws(ws);
 
     if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_FULLSCREEN)) {
         /* If this window is already fullscreen (after restarting!), skip
diff --git a/src/render.c b/src/render.c
index f54094bb..d6306260 100644
--- a/src/render.c
+++ b/src/render.c
@@ -232,7 +232,7 @@ static void render_root(Con *con, Con *fullscreen) {
             continue;
         }
         Con *workspace = TAILQ_FIRST(&(content->focus_head));
-        Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
+        Con *fullscreen = con_get_fullscreen_covering_ws(workspace);
         Con *child;
         TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) {
             if (fullscreen != NULL) {

From 0ac75bea5a21e9cac6ad2139bc57d44d1a77a6e0 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Wed, 22 Aug 2018 14:09:05 +0300
Subject: [PATCH 128/218] con_border_style: check con->fullscreen_mode directly

---
 src/con.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/con.c b/src/con.c
index 844d9b68..253d1d54 100644
--- a/src/con.c
+++ b/src/con.c
@@ -1711,8 +1711,7 @@ adjacent_t con_adjacent_borders(Con *con) {
  *
  */
 int con_border_style(Con *con) {
-    Con *fs = con_get_fullscreen_con(con->parent, CF_OUTPUT);
-    if (fs == con) {
+    if (con->fullscreen_mode == CF_OUTPUT || con->fullscreen_mode == CF_GLOBAL) {
         DLOG("this one is fullscreen! overriding BS_NONE\n");
         return BS_NONE;
     }

From 9ae84593baa6d5b963d127aac52d02445767cd42 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Wed, 22 Aug 2018 14:10:42 +0300
Subject: [PATCH 129/218] handle_configure_request: reuse 'workspace' variable

---
 src/handlers.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/src/handlers.c b/src/handlers.c
index fe10d4b9..c203517b 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -406,23 +406,22 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
             goto out;
         }
 
-        Con *ws = con_get_workspace(con);
-        if (ws == NULL) {
+        if (workspace == NULL) {
             DLOG("Window is not being managed, ignoring ConfigureRequest\n");
             goto out;
         }
 
-        if (strcmp(ws->name, "__i3_scratch") == 0) {
+        if (strcmp(workspace->name, "__i3_scratch") == 0) {
             DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
             goto out;
         }
 
-        if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
+        if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(workspace))) {
             DLOG("Focusing con = %p\n", con);
-            workspace_show(ws);
+            workspace_show(workspace);
             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))) {
+        } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(workspace))) {
             DLOG("Marking con = %p urgent\n", con);
             con_set_urgency(con, true);
             tree_render();

From 9190a9ab702463c98fe3c35ecab4311b4e6c8d6e Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Wed, 22 Aug 2018 14:11:26 +0300
Subject: [PATCH 130/218] handle_configure_request: use 'goto out'

---
 src/handlers.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/handlers.c b/src/handlers.c
index c203517b..66b3038a 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -327,7 +327,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
 
         if (strcmp(con_get_workspace(floatingcon)->name, "__i3_scratch") == 0) {
             DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
-            return;
+            goto out;
         }
 
         Rect newrect = floatingcon->rect;
@@ -387,15 +387,14 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
                 DLOG("Dock client will not be moved, we only support moving it to another output.\n");
             }
         }
-        fake_absolute_configure_notify(con);
-        return;
+        goto out;
     }
 
     if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) {
         DLOG("window 0x%08x wants to be stacked %d\n", event->window, event->stack_mode);
 
         /* Emacs and IntelliJ Idea “request focus” by stacking their window
-             * above all others. */
+         * above all others. */
         if (event->stack_mode != XCB_STACK_MODE_ABOVE) {
             DLOG("stack_mode != XCB_STACK_MODE_ABOVE, ignoring ConfigureRequest\n");
             goto out;

From e1a83d057f81bf402c73ac051abde27832500bfe Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Wed, 22 Aug 2018 14:23:01 +0300
Subject: [PATCH 131/218] handle_configure_request: check for scratchpad once

---
 src/handlers.c | 15 ++++-----------
 1 file changed, 4 insertions(+), 11 deletions(-)

diff --git a/src/handlers.c b/src/handlers.c
index 66b3038a..f632d91c 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -311,6 +311,10 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
     DLOG("Configure request!\n");
 
     Con *workspace = con_get_workspace(con);
+    if (workspace && (strcmp(workspace->name, "__i3_scratch") == 0)) {
+        DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
+        goto out;
+    }
     Con *fullscreen = con_get_fullscreen_covering_ws(workspace);
 
     if (fullscreen != con && con_is_floating(con) && con_is_leaf(con)) {
@@ -324,12 +328,6 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
             bsr.height -= deco_height;
         }
         Con *floatingcon = con->parent;
-
-        if (strcmp(con_get_workspace(floatingcon)->name, "__i3_scratch") == 0) {
-            DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
-            goto out;
-        }
-
         Rect newrect = floatingcon->rect;
 
         if (event->value_mask & XCB_CONFIG_WINDOW_X) {
@@ -410,11 +408,6 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
             goto out;
         }
 
-        if (strcmp(workspace->name, "__i3_scratch") == 0) {
-            DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
-            goto out;
-        }
-
         if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(workspace))) {
             DLOG("Focusing con = %p\n", con);
             workspace_show(workspace);

From e6202d43f5fc2048b4870203512ec829e0e231fe Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Wed, 22 Aug 2018 14:51:17 +0300
Subject: [PATCH 132/218] Apply compatible changes from clang-format 6.0.1

These are the changes that clang-format 6.0.1 makes to the codebase that
clang-format-3.8 doesn't change back.

Useful for those that use a more recent version of clang-format in their
local machines.
---
 i3-config-wizard/main.c |  3 +--
 libi3/get_colorpixel.c  |  3 +--
 src/floating.c          |  3 +--
 src/handlers.c          | 12 ++++--------
 4 files changed, 7 insertions(+), 14 deletions(-)

diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c
index f3c33034..4a843fae 100644
--- a/i3-config-wizard/main.c
+++ b/i3-config-wizard/main.c
@@ -951,13 +951,12 @@ int main(int argc, char *argv[]) {
         /* Strip off the highest bit (set if the event is generated) */
         int type = (event->response_type & 0x7F);
 
+        /* TODO: handle mappingnotify */
         switch (type) {
             case XCB_KEY_PRESS:
                 handle_key_press(NULL, conn, (xcb_key_press_event_t *)event);
                 break;
 
-            /* TODO: handle mappingnotify */
-
             case XCB_BUTTON_PRESS:
                 handle_button_press((xcb_button_press_event_t *)event);
                 break;
diff --git a/libi3/get_colorpixel.c b/libi3/get_colorpixel.c
index 9bdd0a71..afba202b 100644
--- a/libi3/get_colorpixel.c
+++ b/libi3/get_colorpixel.c
@@ -60,8 +60,7 @@ uint32_t get_colorpixel(const char *hex) {
 
     xcb_alloc_color_reply_t *reply;
 
-    reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, root_screen->default_colormap,
-                                                        r16, g16, b16),
+    reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, root_screen->default_colormap, r16, g16, b16),
                                   NULL);
 
     if (!reply) {
diff --git a/src/floating.c b/src/floating.c
index 340ef864..08041334 100644
--- a/src/floating.c
+++ b/src/floating.c
@@ -791,8 +791,7 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
  * rect of the client, the event and the new coordinates (x, y).
  *
  */
-drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
-                                                                                confine_to,
+drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to,
                            border_t border, int cursor, callback_t callback, const void *extra) {
     xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE;
 
diff --git a/src/handlers.c b/src/handlers.c
index 2c81939c..5d0a6c62 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -1141,8 +1141,7 @@ static bool handle_transient_for(void *data, xcb_connection_t *conn, uint8_t sta
     }
 
     if (prop == NULL) {
-        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
-                                                                       false, window, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 0, 32),
+        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 0, 32),
                                       NULL);
         if (prop == NULL)
             return false;
@@ -1165,8 +1164,7 @@ static bool handle_clientleader_change(void *data, xcb_connection_t *conn, uint8
         return false;
 
     if (prop == NULL) {
-        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
-                                                                       false, window, A_WM_CLIENT_LEADER, XCB_ATOM_WINDOW, 0, 32),
+        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, A_WM_CLIENT_LEADER, XCB_ATOM_WINDOW, 0, 32),
                                       NULL);
         if (prop == NULL)
             return false;
@@ -1266,8 +1264,7 @@ static bool handle_class_change(void *data, xcb_connection_t *conn, uint8_t stat
         return false;
 
     if (prop == NULL) {
-        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
-                                                                       false, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 32),
+        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 32),
                                       NULL);
 
         if (prop == NULL)
@@ -1290,8 +1287,7 @@ static bool handle_motif_hints_change(void *data, xcb_connection_t *conn, uint8_
         return false;
 
     if (prop == NULL) {
-        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
-                                                                       false, window, A__MOTIF_WM_HINTS, XCB_GET_PROPERTY_TYPE_ANY, 0, 5 * sizeof(uint64_t)),
+        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, A__MOTIF_WM_HINTS, XCB_GET_PROPERTY_TYPE_ANY, 0, 5 * sizeof(uint64_t)),
                                       NULL);
 
         if (prop == NULL)

From 6e1b79e0573bce6544d457a071eaf1adefa1d7ff Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 23 Aug 2018 15:36:23 +0300
Subject: [PATCH 133/218] Introduce orientation_from_direction

---
 include/util.h | 6 ++++++
 src/move.c     | 2 +-
 src/resize.c   | 2 +-
 src/util.c     | 8 ++++++++
 4 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/include/util.h b/include/util.h
index d58e21b7..90ddc4a4 100644
--- a/include/util.h
+++ b/include/util.h
@@ -174,3 +174,9 @@ bool parse_long(const char *str, long *out, int base);
  *
  */
 ssize_t slurp(const char *path, char **buf);
+
+/**
+ * Convert a direction to its corresponding orientation.
+ *
+ */
+orientation_t orientation_from_direction(direction_t direction);
diff --git a/src/move.c b/src/move.c
index 5bff3dae..32178e6b 100644
--- a/src/move.c
+++ b/src/move.c
@@ -265,7 +265,7 @@ void tree_move(Con *con, int direction) {
         return;
     }
 
-    orientation_t o = (direction == D_LEFT || direction == D_RIGHT ? HORIZ : VERT);
+    orientation_t o = orientation_from_direction(direction);
 
     Con *same_orientation = con_parent_with_orientation(con, o);
     /* The do {} while is used to 'restart' at this point with a different
diff --git a/src/resize.c b/src/resize.c
index ee50bfbc..184dc7c5 100644
--- a/src/resize.c
+++ b/src/resize.c
@@ -57,7 +57,7 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
     }
 
     /* Go up in the tree and search for a container to resize */
-    const orientation_t search_orientation = ((direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT);
+    const orientation_t search_orientation = orientation_from_direction(direction);
     const bool dir_backwards = (direction == D_UP || direction == D_LEFT);
     while (first->type != CT_WORKSPACE &&
            first->type != CT_FLOATING_CON &&
diff --git a/src/util.c b/src/util.c
index dc3444f7..a59283a8 100644
--- a/src/util.c
+++ b/src/util.c
@@ -507,3 +507,11 @@ ssize_t slurp(const char *path, char **buf) {
     }
     return (ssize_t)n;
 }
+
+/*
+ * Convert a direction to its corresponding orientation.
+ *
+ */
+orientation_t orientation_from_direction(direction_t direction) {
+    return (direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT;
+}

From 9522b46f1b78d1020285f53d2053ccd6c800c62e Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 23 Aug 2018 15:36:50 +0300
Subject: [PATCH 134/218] Introduce parse_direction

Also fixes the following bug: in the fix for #1011 in
cmd_resize_floating direction "width" is not considered.

Influenced by #3240.
---
 include/commands.h |   2 +-
 src/commands.c     | 131 +++++++++++++++++++++++++++------------------
 2 files changed, 79 insertions(+), 54 deletions(-)

diff --git a/include/commands.h b/include/commands.h
index aaa0875f..b4b3da38 100644
--- a/include/commands.h
+++ b/include/commands.h
@@ -216,7 +216,7 @@ void cmd_sticky(I3_CMD, const char *action);
  * Implementation of 'move  [ [px]]'.
  *
  */
-void cmd_move_direction(I3_CMD, const char *direction, long move_px);
+void cmd_move_direction(I3_CMD, const char *direction_str, long move_px);
 
 /**
  * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
diff --git a/src/commands.c b/src/commands.c
index ad7d9770..0a2f3424 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -419,71 +419,89 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no
     ysuccess(true);
 }
 
-static void cmd_resize_floating(I3_CMD, const char *way, const char *direction, Con *floating_con, int px) {
-    LOG("floating resize\n");
+/*
+ * Convert a string direction ("left", "right", etc.) to a direction_t. Assumes
+ * valid direction string.
+ */
+static direction_t parse_direction(const char *str) {
+    if (strcmp(str, "left") == 0) {
+        return D_LEFT;
+    } else if (strcmp(str, "right") == 0) {
+        return D_RIGHT;
+    } else if (strcmp(str, "up") == 0) {
+        return D_UP;
+    } else if (strcmp(str, "down") == 0) {
+        return D_DOWN;
+    } else {
+        ELOG("Invalid direction. This is a parser bug.\n");
+        assert(false);
+    }
+}
+
+static void cmd_resize_floating(I3_CMD, const char *way, const char *direction_str, Con *floating_con, int px) {
     Rect old_rect = floating_con->rect;
     Con *focused_con = con_descend_focused(floating_con);
 
+    direction_t direction;
+    if (strcmp(direction_str, "height") == 0) {
+        direction = D_DOWN;
+    } else if (strcmp(direction_str, "width") == 0) {
+        direction = D_RIGHT;
+    } else {
+        direction = parse_direction(direction_str);
+    }
+    orientation_t orientation = orientation_from_direction(direction);
+
     /* ensure that resize will take place even if pixel increment is smaller than
      * height increment or width increment.
      * fixes #1011 */
     const i3Window *window = focused_con->window;
     if (window != NULL) {
-        if (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0 ||
-            strcmp(direction, "height") == 0) {
-            if (px < 0)
+        if (orientation == VERT) {
+            if (px < 0) {
                 px = (-px < window->height_increment) ? -window->height_increment : px;
-            else
+            } else {
                 px = (px < window->height_increment) ? window->height_increment : px;
-        } else if (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0) {
-            if (px < 0)
+            }
+        } else {
+            if (px < 0) {
                 px = (-px < window->width_increment) ? -window->width_increment : px;
-            else
+            } else {
                 px = (px < window->width_increment) ? window->width_increment : px;
+            }
         }
     }
 
-    if (strcmp(direction, "up") == 0) {
+    if (orientation == VERT) {
         floating_con->rect.height += px;
-    } else if (strcmp(direction, "down") == 0 || strcmp(direction, "height") == 0) {
-        floating_con->rect.height += px;
-    } else if (strcmp(direction, "left") == 0) {
-        floating_con->rect.width += px;
     } else {
         floating_con->rect.width += px;
     }
-
     floating_check_size(floating_con);
 
     /* Did we actually resize anything or did the size constraints prevent us?
      * If we could not resize, exit now to not move the window. */
-    if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0)
+    if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0) {
         return;
+    }
 
-    if (strcmp(direction, "up") == 0) {
+    if (direction == D_UP) {
         floating_con->rect.y -= (floating_con->rect.height - old_rect.height);
-    } else if (strcmp(direction, "left") == 0) {
+    } else if (direction == D_LEFT) {
         floating_con->rect.x -= (floating_con->rect.width - old_rect.width);
     }
 
     /* If this is a scratchpad window, don't auto center it from now on. */
-    if (floating_con->scratchpad_state == SCRATCHPAD_FRESH)
+    if (floating_con->scratchpad_state == SCRATCHPAD_FRESH) {
         floating_con->scratchpad_state = SCRATCHPAD_CHANGED;
+    }
 }
 
 static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
     LOG("tiling resize\n");
     Con *second = NULL;
     Con *first = current;
-    direction_t search_direction;
-    if (!strcmp(direction, "left"))
-        search_direction = D_LEFT;
-    else if (!strcmp(direction, "right"))
-        search_direction = D_RIGHT;
-    else if (!strcmp(direction, "up"))
-        search_direction = D_UP;
-    else
-        search_direction = D_DOWN;
+    direction_t search_direction = parse_direction(direction);
 
     bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
     if (!res) {
@@ -1254,20 +1272,19 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) {
  *
  */
 void cmd_focus_direction(I3_CMD, const char *direction) {
-    DLOG("direction = *%s*\n", direction);
-
-    if (strcmp(direction, "left") == 0)
-        tree_next('p', HORIZ);
-    else if (strcmp(direction, "right") == 0)
-        tree_next('n', HORIZ);
-    else if (strcmp(direction, "up") == 0)
-        tree_next('p', VERT);
-    else if (strcmp(direction, "down") == 0)
-        tree_next('n', VERT);
-    else {
-        ELOG("Invalid focus direction (%s)\n", direction);
-        ysuccess(false);
-        return;
+    switch (parse_direction(direction)) {
+        case D_LEFT:
+            tree_next('p', HORIZ);
+            break;
+        case D_RIGHT:
+            tree_next('n', HORIZ);
+            break;
+        case D_UP:
+            tree_next('p', VERT);
+            break;
+        case D_DOWN:
+            tree_next('n', VERT);
+            break;
     }
 
     cmd_output->needs_tree_render = true;
@@ -1492,29 +1509,37 @@ void cmd_sticky(I3_CMD, const char *action) {
  * Implementation of 'move  [ [px]]'.
  *
  */
-void cmd_move_direction(I3_CMD, const char *direction, long move_px) {
+void cmd_move_direction(I3_CMD, const char *direction_str, long move_px) {
     owindow *current;
     HANDLE_EMPTY_MATCH;
 
     Con *initially_focused = focused;
+    direction_t direction = parse_direction(direction_str);
 
     TAILQ_FOREACH(current, &owindows, owindows) {
-        DLOG("moving in direction %s, px %ld\n", direction, move_px);
+        DLOG("moving in direction %s, px %ld\n", direction_str, move_px);
         if (con_is_floating(current->con)) {
             DLOG("floating move with %ld pixels\n", move_px);
             Rect newrect = current->con->parent->rect;
-            if (strcmp(direction, "left") == 0) {
-                newrect.x -= move_px;
-            } else if (strcmp(direction, "right") == 0) {
-                newrect.x += move_px;
-            } else if (strcmp(direction, "up") == 0) {
-                newrect.y -= move_px;
-            } else if (strcmp(direction, "down") == 0) {
-                newrect.y += move_px;
+
+            switch (direction) {
+                case D_LEFT:
+                    newrect.x -= move_px;
+                    break;
+                case D_RIGHT:
+                    newrect.x += move_px;
+                    break;
+                case D_UP:
+                    newrect.y -= move_px;
+                    break;
+                case D_DOWN:
+                    newrect.y += move_px;
+                    break;
             }
+
             floating_reposition(current->con->parent, newrect);
         } else {
-            tree_move(current->con, (strcmp(direction, "right") == 0 ? D_RIGHT : (strcmp(direction, "left") == 0 ? D_LEFT : (strcmp(direction, "up") == 0 ? D_UP : D_DOWN))));
+            tree_move(current->con, direction);
             cmd_output->needs_tree_render = true;
         }
     }

From db294f4505bc149cb6a53b4b95552d381f3bd5f6 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 23 Aug 2018 21:04:52 +0300
Subject: [PATCH 135/218] Improve resize_graphical_handler code style

- int return type is not useful
- Consistent comment style
---
 include/resize.h |  2 +-
 src/resize.c     | 35 ++++++++++++++---------------------
 2 files changed, 15 insertions(+), 22 deletions(-)

diff --git a/include/resize.h b/include/resize.h
index 38634156..234f4b1f 100644
--- a/include/resize.h
+++ b/include/resize.h
@@ -13,4 +13,4 @@
 
 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);
+void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
diff --git a/src/resize.c b/src/resize.c
index 184dc7c5..1f8ede10 100644
--- a/src/resize.c
+++ b/src/resize.c
@@ -101,10 +101,8 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
     return true;
 }
 
-int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
-    DLOG("resize handler\n");
 
-    /* TODO: previously, we were getting a rect containing all screens. why? */
+void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
     Con *output = con_get_output(first);
     DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width);
 
@@ -117,29 +115,27 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
     mask = XCB_CW_OVERRIDE_REDIRECT;
     values[0] = 1;
 
-    /* Open a new window, the resizebar. Grab the pointer and move the window around
-       as the user moves the pointer. */
+    /* Open a new window, the resizebar. Grab the pointer and move the window
+     * around as the user moves the pointer. */
     xcb_window_t grabwin = create_window(conn, output->rect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
                                          XCB_WINDOW_CLASS_INPUT_ONLY, XCURSOR_CURSOR_POINTER, true, mask, values);
 
-    /* Keep track of the coordinate orthogonal to motion so we can determine
-     * the length of the resize afterward. */
+    /* Keep track of the coordinate orthogonal to motion so we can determine the
+     * length of the resize afterward. */
     uint32_t initial_position, new_position;
 
     /* Configure the resizebar and snap the pointer. The resizebar runs along
      * the rect of the second con and follows the motion of the pointer. */
     Rect helprect;
+    helprect.x = second->rect.x;
+    helprect.y = second->rect.y;
     if (orientation == HORIZ) {
-        helprect.x = second->rect.x;
-        helprect.y = second->rect.y;
         helprect.width = logical_px(2);
         helprect.height = second->rect.height;
         initial_position = second->rect.x;
         xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
                          second->rect.x, event->root_y);
     } else {
-        helprect.x = second->rect.x;
-        helprect.y = second->rect.y;
         helprect.width = second->rect.width;
         helprect.height = logical_px(2);
         initial_position = second->rect.y;
@@ -173,19 +169,18 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
     xcb_flush(conn);
 
     /* User cancelled the drag so no action should be taken. */
-    if (drag_result == DRAG_REVERT)
-        return 0;
+    if (drag_result == DRAG_REVERT) {
+        return;
+    }
 
     int pixels = (new_position - initial_position);
-
     DLOG("Done, pixels = %d\n", pixels);
 
-    // if we got thus far, the containers must have
-    // percentages associated with them
+    /* if we got thus far, the containers must have valid percentages. */
     assert(first->percent > 0.0);
     assert(second->percent > 0.0);
 
-    // calculate the new percentage for the first container
+    /* calculate the new percentage for the first container */
     double new_percent, difference;
     double percent = first->percent;
     DLOG("percent = %f\n", percent);
@@ -197,13 +192,11 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
     DLOG("new percent = %f\n", new_percent);
     first->percent = new_percent;
 
-    // calculate the new percentage for the second container
+    /* calculate the new percentage for the second container */
     double s_percent = second->percent;
     second->percent = s_percent + difference;
     DLOG("second->percent = %f\n", second->percent);
 
-    // now we must make sure that the sum of the percentages remain 1.0
+    /* now we must make sure that the sum of the percentages remain 1.0 */
     con_fix_percent(first->parent);
-
-    return 0;
 }

From 7b9318a5412c38340701c54f1d516748f053f780 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 23 Aug 2018 21:57:21 +0300
Subject: [PATCH 136/218] precalculate_sizes: round sizes instead of flooring
 them

This will lead to more accurate and consistent container sizes.

Needed to fix the failing test of #3240.
---
 src/render.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/render.c b/src/render.c
index 0125b89d..8699660e 100644
--- a/src/render.c
+++ b/src/render.c
@@ -192,7 +192,7 @@ static int *precalculate_sizes(Con *con, render_params *p) {
         int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height;
         TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
             double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children;
-            assigned += sizes[i++] = percentage * total;
+            assigned += sizes[i++] = lround(percentage * total);
         }
         assert(assigned == total ||
                (assigned > total && assigned - total <= p->children * 2) ||

From ea43507bedb22fc8844411e431e059f90a535476 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 24 Aug 2018 03:02:15 +0300
Subject: [PATCH 137/218] precalculate_sizes: don't malloc needlessly

---
 src/render.c | 40 +++++++++++++++++++++-------------------
 1 file changed, 21 insertions(+), 19 deletions(-)

diff --git a/src/render.c b/src/render.c
index 8699660e..dafccbb1 100644
--- a/src/render.c
+++ b/src/render.c
@@ -183,26 +183,28 @@ free_params:
 }
 
 static int *precalculate_sizes(Con *con, render_params *p) {
-    int *sizes = smalloc(p->children * sizeof(int));
-    if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && p->children > 0) {
-        assert(!TAILQ_EMPTY(&con->nodes_head));
+    if ((con->layout != L_SPLITH && con->layout != L_SPLITV) || p->children <= 0) {
+        return NULL;
+    }
 
-        Con *child;
-        int i = 0, assigned = 0;
-        int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height;
-        TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
-            double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children;
-            assigned += sizes[i++] = lround(percentage * total);
-        }
-        assert(assigned == total ||
-               (assigned > total && assigned - total <= p->children * 2) ||
-               (assigned < total && total - assigned <= p->children * 2));
-        int signal = assigned < total ? 1 : -1;
-        while (assigned != total) {
-            for (i = 0; i < p->children && assigned != total; ++i) {
-                sizes[i] += signal;
-                assigned += signal;
-            }
+    int *sizes = smalloc(p->children * sizeof(int));
+    assert(!TAILQ_EMPTY(&con->nodes_head));
+
+    Con *child;
+    int i = 0, assigned = 0;
+    int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height;
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children;
+        assigned += sizes[i++] = lround(percentage * total);
+    }
+    assert(assigned == total ||
+           (assigned > total && assigned - total <= p->children * 2) ||
+           (assigned < total && total - assigned <= p->children * 2));
+    int signal = assigned < total ? 1 : -1;
+    while (assigned != total) {
+        for (i = 0; i < p->children && assigned != total; ++i) {
+            sizes[i] += signal;
+            assigned += signal;
         }
     }
 

From 2ead7745d6e6d911d689a83140c0c16530391b50 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 23 Aug 2018 22:09:52 +0300
Subject: [PATCH 138/218] Make cmd_resize_tiling_direction work with pixels

Introduces resize_neighboring_cons in resize.c which is also used by
resize_graphical_handler.

Co-authored-by: Andrew Laucius 
Authored original code and tests in #3240. I rewrote most of the
resizing code and fixed the failing tests.
---
 docs/userguide                    | 12 ++---
 include/resize.h                  | 16 +++++++
 parser-specs/commands.spec        |  2 +-
 src/commands.c                    | 41 ++++-------------
 src/resize.c                      | 70 ++++++++++++++++++++---------
 testcases/t/141-resize.t          | 75 +++++++++++++++++++++++++++++++
 testcases/t/187-commands-parser.t |  4 +-
 7 files changed, 159 insertions(+), 61 deletions(-)

diff --git a/docs/userguide b/docs/userguide
index 2746f24e..1c85feb6 100644
--- a/docs/userguide
+++ b/docs/userguide
@@ -2330,12 +2330,12 @@ resize set [width]  [px | ppt] [height]  [px | ppt]
 -------------------------------------------------------
 
 Direction can either be one of +up+, +down+, +left+ or +right+. Or you can be
-less specific and use +width+ or +height+, in which case i3 will take/give
-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).
+less specific and use +width+ or +height+, in which case i3 will take/give space
+from all the other containers. The optional pixel argument specifies by how many
+pixels a container should be grown or shrunk (the default is 10 pixels). The
+optional ppt argument means "percentage points", and if specified it indicates
+that a *tiling container* should be grown or shrunk by that many points, instead
+of by the +px+ value.
 
 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
diff --git a/include/resize.h b/include/resize.h
index 234f4b1f..758cb4a1 100644
--- a/include/resize.h
+++ b/include/resize.h
@@ -14,3 +14,19 @@
 bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides);
 
 void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
+
+/**
+ * Resize the two given containers using the given amount of pixels or
+ * percentage points. One of the two needs to be 0. A positive amount means
+ * growing the first container while a negative means shrinking it.
+ * Returns false when the resize would result in one of the two containers
+ * having less than 1 pixel of size.
+ *
+ */
+bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt);
+
+/**
+ * Calculate the given container's new percent given a change in pixels.
+ *
+ */
+double px_resize_to_percent(Con *con, int px_diff);
diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec
index c0c32933..6b015188 100644
--- a/parser-specs/commands.spec
+++ b/parser-specs/commands.spec
@@ -243,7 +243,7 @@ state RESIZE_TILING:
   'or'
       -> RESIZE_TILING_OR
   end
-      -> call cmd_resize($way, $direction, &resize_px, 10)
+      -> call cmd_resize($way, $direction, &resize_px, 0)
 
 state RESIZE_TILING_OR:
   resize_ppt = number
diff --git a/src/commands.c b/src/commands.c
index 0a2f3424..99c5368e 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -497,47 +497,23 @@ static void cmd_resize_floating(I3_CMD, const char *way, const char *direction_s
     }
 }
 
-static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
-    LOG("tiling resize\n");
+static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, const char *direction, int px, int ppt) {
     Con *second = NULL;
     Con *first = current;
     direction_t search_direction = parse_direction(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);
+        yerror("No second container found in this direction.\n");
         return false;
     }
 
-    /* get the default percentage */
-    int children = con_num_children(first->parent);
-    LOG("ins. %d children\n", children);
-    double percentage = 1.0 / children;
-    LOG("default percentage = %f\n", percentage);
-
-    /* resize */
-    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)
-        second->percent = percentage;
-    double new_first_percent = first->percent + ((double)ppt / 100.0);
-    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. */
-    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 {
-        LOG("Not resizing, already at minimum size\n");
+    if (ppt) {
+        /* For backwards compatibility, 'X px or Y ppt' means that ppt is
+         * preferred. */
+        px = 0;
     }
-
-    return true;
+    return resize_neighboring_cons(first, second, px, ppt);
 }
 
 static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
@@ -631,7 +607,8 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
                     return;
             } else {
                 if (!cmd_resize_tiling_direction(current_match, cmd_output,
-                                                 current->con, way, direction, resize_ppt))
+                                                 current->con, way, direction,
+                                                 resize_px, resize_ppt))
                     return;
             }
         }
diff --git a/src/resize.c b/src/resize.c
index 1f8ede10..38591b45 100644
--- a/src/resize.c
+++ b/src/resize.c
@@ -101,6 +101,53 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
     return true;
 }
 
+/*
+ * Calculate the given container's new percent given a change in pixels.
+ *
+ */
+double px_resize_to_percent(Con *con, int px_diff) {
+    Con *parent = con->parent;
+    const orientation_t o = con_orientation(parent);
+    const int total = (o == HORIZ ? parent->rect.width : parent->rect.height);
+    /* deco_rect.height is subtracted from each child in render_con_split */
+    const int target = px_diff + (o == HORIZ ? con->rect.width : con->rect.height + con->deco_rect.height);
+    return ((double)target / (double)total);
+}
+
+/*
+ * Resize the two given containers using the given amount of pixels or
+ * percentage points. One of the two needs to be 0. A positive amount means
+ * growing the first container while a negative means shrinking it.
+ * Returns false when the resize would result in one of the two containers
+ * having less than 1 pixel of size.
+ *
+ */
+bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt) {
+    assert(px * ppt == 0);
+
+    Con *parent = first->parent;
+    orientation_t orientation = con_orientation(parent);
+    const int total = (orientation == HORIZ ? parent->rect.width : parent->rect.height);
+    double new_first_percent;
+    double new_second_percent;
+    if (ppt) {
+        new_first_percent = first->percent + ((double)ppt / 100.0);
+        new_second_percent = second->percent - ((double)ppt / 100.0);
+    } else {
+        new_first_percent = px_resize_to_percent(first, px);
+        new_second_percent = second->percent + first->percent - new_first_percent;
+    }
+    /* Ensure that no container will be less than 1 pixel in the resizing
+     * direction. */
+    if (lround(new_first_percent * total) <= 0 || lround(new_second_percent * total) <= 0) {
+        return false;
+    }
+
+    first->percent = new_first_percent;
+    second->percent = new_second_percent;
+    con_fix_percent(parent);
+    return true;
+}
 
 void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
     Con *output = con_get_output(first);
@@ -179,24 +226,7 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
     /* if we got thus far, the containers must have valid percentages. */
     assert(first->percent > 0.0);
     assert(second->percent > 0.0);
-
-    /* calculate the new percentage for the first container */
-    double new_percent, difference;
-    double percent = first->percent;
-    DLOG("percent = %f\n", percent);
-    int original = (orientation == HORIZ ? first->rect.width : first->rect.height);
-    DLOG("original = %d\n", original);
-    new_percent = (original + pixels) * (percent / original);
-    difference = percent - new_percent;
-    DLOG("difference = %f\n", difference);
-    DLOG("new percent = %f\n", new_percent);
-    first->percent = new_percent;
-
-    /* calculate the new percentage for the second container */
-    double s_percent = second->percent;
-    second->percent = s_percent + difference;
-    DLOG("second->percent = %f\n", second->percent);
-
-    /* now we must make sure that the sum of the percentages remain 1.0 */
-    con_fix_percent(first->parent);
+    const bool result = resize_neighboring_cons(first, second, pixels, 0);
+    DLOG("Graphical resize %s: first->percent = %f, second->percent = %f.\n",
+         result ? "successful" : "failed", first->percent, second->percent);
 }
diff --git a/testcases/t/141-resize.t b/testcases/t/141-resize.t
index 0e7fd526..7e8249f1 100644
--- a/testcases/t/141-resize.t
+++ b/testcases/t/141-resize.t
@@ -141,6 +141,81 @@ cmp_float($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%');
 cmp_float($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%');
 cmp_float($nodes->[3]->{percent}, 0.50, 'fourth window got 50%');
 
+################################################################################
+# Check that we can grow tiled windows by pixels
+################################################################################
+
+$tmp = fresh_workspace;
+
+$left = open_window;
+$right = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, 640, 'left window is 640px');
+cmp_float($nodes->[1]->{rect}->{width}, 640, 'right window is 640px');
+
+cmd 'resize grow left 10px';
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, 630, 'left window is 630px');
+cmp_float($nodes->[1]->{rect}->{width}, 650, 'right window is 650px');
+
+################################################################################
+# Check that we can shrink tiled windows by pixels
+################################################################################
+
+$tmp = fresh_workspace;
+
+$left = open_window;
+$right = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, 640, 'left window is 640px');
+cmp_float($nodes->[1]->{rect}->{width}, 640, 'right window is 640px');
+
+cmd 'resize shrink left 10px';
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, 650, 'left window is 650px');
+cmp_float($nodes->[1]->{rect}->{width}, 630, 'right window is 630px');
+
+
+################################################################################
+# Check that we can shrink vertical tiled windows by pixels
+################################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'split v';
+
+$top = open_window;
+$bottom = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+my @heights = ($nodes->[0]->{rect}->{height}, $nodes->[1]->{rect}->{height});
+
+cmd 'resize grow up 10px';
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{height}, $heights[0] - 10, 'top window is 10px larger');
+cmp_float($nodes->[1]->{rect}->{height}, $heights[1] + 10, 'bottom window is 10px smaller');
+
+################################################################################
+# Check that we can shrink vertical tiled windows by pixels
+################################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'split v';
+
+$top = open_window;
+$bottom = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+my @heights = ($nodes->[0]->{rect}->{height}, $nodes->[1]->{rect}->{height});
+
+cmd 'resize shrink up 10px';
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{height}, $heights[0] + 10, 'top window is 10px smaller');
+cmp_float($nodes->[1]->{rect}->{height}, $heights[1] - 10, 'bottom window is 10px larger');
+
 ################################################################################
 # Check that the resize grow/shrink width/height syntax works if a nested split
 # was set on the container, but no sibling has been opened yet. See #2015.
diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t
index 39da19d8..48f50e8d 100644
--- a/testcases/t/187-commands-parser.t
+++ b/testcases/t/187-commands-parser.t
@@ -86,9 +86,9 @@ is(parser_calls(
    'resize shrink left 25 px or 33 ppt; ' .
    'resize shrink left 25'),
    "cmd_resize(shrink, left, 10, 10)\n" .
-   "cmd_resize(shrink, left, 25, 10)\n" .
+   "cmd_resize(shrink, left, 25, 0)\n" .
    "cmd_resize(shrink, left, 25, 33)\n" .
-   "cmd_resize(shrink, left, 25, 10)",
+   "cmd_resize(shrink, left, 25, 0)",
    'simple resize ok');
 
 is(parser_calls('resize shrink left 25 px or 33 ppt,'),

From 26bbaf62378dce6cb68c4e35454bc463a138131c Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 24 Aug 2018 02:14:11 +0300
Subject: [PATCH 139/218] Make cmd_resize_tiling_width_height work with pixels

---
 src/commands.c           | 22 ++++++++++----
 testcases/t/141-resize.t | 66 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+), 6 deletions(-)

diff --git a/src/commands.c b/src/commands.c
index 99c5368e..1f6d1daf 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -516,7 +516,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c
     return resize_neighboring_cons(first, second, px, ppt);
 }
 
-static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
+static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int px, int _ppt) {
     LOG("width/height resize\n");
 
     /* get the appropriate current container (skip stacked/tabbed cons) */
@@ -542,8 +542,17 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way
             child->percent = percentage;
     }
 
-    double new_current_percent = current->percent + ((double)ppt / 100.0);
-    double subtract_percent = ((double)ppt / 100.0) / (children - 1);
+    double ppt = (double)_ppt / 100.0;
+    double new_current_percent;
+    double subtract_percent;
+    if (_ppt) {
+        new_current_percent = current->percent + ppt;
+    } else {
+        new_current_percent = px_resize_to_percent(current, px);
+        ppt = new_current_percent - current->percent;
+    }
+    subtract_percent = ppt / (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. */
@@ -603,7 +612,8 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
             if (strcmp(direction, "width") == 0 ||
                 strcmp(direction, "height") == 0) {
                 if (!cmd_resize_tiling_width_height(current_match, cmd_output,
-                                                    current->con, way, direction, resize_ppt))
+                                                    current->con, way, direction,
+                                                    resize_px, resize_ppt))
                     return;
             } else {
                 if (!cmd_resize_tiling_direction(current_match, cmd_output,
@@ -676,7 +686,7 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
 
                 /* perform resizing and report failure if not possible */
                 if (!cmd_resize_tiling_width_height(current_match, cmd_output,
-                                                    target, action_string, "width", adjustment)) {
+                                                    target, action_string, "width", 0, adjustment)) {
                     success = false;
                 }
             }
@@ -702,7 +712,7 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
 
                 /* perform resizing and report failure if not possible */
                 if (!cmd_resize_tiling_width_height(current_match, cmd_output,
-                                                    target, action_string, "height", adjustment)) {
+                                                    target, action_string, "height", 0, adjustment)) {
                     success = false;
                 }
             }
diff --git a/testcases/t/141-resize.t b/testcases/t/141-resize.t
index 7e8249f1..5a264169 100644
--- a/testcases/t/141-resize.t
+++ b/testcases/t/141-resize.t
@@ -141,6 +141,72 @@ cmp_float($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%');
 cmp_float($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%');
 cmp_float($nodes->[3]->{percent}, 0.50, 'fourth window got 50%');
 
+################################################################################
+# Same but using pixels instead of ppt.
+################################################################################
+
+# Use two windows
+$tmp = fresh_workspace;
+
+$left = open_window;
+$right = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+my @widths = ($nodes->[0]->{rect}->{width}, $nodes->[1]->{rect}->{width});
+
+cmd 'resize grow width 10 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, $widths[0] - 10, 'left window is 10px smaller');
+cmp_float($nodes->[1]->{rect}->{width}, $widths[1] + 10, 'right window is 10px larger');
+
+# Now test it with four windows
+$tmp = fresh_workspace;
+
+open_window for (1..4);
+
+($nodes, $focus) = get_ws_content($tmp);
+my $width = $nodes->[0]->{rect}->{width};
+
+cmd 'resize grow width 10 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[3]->{rect}->{width}, $width + 10, 'last window is 10px larger');
+
+################################################################################
+# Same but for height
+################################################################################
+
+# Use two windows
+$tmp = fresh_workspace;
+cmd 'split v';
+
+$left = open_window;
+$right = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+my @heights = ($nodes->[0]->{rect}->{height}, $nodes->[1]->{rect}->{height});
+
+cmd 'resize grow height 10 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{height}, $heights[0] - 10, 'left window is 10px smaller');
+cmp_float($nodes->[1]->{rect}->{height}, $heights[1] + 10, 'right window is 10px larger');
+
+# Now test it with four windows
+$tmp = fresh_workspace;
+cmd 'split v';
+
+open_window for (1..4);
+
+($nodes, $focus) = get_ws_content($tmp);
+my $height = $nodes->[0]->{rect}->{height};
+
+cmd 'resize grow height 10 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[3]->{rect}->{height}, $height + 10, 'last window is 10px larger');
+
 ################################################################################
 # Check that we can grow tiled windows by pixels
 ################################################################################

From 423e20b960171f27a9d6224408d6a05a62ec91b3 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 24 Aug 2018 03:03:26 +0300
Subject: [PATCH 140/218] cmd_resize* statics: remove useless 'way' argument

---
 src/commands.c | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)

diff --git a/src/commands.c b/src/commands.c
index 1f6d1daf..9686934f 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -497,7 +497,7 @@ static void cmd_resize_floating(I3_CMD, const char *way, const char *direction_s
     }
 }
 
-static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, const char *direction, int px, int ppt) {
+static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *direction, int px, int ppt) {
     Con *second = NULL;
     Con *first = current;
     direction_t search_direction = parse_direction(direction);
@@ -516,7 +516,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c
     return resize_neighboring_cons(first, second, px, ppt);
 }
 
-static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int px, int _ppt) {
+static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *direction, int px, int _ppt) {
     LOG("width/height resize\n");
 
     /* get the appropriate current container (skip stacked/tabbed cons) */
@@ -612,12 +612,12 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
             if (strcmp(direction, "width") == 0 ||
                 strcmp(direction, "height") == 0) {
                 if (!cmd_resize_tiling_width_height(current_match, cmd_output,
-                                                    current->con, way, direction,
+                                                    current->con, direction,
                                                     resize_px, resize_ppt))
                     return;
             } else {
                 if (!cmd_resize_tiling_direction(current_match, cmd_output,
-                                                 current->con, way, direction,
+                                                 current->con, direction,
                                                  resize_px, resize_ppt))
                     return;
             }
@@ -673,20 +673,17 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
 
                 /* 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", 0, adjustment)) {
+                                                    target, "width", 0, adjustment)) {
                     success = false;
                 }
             }
@@ -699,20 +696,17 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
 
                 /* 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", 0, adjustment)) {
+                                                    target, "height", 0, adjustment)) {
                     success = false;
                 }
             }

From f28c50b631cb9ac5b9ef832794d4a734c5fe5782 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 24 Aug 2018 03:18:01 +0300
Subject: [PATCH 141/218] 541-resize-set-tiling.t: fix "my" variable mask
 warnings

---
 testcases/t/541-resize-set-tiling.t | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/testcases/t/541-resize-set-tiling.t b/testcases/t/541-resize-set-tiling.t
index fcf3267a..659fcc67 100644
--- a/testcases/t/541-resize-set-tiling.t
+++ b/testcases/t/541-resize-set-tiling.t
@@ -51,7 +51,7 @@ cmp_float($nodes->[1]->{percent}, 0.80, 'right window got 80%');
 # resize vertically
 ############################################################
 
-my $tmp = fresh_workspace;
+$tmp = fresh_workspace;
 
 cmd 'split v';
 
@@ -64,7 +64,7 @@ is($x->input_focus, $bottom->id, 'Bottom window focused');
 
 cmd 'resize set 0 ppt 75 ppt';
 
-my ($nodes, $focus) = get_ws_content($tmp);
+($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%');
@@ -81,10 +81,10 @@ cmp_float($nodes->[1]->{percent}, 0.80, 'bottom window got 80%');
 # resize horizontally and vertically
 ############################################################
 
-my $tmp = fresh_workspace;
+$tmp = fresh_workspace;
 
 cmd 'split h';
-my $left = open_window;
+$left = open_window;
 my $top_right = open_window;
 cmd 'split v';
 my $bottom_right = open_window;
@@ -95,7 +95,7 @@ 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);
+($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%');
@@ -107,11 +107,11 @@ cmp_float($nodes->[1]->{nodes}->[1]->{percent}, 0.75, 'bottom-right window got 7
 # resize from inside a tabbed container
 ############################################################
 
-my $tmp = fresh_workspace;
+$tmp = fresh_workspace;
 
 cmd 'split h';
 
-my $left = open_window;
+$left = open_window;
 my $right1 = open_window;
 
 cmd 'split h';
@@ -125,7 +125,7 @@ is($x->input_focus, $right2->id, '2nd right window focused');
 
 cmd 'resize set 75 ppt 0 ppt';
 
-my ($nodes, $focus) = get_ws_content($tmp);
+($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%');
@@ -135,17 +135,17 @@ cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
 # resize from inside a stacked container
 ############################################################
 
-my $tmp = fresh_workspace;
+$tmp = fresh_workspace;
 
 cmd 'split h';
 
-my $left = open_window;
-my $right1 = open_window;
+$left = open_window;
+$right1 = open_window;
 
 cmd 'split h';
 cmd 'layout stacked';
 
-my $right2 = open_window;
+$right2 = open_window;
 
 diag("left = " . $left->id . ", right1 = " . $right1->id . ", right2 = " . $right2->id);
 
@@ -153,7 +153,7 @@ is($x->input_focus, $right2->id, '2nd right window focused');
 
 cmd 'resize set 75 ppt 0 ppt';
 
-my ($nodes, $focus) = get_ws_content($tmp);
+($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%');

From 51d230ad4ca1bc4202ffb1b7e9157944542e3596 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 24 Aug 2018 03:06:52 +0300
Subject: [PATCH 142/218] Make resize set px work with tiling containers

---
 docs/userguide                      |  5 +-
 src/commands.c                      | 78 +++++++++++++----------------
 testcases/t/541-resize-set-tiling.t | 33 ++++++++++++
 3 files changed, 70 insertions(+), 46 deletions(-)

diff --git a/docs/userguide b/docs/userguide
index 1c85feb6..9c601e88 100644
--- a/docs/userguide
+++ b/docs/userguide
@@ -2337,9 +2337,8 @@ optional ppt argument means "percentage points", and if specified it indicates
 that a *tiling container* should be grown or shrunk by that many points, instead
 of by the +px+ value.
 
-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.
+Note about +resize set+: a value of 0 for  or  means "do not
+resize in this direction".
 
 It is recommended to define bindings for resizing in a dedicated binding mode.
 See <> and the example in the i3
diff --git a/src/commands.c b/src/commands.c
index 9686934f..1b05d672 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -629,6 +629,35 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
     ysuccess(true);
 }
 
+static bool resize_set_tiling(I3_CMD, Con *target, orientation_t resize_orientation, bool is_ppt, long target_size) {
+    direction_t search_direction;
+    char *mode;
+    if (resize_orientation == HORIZ) {
+        search_direction = D_LEFT;
+        mode = "width";
+    } else {
+        search_direction = D_DOWN;
+        mode = "height";
+    }
+
+    /* Get the appropriate current container (skip stacked/tabbed cons) */
+    Con *dummy;
+    resize_find_tiling_participants(&target, &dummy, search_direction, true);
+
+    /* Calculate new size for the target container */
+    int ppt = 0;
+    int px = 0;
+    if (is_ppt) {
+        ppt = target_size - target->percent * 100;
+    } else {
+        px = target_size - (resize_orientation == HORIZ ? target->rect.width : target->rect.height);
+    }
+
+    /* Perform resizing and report failure if not possible */
+    return cmd_resize_tiling_width_height(current_match, cmd_output,
+                                          target, mode, px, ppt);
+}
+
 /*
  * Implementation of 'resize set  [px | ppt]  [px | ppt]'.
  *
@@ -665,50 +694,13 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
                 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;
-                long adjustment;
-
-                if (current_percent > cwidth) {
-                    adjustment = (int)(current_percent * 100) - cwidth;
-                } else {
-                    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, "width", 0, adjustment)) {
-                    success = false;
-                }
+            if (cwidth > 0 && mode_width) {
+                success &= resize_set_tiling(current_match, cmd_output, current->con,
+                                             HORIZ, strcmp(mode_width, "ppt") == 0, cwidth);
             }
-
-            if (cheight > 0 && mode_height && strcmp(mode_height, "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;
-                long adjustment;
-
-                if (current_percent > cheight) {
-                    adjustment = (int)(current_percent * 100) - cheight;
-                } else {
-                    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, "height", 0, adjustment)) {
-                    success = false;
-                }
+            if (cheight > 0 && mode_height) {
+                success &= resize_set_tiling(current_match, cmd_output, current->con,
+                                             VERT, strcmp(mode_height, "ppt") == 0, cheight);
             }
         }
     }
diff --git a/testcases/t/541-resize-set-tiling.t b/testcases/t/541-resize-set-tiling.t
index 659fcc67..956622a2 100644
--- a/testcases/t/541-resize-set-tiling.t
+++ b/testcases/t/541-resize-set-tiling.t
@@ -47,6 +47,13 @@ cmd 'resize set width 80 ppt';
 cmp_float($nodes->[0]->{percent}, 0.20, 'left window got 20%');
 cmp_float($nodes->[1]->{percent}, 0.80, 'right window got 80%');
 
+# Same but with px.
+cmd 'resize set width 200 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{rect}->{width}, 200, 'right window got 200 px');
+
 ############################################################
 # resize vertically
 ############################################################
@@ -77,6 +84,13 @@ cmd 'resize set height 80 ppt';
 cmp_float($nodes->[0]->{percent}, 0.20, 'top window got 20%');
 cmp_float($nodes->[1]->{percent}, 0.80, 'bottom window got 80%');
 
+# Same but with px.
+cmd 'resize set height 200 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{rect}->{height}, 200, 'bottom window got 200 px');
+
 ############################################################
 # resize horizontally and vertically
 ############################################################
@@ -102,6 +116,13 @@ 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%');
 
+# Same but with px.
+cmd 'resize set 155 px 135 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{width}, 155, 'bottom-right window got 155 px width');
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{height}, 135, 'bottom-right window got 135 px height');
 
 ############################################################
 # resize from inside a tabbed container
@@ -130,6 +151,12 @@ cmd 'resize set 75 ppt 0 ppt';
 cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
 cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
 
+# Same but with px.
+cmd 'resize set 155 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{rect}->{width}, 155, 'right container got 155 px');
 
 ############################################################
 # resize from inside a stacked container
@@ -158,5 +185,11 @@ cmd 'resize set 75 ppt 0 ppt';
 cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
 cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
 
+# Same but with px.
+cmd 'resize set 130 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{rect}->{width}, 130, 'right container got 130 px');
 
 done_testing;

From 096cff0aad7f6a03e3dc04d0c11fa15ad851ab59 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 24 Aug 2018 03:50:23 +0300
Subject: [PATCH 143/218] Make resize set ppt more accurate

See the testcase for the usecase.
---
 src/commands.c                      | 12 ++++++------
 testcases/t/541-resize-set-tiling.t |  9 +++++++++
 2 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/src/commands.c b/src/commands.c
index 1b05d672..0874eec2 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -516,7 +516,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *direct
     return resize_neighboring_cons(first, second, px, ppt);
 }
 
-static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *direction, int px, int _ppt) {
+static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *direction, int px, double ppt) {
     LOG("width/height resize\n");
 
     /* get the appropriate current container (skip stacked/tabbed cons) */
@@ -542,10 +542,9 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *dir
             child->percent = percentage;
     }
 
-    double ppt = (double)_ppt / 100.0;
     double new_current_percent;
     double subtract_percent;
-    if (_ppt) {
+    if (ppt != 0.0) {
         new_current_percent = current->percent + ppt;
     } else {
         new_current_percent = px_resize_to_percent(current, px);
@@ -611,9 +610,10 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
         } else {
             if (strcmp(direction, "width") == 0 ||
                 strcmp(direction, "height") == 0) {
+                const double ppt = (double)resize_ppt / 100.0;
                 if (!cmd_resize_tiling_width_height(current_match, cmd_output,
                                                     current->con, direction,
-                                                    resize_px, resize_ppt))
+                                                    resize_px, ppt))
                     return;
             } else {
                 if (!cmd_resize_tiling_direction(current_match, cmd_output,
@@ -645,10 +645,10 @@ static bool resize_set_tiling(I3_CMD, Con *target, orientation_t resize_orientat
     resize_find_tiling_participants(&target, &dummy, search_direction, true);
 
     /* Calculate new size for the target container */
-    int ppt = 0;
+    double ppt = 0.0;
     int px = 0;
     if (is_ppt) {
-        ppt = target_size - target->percent * 100;
+        ppt = (double)target_size / 100.0 - target->percent;
     } else {
         px = target_size - (resize_orientation == HORIZ ? target->rect.width : target->rect.height);
     }
diff --git a/testcases/t/541-resize-set-tiling.t b/testcases/t/541-resize-set-tiling.t
index 956622a2..d695edee 100644
--- a/testcases/t/541-resize-set-tiling.t
+++ b/testcases/t/541-resize-set-tiling.t
@@ -124,6 +124,15 @@ cmd 'resize set 155 px 135 px';
 cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{width}, 155, 'bottom-right window got 155 px width');
 cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{height}, 135, 'bottom-right window got 135 px height');
 
+# Mix ppt and px
+cmd 'resize set 75 ppt 200 px';
+
+($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}->[1]->{rect}->{height}, 200, 'bottom-right window got 200 px height');
+
 ############################################################
 # resize from inside a tabbed container
 ############################################################

From a8b90317a0a20fb061ce97d73a7df4268cdd49d6 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Sat, 25 Aug 2018 14:49:28 +0300
Subject: [PATCH 144/218] i3-config-wizard: fix small memleak

---
 i3-config-wizard/main.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c
index 4a843fae..0e0a3e65 100644
--- a/i3-config-wizard/main.c
+++ b/i3-config-wizard/main.c
@@ -293,6 +293,7 @@ static char *next_state(const cmdp_token *token) {
         }
         sasprintf(&res, "bindsym %s%s%s %s%s\n", (modifiers == NULL ? "" : modrep), (modifiers == NULL ? "" : "+"), str, (release == NULL ? "" : release), get_string("command"));
         clear_stack();
+        free(modrep);
         return res;
     }
 

From bf1cb39b4b85911e83e961f27367f3e91428ce73 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Sat, 25 Aug 2018 14:48:14 +0300
Subject: [PATCH 145/218] Support startup-notification in i3-nagbar &
 i3-config-wizard

The default i3 config uses the `exec` command without `--no-startup-id`
to launch:
1. i3-nagbar
https://github.com/i3/i3/blob/4cba9fcbdab1487459014dbf8882f5f34e61435e/etc/config#L150
2. i3-config-wizard
https://github.com/i3/i3/blob/4cba9fcbdab1487459014dbf8882f5f34e61435e/etc/config#L194

A user that opens i3 for the first time will be greeted with a "loading"
cursor because of i3-config-wizard.
---
 Makefile.am             |  4 ++++
 i3-config-wizard/main.c | 13 +++++++++++++
 i3-nagbar/main.c        | 13 +++++++++++++
 3 files changed, 30 insertions(+)

diff --git a/Makefile.am b/Makefile.am
index f8ae7a1b..09bbb6d5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -357,10 +357,12 @@ i3_msg_i3_msg_SOURCES = \
 
 i3_nagbar_i3_nagbar_CFLAGS = \
 	$(AM_CFLAGS) \
+	$(LIBSN_CFLAGS) \
 	$(libi3_CFLAGS)
 
 i3_nagbar_i3_nagbar_LDADD = \
 	$(libi3_LIBS) \
+	$(LIBSN_LIBS) \
 	$(XCB_UTIL_CURSOR_LIBS)
 
 i3_nagbar_i3_nagbar_SOURCES = \
@@ -414,10 +416,12 @@ i3bar_i3bar_SOURCES = \
 i3_config_wizard_i3_config_wizard_CFLAGS = \
 	$(AM_CFLAGS) \
 	$(libi3_CFLAGS) \
+	$(LIBSN_CFLAGS) \
 	$(XKBCOMMON_CFLAGS)
 
 i3_config_wizard_i3_config_wizard_LDADD = \
 	$(libi3_LIBS) \
+	$(LIBSN_LIBS) \
 	$(XCB_UTIL_KEYSYMS_LIBS) \
 	$(XKBCOMMON_LIBS)
 
diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c
index 0e0a3e65..013d1053 100644
--- a/i3-config-wizard/main.c
+++ b/i3-config-wizard/main.c
@@ -48,6 +48,9 @@
 #include 
 #include 
 
+#define SN_API_NOT_YET_FROZEN 1
+#include 
+
 #include 
 #include 
 #include 
@@ -847,6 +850,10 @@ int main(int argc, char *argv[]) {
 #include "atoms.xmacro"
 #undef xmacro
 
+    /* Init startup notification. */
+    SnDisplay *sndisplay = sn_xcb_display_new(conn, NULL, NULL);
+    SnLauncheeContext *sncontext = sn_launchee_context_new_from_environment(sndisplay, screen);
+
     root_screen = xcb_aux_get_screen(conn, screen);
     root = root_screen->root;
 
@@ -879,6 +886,7 @@ int main(int argc, char *argv[]) {
             0, /* back pixel: black */
             XCB_EVENT_MASK_EXPOSURE |
                 XCB_EVENT_MASK_BUTTON_PRESS});
+    sn_launchee_context_setup_window(sncontext, win);
 
     /* Map the window (make it visible) */
     xcb_map_window(conn, win);
@@ -940,6 +948,11 @@ int main(int argc, char *argv[]) {
         exit(-1);
     }
 
+    /* Startup complete. */
+    sn_launchee_context_complete(sncontext);
+    sn_launchee_context_unref(sncontext);
+    sn_display_unref(sndisplay);
+
     xcb_flush(conn);
 
     xcb_generic_event_t *event;
diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c
index 3e5f43e3..1d1fcc51 100644
--- a/i3-nagbar/main.c
+++ b/i3-nagbar/main.c
@@ -32,6 +32,9 @@
 #include 
 #include 
 
+#define SN_API_NOT_YET_FROZEN 1
+#include 
+
 #include "i3-nagbar.h"
 
 /** This is the equivalent of XC_left_ptr. I’m not sure why xcb doesn’t have a
@@ -415,6 +418,10 @@ int main(int argc, char *argv[]) {
 #include "atoms.xmacro"
 #undef xmacro
 
+    /* Init startup notification. */
+    SnDisplay *sndisplay = sn_xcb_display_new(conn, NULL, NULL);
+    SnLauncheeContext *sncontext = sn_launchee_context_new_from_environment(sndisplay, screens);
+
     root_screen = xcb_aux_get_screen(conn, screens);
     root = root_screen->root;
 
@@ -484,6 +491,7 @@ int main(int argc, char *argv[]) {
                 XCB_EVENT_MASK_BUTTON_PRESS |
                 XCB_EVENT_MASK_BUTTON_RELEASE,
             cursor});
+    sn_launchee_context_setup_window(sncontext, win);
 
     /* Map the window (make it visible) */
     xcb_map_window(conn, win);
@@ -544,6 +552,11 @@ int main(int argc, char *argv[]) {
     /* Initialize the drawable bar */
     draw_util_surface_init(conn, &bar, win, get_visualtype(root_screen), win_pos.width, win_pos.height);
 
+    /* Startup complete. */
+    sn_launchee_context_complete(sncontext);
+    sn_launchee_context_unref(sncontext);
+    sn_display_unref(sndisplay);
+
     /* Grab the keyboard to get all input */
     xcb_flush(conn);
 

From 37b879f4adcee8c7ab360512487ab617211a4e2a Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Mon, 27 Aug 2018 22:19:36 +0300
Subject: [PATCH 146/218] ipc_client_timeout: Fix build when not on linux

Also moved the 'end' label because it is unused otherwise.

Reported here: https://github.com/Airblader/i3/issues/233
---
 src/ipc.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/ipc.c b/src/ipc.c
index 977fe377..3022d5b3 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -1414,12 +1414,14 @@ static void ipc_client_timeout(EV_P_ ev_timer *w, int revents) {
         }
     }
     cmdline = buf;
-#endif
 
-end:
     if (cmdline) {
         ELOG("client %p with pid %d and cmdline '%s' on fd %d timed out, killing\n", client, peercred.pid, cmdline, client->fd);
-    } else {
+    }
+
+end:
+#endif
+    if (!cmdline) {
         ELOG("client %p on fd %d timed out, killing\n", client, client->fd);
     }
 

From 9718e38a7af4afc65da26e9a6762074a0fba404a Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Tue, 28 Aug 2018 10:26:07 +0300
Subject: [PATCH 147/218] Don't disable floating in internal workspaces

---
 src/floating.c               |  4 ++++
 testcases/t/185-scratchpad.t | 16 ++++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/src/floating.c b/src/floating.c
index 08041334..3c5afb25 100644
--- a/src/floating.c
+++ b/src/floating.c
@@ -366,6 +366,10 @@ void floating_disable(Con *con, bool automatic) {
     }
 
     Con *ws = con_get_workspace(con);
+    if (con_is_internal(ws)) {
+        LOG("Can't disable floating for container in internal workspace.\n");
+        return;
+    }
     Con *tiling_focused = con_descend_tiling_focused(ws);
 
     if (tiling_focused->type == CT_WORKSPACE) {
diff --git a/testcases/t/185-scratchpad.t b/testcases/t/185-scratchpad.t
index efb5d002..fd3827f7 100644
--- a/testcases/t/185-scratchpad.t
+++ b/testcases/t/185-scratchpad.t
@@ -517,4 +517,20 @@ fresh_workspace;
 $result = cmd 'scratchpad show';
 is($result->[0]->{success}, 1, 'call to scratchpad in another workspace succeeded');
 
+################################################################################
+# 18: Disabling floating for a scratchpad window should not work.
+################################################################################
+
+kill_all_windows;
+
+$ws = fresh_workspace;
+$window = open_window;
+cmd 'move scratchpad';
+cmd '[id=' . $window->id . '] floating disable';
+
+is(scalar @{get_ws_content($ws)}, 0, 'no window in workspace');
+cmd 'scratchpad show';
+is($x->input_focus, $window->id, 'scratchpad window shown');
+
+
 done_testing;

From 09683d21a93345fcb5bc3ca10bb52e14dcb484b5 Mon Sep 17 00:00:00 2001
From: Michael Stapelberg 
Date: Fri, 31 Aug 2018 08:59:08 -0600
Subject: [PATCH 148/218] configure.ac: fix AC_SEARCH_LIBS([shm_open]) for
 static linking (#3379)

Without specifying -pthread, the conftest fails and -lrt is missing during
compilation of i3, resulting in a failing build.
---
 configure.ac | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/configure.ac b/configure.ac
index 39c228e3..1784fa83 100644
--- a/configure.ac
+++ b/configure.ac
@@ -81,7 +81,7 @@ AC_SEARCH_LIBS([floor], [m], , [AC_MSG_FAILURE([cannot find the required floor()
 # libev does not ship with a pkg-config file :(.
 AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_run() function despite trying to link with -lev])])
 
-AC_SEARCH_LIBS([shm_open], [rt])
+AC_SEARCH_LIBS([shm_open], [rt], [], [], [-pthread])
 
 AC_SEARCH_LIBS([iconv_open], [iconv], ,
 AC_SEARCH_LIBS([libiconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])]))

From 7c3c50c5cc9f8db14ab23db99518ab3568bf8945 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 6 Sep 2018 03:38:45 +0300
Subject: [PATCH 149/218] Improve comment from #3245

---
 testcases/t/522-rename-assigned-workspace.t | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/testcases/t/522-rename-assigned-workspace.t b/testcases/t/522-rename-assigned-workspace.t
index 824f4a06..af3b3262 100644
--- a/testcases/t/522-rename-assigned-workspace.t
+++ b/testcases/t/522-rename-assigned-workspace.t
@@ -82,16 +82,16 @@ is(get_output_for_workspace('5'), 'fake-0',
     'Renaming the workspace to a workspace assigned to a directional output should not move the workspace');
 
 ##########################################################################
-# Renaming a workspace, so that it becomes assigned to the focused
-# output's workspace (and the focused output is empty) should
-# result in the original workspace replacing the originally
-# focused workspace.
+# Renaming an unfocused workspace, triggering an assignment to the output
+# which holds the currently focused empty workspace should result in the
+# original workspace replacing the empty one.
+# See issue #3228.
 ##########################################################################
 
 cmd 'workspace baz';
 cmd 'rename workspace 5 to 2';
 is(get_output_for_workspace('2'), 'fake-1',
-    'Renaming a workspace so that it moves to the focused output which contains only an empty workspace should replace the empty workspace');
+    'Renaming an unfocused workspace, triggering an assignment to the output which holds the currently focused empty workspace should result in the original workspace replacing the empty one');
 
 ##########################################################################
 # Renaming a workspace with multiple assignments, where the first output

From 6846ac98c0a3c4f179d65b51f9481d7f77625c55 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 6 Sep 2018 03:11:47 +0300
Subject: [PATCH 150/218] cmd_rename_workspace: correct order of events

1. Rename happens
2. Workspace is moved because of assignments
3. Workspace closes because it is empty (#3248)

Fixes #3248.
---
 src/commands.c                              |  2 +-
 testcases/t/522-rename-assigned-workspace.t | 15 +++++++++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/src/commands.c b/src/commands.c
index 0343f9dc..33743848 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -2002,6 +2002,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
     Con *parent = workspace->parent;
     con_detach(workspace);
     con_attach(workspace, parent, false);
+    ipc_send_workspace_event("rename", workspace, NULL);
 
     /* Move the workspace to the correct output if it has an assignment */
     struct Workspace_Assignment *assignment = NULL;
@@ -2046,7 +2047,6 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
     cmd_output->needs_tree_render = true;
     ysuccess(true);
 
-    ipc_send_workspace_event("rename", workspace, NULL);
     ewmh_update_desktop_names();
     ewmh_update_desktop_viewport();
     ewmh_update_current_desktop();
diff --git a/testcases/t/522-rename-assigned-workspace.t b/testcases/t/522-rename-assigned-workspace.t
index af3b3262..9897e4ee 100644
--- a/testcases/t/522-rename-assigned-workspace.t
+++ b/testcases/t/522-rename-assigned-workspace.t
@@ -93,6 +93,21 @@ cmd 'rename workspace 5 to 2';
 is(get_output_for_workspace('2'), 'fake-1',
     'Renaming an unfocused workspace, triggering an assignment to the output which holds the currently focused empty workspace should result in the original workspace replacing the empty one');
 
+##########################################################################
+# Renaming an unfocused empty workspace, triggering an assignment to the
+# output which holds the currently focused non-empty workspace should
+# close the empty workspace and not crash i3.
+# See issue #3248.
+##########################################################################
+
+cmd 'workspace 1';
+cmd 'workspace 2';
+open_window;
+cmd 'rename workspace 1 to baz';
+is(get_output_for_workspace('baz'), '',
+    'Renaming an unfocused empty workspace, triggering an assignment to the output which holds the currently focused non-empty workspace should close the empty workspace and not crash i3');
+kill_all_windows;
+
 ##########################################################################
 # Renaming a workspace with multiple assignments, where the first output
 # doesn't exist.

From 23c1c13d3479098aabf768cf83be4b6b70cfd7f0 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 6 Sep 2018 04:56:31 +0300
Subject: [PATCH 151/218] Ensure containers have a size of at least 1px after
 resize

Fixes #2226.
Fixes #2776.
Fixes #3241.
Related to #3194.
---
 include/resize.h |  7 +++++++
 src/commands.c   | 25 +++++++++++++------------
 src/resize.c     | 17 ++++++++++++++---
 3 files changed, 34 insertions(+), 15 deletions(-)

diff --git a/include/resize.h b/include/resize.h
index 758cb4a1..72dffc0f 100644
--- a/include/resize.h
+++ b/include/resize.h
@@ -25,6 +25,13 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
  */
 bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt);
 
+/**
+ * Calculate the minimum percent needed for the given container to be at least 1
+ * pixel.
+ *
+ */
+double percent_for_1px(Con *con);
+
 /**
  * Calculate the given container's new percent given a change in pixels.
  *
diff --git a/src/commands.c b/src/commands.c
index 0343f9dc..ab41ec97 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -551,24 +551,25 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *dir
         ppt = new_current_percent - current->percent;
     }
     subtract_percent = ppt / (children - 1);
+    if (ppt < 0.0 && new_current_percent < percent_for_1px(current)) {
+        yerror("Not resizing, container would end with less than 1px\n");
+        return false;
+    }
 
     LOG("new_current_percent = %f\n", new_current_percent);
     LOG("subtract_percent = %f\n", subtract_percent);
     /* Ensure that the new percentages are positive. */
-    TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
-        if (child == current)
-            continue;
-        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 (subtract_percent >= 0.0) {
+        TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
+            if (child == current) {
+                continue;
+            }
+            if (child->percent - subtract_percent < percent_for_1px(child)) {
+                yerror("Not resizing, already at minimum size (child %p would end up with a size of %.f\n", child, child->percent - subtract_percent);
+                return false;
+            }
         }
     }
-    if (new_current_percent <= 0.0) {
-        LOG("Not resizing, already at minimum size\n");
-        ysuccess(false);
-        return false;
-    }
 
     current->percent = new_current_percent;
     LOG("current->percent after = %f\n", current->percent);
diff --git a/src/resize.c b/src/resize.c
index 38591b45..d746ea22 100644
--- a/src/resize.c
+++ b/src/resize.c
@@ -114,6 +114,19 @@ double px_resize_to_percent(Con *con, int px_diff) {
     return ((double)target / (double)total);
 }
 
+/*
+ * Calculate the minimum percent needed for the given container to be at least 1
+ * pixel.
+ *
+ */
+double percent_for_1px(Con *con) {
+    Con *parent = con->parent;
+    const orientation_t o = con_orientation(parent);
+    const int total = (o == HORIZ ? parent->rect.width : parent->rect.height);
+    const int target = (o == HORIZ ? 1 : 1 + con->deco_rect.height);
+    return ((double)target / (double)total);
+}
+
 /*
  * Resize the two given containers using the given amount of pixels or
  * percentage points. One of the two needs to be 0. A positive amount means
@@ -126,8 +139,6 @@ bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt) {
     assert(px * ppt == 0);
 
     Con *parent = first->parent;
-    orientation_t orientation = con_orientation(parent);
-    const int total = (orientation == HORIZ ? parent->rect.width : parent->rect.height);
     double new_first_percent;
     double new_second_percent;
     if (ppt) {
@@ -139,7 +150,7 @@ bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt) {
     }
     /* Ensure that no container will be less than 1 pixel in the resizing
      * direction. */
-    if (lround(new_first_percent * total) <= 0 || lround(new_second_percent * total) <= 0) {
+    if (new_first_percent < percent_for_1px(first) || new_second_percent < percent_for_1px(second)) {
         return false;
     }
 

From 64142eeef222d3f525cabee89f1c0a30b264c589 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 6 Sep 2018 05:58:51 +0300
Subject: [PATCH 152/218] resize set for tiling: default to px when not
 specified

---
 src/commands.c                      | 10 ++++++----
 testcases/t/541-resize-set-tiling.t |  8 ++++++++
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/commands.c b/src/commands.c
index 0343f9dc..28798017 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -694,13 +694,15 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
                 continue;
             }
 
-            if (cwidth > 0 && mode_width) {
+            if (cwidth > 0) {
+                bool is_ppt = mode_width && strcmp(mode_width, "ppt") == 0;
                 success &= resize_set_tiling(current_match, cmd_output, current->con,
-                                             HORIZ, strcmp(mode_width, "ppt") == 0, cwidth);
+                                             HORIZ, is_ppt, cwidth);
             }
-            if (cheight > 0 && mode_height) {
+            if (cheight > 0) {
+                bool is_ppt = mode_height && strcmp(mode_height, "ppt") == 0;
                 success &= resize_set_tiling(current_match, cmd_output, current->con,
-                                             VERT, strcmp(mode_height, "ppt") == 0, cheight);
+                                             VERT, is_ppt, cheight);
             }
         }
     }
diff --git a/testcases/t/541-resize-set-tiling.t b/testcases/t/541-resize-set-tiling.t
index d695edee..0298fecd 100644
--- a/testcases/t/541-resize-set-tiling.t
+++ b/testcases/t/541-resize-set-tiling.t
@@ -124,6 +124,14 @@ cmd 'resize set 155 px 135 px';
 cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{width}, 155, 'bottom-right window got 155 px width');
 cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{height}, 135, 'bottom-right window got 135 px height');
 
+# Without specifying mode
+cmd 'resize set 201 131';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{width}, 201, 'bottom-right window got 201 px width');
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{height}, 131, 'bottom-right window got 131 px height');
+
 # Mix ppt and px
 cmd 'resize set 75 ppt 200 px';
 

From e8d2b9b7b53b12f81d019b856c54ed2225ec820c Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 6 Sep 2018 14:48:21 +0300
Subject: [PATCH 153/218] tree_append_json: don't focus freed container

---
 src/load_layout.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/load_layout.c b/src/load_layout.c
index add78875..5f391ad7 100644
--- a/src/load_layout.c
+++ b/src/load_layout.c
@@ -632,6 +632,9 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
         while (incomplete-- > 0) {
             Con *parent = json_node->parent;
             DLOG("freeing incomplete container %p\n", json_node);
+            if (json_node == to_focus) {
+                to_focus = NULL;
+            }
             con_free(json_node);
             json_node = parent;
         }

From be6faa31617e3da2fc5c4af43821e9d76dc6d5ac Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 6 Sep 2018 14:54:27 +0300
Subject: [PATCH 154/218] tree_append_json: Allow strings that are not valid
 UTF8

Fixes #3156.

I couldn't reproduce the problem in a "natural" way so I cheated:
1. Start i3 with gdb
2. Set breakpoing on tree_restore
3. Run, open window, i3-msg restart
5. Open the file in *path with a hex editor
6. Edit the "name" field of the window and insert bytes that are not
valid UTF8
7. Continue

After parsing fails, all nodes including croot are incomplete, meaning
they have to be deleted. We can't recover in any reasonable way so we
have to allow non-UTF8 characters to avoid this situation altogether.
---
 src/load_layout.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/load_layout.c b/src/load_layout.c
index 5f391ad7..d2ad4e87 100644
--- a/src/load_layout.c
+++ b/src/load_layout.c
@@ -611,6 +611,10 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
     yajl_config(hand, yajl_allow_comments, true);
     /* Allow multiple values, i.e. multiple nodes to attach */
     yajl_config(hand, yajl_allow_multiple_values, true);
+    /* Allow strings that are not valid UTF8. Could be possible if a container
+     * name contains such characters. If yajl stops parsing because of this, an
+     * in-place restart could fail: see #3156. */
+    yajl_config(hand, yajl_dont_validate_strings, true);
     json_node = con;
     to_focus = NULL;
     incomplete = 0;

From a3dcee35f3a7739fb8f3e19d59e9064f696859cc Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Sun, 9 Sep 2018 16:09:39 +0300
Subject: [PATCH 155/218] tree_restore: Check croot

Related to #2414, #3156.
---
 src/tree.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/tree.c b/src/tree.c
index 96766b6a..5a7c5dfa 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -92,6 +92,10 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) {
 
     DLOG("appended tree, using new root\n");
     croot = TAILQ_FIRST(&(croot->nodes_head));
+    if (!croot) {
+        /* tree_append_json failed. Continuing here would segfault. */
+        goto out;
+    }
     DLOG("new root = %p\n", croot);
     Con *out = TAILQ_FIRST(&(croot->nodes_head));
     DLOG("out = %p\n", out);

From 6a1f653508052b631afc12a16c2854cb0eefe239 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Sun, 9 Sep 2018 15:32:54 +0300
Subject: [PATCH 156/218] libi3: validate UTF8 strings

Will validate container / window titles.

Fixes #3156.
---
 include/libi3.h                    |  2 +-
 libi3/string.c                     | 22 ++++++-------------
 src/load_layout.c                  | 13 +++++++++---
 testcases/t/300-restart-non-utf8.t | 34 ++++++++++++++++++++++++++++++
 4 files changed, 51 insertions(+), 20 deletions(-)
 create mode 100644 testcases/t/300-restart-non-utf8.t

diff --git a/include/libi3.h b/include/libi3.h
index d3b40796..91395328 100644
--- a/include/libi3.h
+++ b/include/libi3.h
@@ -200,7 +200,7 @@ i3String *i3string_from_markup(const char *from_markup);
  * Returns the newly-allocated i3String.
  *
  */
-i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes);
+i3String *i3string_from_utf8_with_length(const char *from_utf8, ssize_t num_bytes);
 
 /**
  * Build an i3String from an UTF-8 encoded string in Pango markup with fixed
diff --git a/libi3/string.c b/libi3/string.c
index edd588da..f78a140f 100644
--- a/libi3/string.c
+++ b/libi3/string.c
@@ -30,15 +30,7 @@ struct _i3String {
  *
  */
 i3String *i3string_from_utf8(const char *from_utf8) {
-    i3String *str = scalloc(1, sizeof(i3String));
-
-    /* Get the text */
-    str->utf8 = sstrdup(from_utf8);
-
-    /* Compute and store the length */
-    str->num_bytes = strlen(str->utf8);
-
-    return str;
+    return i3string_from_utf8_with_length(from_utf8, -1);
 }
 
 /*
@@ -60,16 +52,14 @@ i3String *i3string_from_markup(const char *from_markup) {
  * Returns the newly-allocated i3String.
  *
  */
-i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes) {
+i3String *i3string_from_utf8_with_length(const char *from_utf8, ssize_t num_bytes) {
     i3String *str = scalloc(1, sizeof(i3String));
 
-    /* Copy the actual text to our i3String */
-    str->utf8 = scalloc(num_bytes + 1, 1);
-    strncpy(str->utf8, from_utf8, num_bytes);
-    str->utf8[num_bytes] = '\0';
+    /* g_utf8_make_valid NULL-terminates the string. */
+    str->utf8 = g_utf8_make_valid(from_utf8, num_bytes);
 
-    /* Store the length */
-    str->num_bytes = num_bytes;
+    /* num_bytes < 0 means NULL-terminated string, need to calculate length */
+    str->num_bytes = num_bytes < 0 ? strlen(str->utf8) : (size_t)num_bytes;
 
     return str;
 }
diff --git a/src/load_layout.c b/src/load_layout.c
index d2ad4e87..32da9c47 100644
--- a/src/load_layout.c
+++ b/src/load_layout.c
@@ -611,9 +611,16 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
     yajl_config(hand, yajl_allow_comments, true);
     /* Allow multiple values, i.e. multiple nodes to attach */
     yajl_config(hand, yajl_allow_multiple_values, true);
-    /* Allow strings that are not valid UTF8. Could be possible if a container
-     * name contains such characters. If yajl stops parsing because of this, an
-     * in-place restart could fail: see #3156. */
+    /* We don't need to validate that the input is valid UTF8 here.
+     * tree_append_json is called in two cases:
+     * 1. With the append_layout command. json_validate is called first and will
+     *    fail on invalid UTF8 characters so we don't need to recheck.
+     * 2. With an in-place restart. The rest of the codebase should be
+     *    responsible for producing valid UTF8 JSON output. If not,
+     *    tree_append_json will just preserve invalid UTF8 strings in the tree
+     *    instead of failing to parse the layout file which could lead to
+     *    problems like in #3156.
+     * Either way, disabling UTF8 validation slightly speeds up yajl. */
     yajl_config(hand, yajl_dont_validate_strings, true);
     json_node = con;
     to_focus = NULL;
diff --git a/testcases/t/300-restart-non-utf8.t b/testcases/t/300-restart-non-utf8.t
new file mode 100644
index 00000000..81b56583
--- /dev/null
+++ b/testcases/t/300-restart-non-utf8.t
@@ -0,0 +1,34 @@
+#!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 i3 does not crash when restart is issued while a window with a
+# title that contains non-UTF8 characters is open.
+# Ticket: #3156
+# Bug still in: 4.15-241-g9dc4df81
+use i3test;
+
+my $ws = fresh_workspace;
+open_window(name => "\x{AA} <-- invalid");
+
+cmd 'restart';
+does_i3_live;
+
+# Confirm that the invalid character got replaced with U+FFFD - "REPLACEMENT
+# CHARACTER"
+cmd '[title="^' . "\x{fffd}" . ' <-- invalid$"] fullscreen enable';
+is_num_fullscreen($ws, 1, 'title based criterion works');
+
+done_testing;

From 3bfcbb88bf9bd45913a81c7ed3d3c01d0ce22ef5 Mon Sep 17 00:00:00 2001
From: Todd Walton 
Date: Mon, 10 Sep 2018 16:31:25 -0400
Subject: [PATCH 157/218] clarify JSON standard non-compliance

Modified section on the layout file's non-compliance with the JSON
standard. The section previously stated that having multiple top-level
JSON texts is non-compliant. This isn't the case. It's just that most
JSON parsers will treat that as if it is non-compliant.
---
 docs/layout-saving | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/docs/layout-saving b/docs/layout-saving
index e90ecada..f31b5e21 100644
--- a/docs/layout-saving
+++ b/docs/layout-saving
@@ -219,13 +219,15 @@ the window which matches any of the criteria. As an example:
 
 A layout file as generated by +i3-save-tree(1)+ is not strictly valid JSON:
 
-1. Layout files contain multiple “JSON documents” on the top level, whereas the
-   JSON standard only allows precisely one “document” (array or hash).
+1. Layout files contain multiple “JSON texts” at the top level. The JSON
+   standard doesn't prohibit this, but in practice most JSON parsers only
+   allow precisely one “text” per document/file, and will mark multiple texts
+   as invalid JSON.
 
-2. Layout files contain comments which are not standardized, but understood by
-   many parsers.
+2. Layout files contain comments which are not allowed by the JSON standard,
+   but are understood by many parsers.
 
-Both deviations from the JSON standard are to make manual editing by humans
+Both of these deviations from the norm are to make manual editing by humans
 easier. In case you are writing a more elaborate tool for manipulating these
 layouts, you can either use a JSON parser that supports these deviations (for
 example libyajl), transform the layout file to a JSON-conforming file, or

From e67be1ccd3888e12339f19de1f8df9e6b74021a1 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Tue, 11 Sep 2018 07:44:40 +0300
Subject: [PATCH 158/218] commands.c: Improve error replies
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Improve / add various error messages.
- Replace all `LOG(…); ysuccess(false);` with `yerror(…);`.
- switch_mode: Remove redundant "ERROR:" ELOG string.
- cmd_move_con_to_workspace*: Make sure that we don't try to move an
empty workspace to another workspace. This can be problematic when we
match a workspace using command criteria (eg marks) and the target is a
non-existing workspace. We create the new workspace but since nothing is
moved there, we are left with an empty workspace. See added testcase.
---
 src/bindings.c                   |   2 +-
 src/commands.c                   | 168 +++++++++++++------------------
 testcases/t/132-move-workspace.t |  11 ++
 3 files changed, 82 insertions(+), 99 deletions(-)

diff --git a/src/bindings.c b/src/bindings.c
index a0b876a4..ad5fe4f3 100644
--- a/src/bindings.c
+++ b/src/bindings.c
@@ -647,7 +647,7 @@ void switch_mode(const char *new_mode) {
         return;
     }
 
-    ELOG("ERROR: Mode not found\n");
+    ELOG("Mode not found\n");
 }
 
 static int reorder_binding_cmp(const void *a, const void *b) {
diff --git a/src/commands.c b/src/commands.c
index e07509c4..70f3bf18 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -269,6 +269,33 @@ static void move_matches_to_workspace(Con *ws) {
     }
 }
 
+#define CHECK_MOVE_CON_TO_WORKSPACE                                                          \
+    do {                                                                                     \
+        HANDLE_EMPTY_MATCH;                                                                  \
+        if (TAILQ_EMPTY(&owindows)) {                                                        \
+            yerror("Nothing to move: specified criteria don't match any window");            \
+            return;                                                                          \
+        } else {                                                                             \
+            bool found = false;                                                              \
+            owindow *current = TAILQ_FIRST(&owindows);                                       \
+            while (current) {                                                                \
+                owindow *next = TAILQ_NEXT(current, owindows);                               \
+                                                                                             \
+                if (current->con->type == CT_WORKSPACE && !con_has_children(current->con)) { \
+                    TAILQ_REMOVE(&owindows, current, owindows);                              \
+                } else {                                                                     \
+                    found = true;                                                            \
+                }                                                                            \
+                                                                                             \
+                current = next;                                                              \
+            }                                                                                \
+            if (!found) {                                                                    \
+                yerror("Nothing to move: workspace empty");                                  \
+                return;                                                                      \
+            }                                                                                \
+        }                                                                                    \
+    } while (0)
+
 /*
  * Implementation of 'move [window|container] [to] workspace
  * next|prev|next_on_output|prev_on_output|current'.
@@ -277,17 +304,7 @@ static void move_matches_to_workspace(Con *ws) {
 void cmd_move_con_to_workspace(I3_CMD, const char *which) {
     DLOG("which=%s\n", which);
 
-    /* We have nothing to move:
-     *  when criteria was specified but didn't match any window or
-     *  when criteria wasn't specified and we don't have any window focused. */
-    if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
-        (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
-         !con_has_children(focused))) {
-        ysuccess(false);
-        return;
-    }
-
-    HANDLE_EMPTY_MATCH;
+    CHECK_MOVE_CON_TO_WORKSPACE;
 
     /* get the workspace */
     Con *ws;
@@ -302,8 +319,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) {
     else if (strcmp(which, "current") == 0)
         ws = con_get_workspace(focused);
     else {
-        ELOG("BUG: called with which=%s\n", which);
-        ysuccess(false);
+        yerror("BUG: called with which=%s", which);
         return;
     }
 
@@ -338,36 +354,21 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
  * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace '.
  *
  */
-void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_forth) {
+void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *no_auto_back_and_forth) {
     if (strncasecmp(name, "__", strlen("__")) == 0) {
-        LOG("You cannot move containers to i3-internal workspaces (\"%s\").\n", name);
-        ysuccess(false);
+        yerror("You cannot move containers to i3-internal workspaces (\"%s\").", name);
         return;
     }
 
-    const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
-
-    /* We have nothing to move:
-     *  when criteria was specified but didn't match any window or
-     *  when criteria wasn't specified and we don't have any window focused. */
-    if (!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) {
-        ELOG("No windows match your criteria, cannot move.\n");
-        ysuccess(false);
-        return;
-    } else if (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
-               !con_has_children(focused)) {
-        ysuccess(false);
-        return;
-    }
+    CHECK_MOVE_CON_TO_WORKSPACE;
 
     LOG("should move window to workspace %s\n", name);
     /* get the workspace */
     Con *ws = workspace_get(name, NULL);
 
-    if (!no_auto_back_and_forth)
+    if (no_auto_back_and_forth == NULL) {
         ws = maybe_auto_back_and_forth_workspace(ws);
-
-    HANDLE_EMPTY_MATCH;
+    }
 
     move_matches_to_workspace(ws);
 
@@ -380,18 +381,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_au
  * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace number '.
  *
  */
-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);
-
-    /* We have nothing to move:
-     *  when criteria was specified but didn't match any window or
-     *  when criteria wasn't specified and we don't have any window focused. */
-    if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
-        (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
-         !con_has_children(focused))) {
-        ysuccess(false);
-        return;
-    }
+void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth) {
+    CHECK_MOVE_CON_TO_WORKSPACE;
 
     LOG("should move window to workspace %s\n", which);
 
@@ -407,10 +398,9 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no
         ws = workspace_get(which, NULL);
     }
 
-    if (!no_auto_back_and_forth)
+    if (no_auto_back_and_forth == NULL) {
         ws = maybe_auto_back_and_forth_workspace(ws);
-
-    HANDLE_EMPTY_MATCH;
+    }
 
     move_matches_to_workspace(ws);
 
@@ -504,7 +494,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *direct
 
     bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
     if (!res) {
-        yerror("No second container found in this direction.\n");
+        yerror("No second container found in this direction.");
         return false;
     }
 
@@ -524,7 +514,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *dir
     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);
+        yerror("Failed to find appropriate tiling containers for resize operation");
         return false;
     }
 
@@ -552,7 +542,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *dir
     }
     subtract_percent = ppt / (children - 1);
     if (ppt < 0.0 && new_current_percent < percent_for_1px(current)) {
-        yerror("Not resizing, container would end with less than 1px\n");
+        yerror("Not resizing, container would end with less than 1px");
         return false;
     }
 
@@ -565,7 +555,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *dir
                 continue;
             }
             if (child->percent - subtract_percent < percent_for_1px(child)) {
-                yerror("Not resizing, already at minimum size (child %p would end up with a size of %.f\n", child, child->percent - subtract_percent);
+                yerror("Not resizing, already at minimum size (child %p would end up with a size of %.f", child, child->percent - subtract_percent);
                 return false;
             }
         }
@@ -755,8 +745,7 @@ void cmd_border(I3_CMD, const char *border_style_str, long border_width) {
         } else if (strcmp(border_style_str, "none") == 0) {
             border_style = BS_NONE;
         } else {
-            ELOG("BUG: called with border_style=%s\n", border_style_str);
-            ysuccess(false);
+            yerror("BUG: called with border_style=%s", border_style_str);
             return;
         }
 
@@ -765,7 +754,6 @@ void cmd_border(I3_CMD, const char *border_style_str, long border_width) {
     }
 
     cmd_output->needs_tree_render = true;
-    // XXX: default reply for now, make this a better reply
     ysuccess(true);
 }
 
@@ -865,8 +853,7 @@ void cmd_workspace(I3_CMD, const char *which) {
     DLOG("which=%s\n", which);
 
     if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
-        LOG("Cannot switch workspace while in global fullscreen\n");
-        ysuccess(false);
+        yerror("Cannot switch workspace while in global fullscreen");
         return;
     }
 
@@ -879,8 +866,7 @@ void cmd_workspace(I3_CMD, const char *which) {
     else if (strcmp(which, "prev_on_output") == 0)
         ws = workspace_prev_on_output();
     else {
-        ELOG("BUG: called with which=%s\n", which);
-        ysuccess(false);
+        yerror("BUG: called with which=%s", which);
         return;
     }
 
@@ -899,15 +885,13 @@ void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_a
     const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
 
     if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
-        LOG("Cannot switch workspace while in global fullscreen\n");
-        ysuccess(false);
+        yerror("Cannot switch workspace while in global fullscreen");
         return;
     }
 
     long parsed_num = ws_name_to_number(which);
     if (parsed_num == -1) {
-        LOG("Could not parse initial part of \"%s\" as a number.\n", which);
-        yerror("Could not parse number \"%s\"", which);
+        yerror("Could not parse initial part of \"%s\" as a number.", which);
         return;
     }
 
@@ -936,8 +920,7 @@ void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_a
  */
 void cmd_workspace_back_and_forth(I3_CMD) {
     if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
-        LOG("Cannot switch workspace while in global fullscreen\n");
-        ysuccess(false);
+        yerror("Cannot switch workspace while in global fullscreen");
         return;
     }
 
@@ -956,14 +939,12 @@ void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_
     const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
 
     if (strncasecmp(name, "__", strlen("__")) == 0) {
-        LOG("You cannot switch to the i3-internal workspaces (\"%s\").\n", name);
-        ysuccess(false);
+        yerror("You cannot switch to the i3-internal workspaces (\"%s\").", name);
         return;
     }
 
     if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
-        LOG("Cannot switch workspace while in global fullscreen\n");
-        ysuccess(false);
+        yerror("Cannot switch workspace while in global fullscreen");
         return;
     }
 
@@ -988,7 +969,7 @@ void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle) {
 
     owindow *current = TAILQ_FIRST(&owindows);
     if (current == NULL) {
-        ysuccess(false);
+        yerror("Given criteria don't match a window");
         return;
     }
 
@@ -1132,28 +1113,24 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) {
 
         Output *current_output = get_output_for_con(ws);
         if (current_output == NULL) {
-            ELOG("Cannot get current output. This is a bug in i3.\n");
-            ysuccess(false);
+            yerror("Cannot get current output. This is a bug in i3.");
             return;
         }
 
         Output *target_output = get_output_from_string(current_output, name);
         if (!target_output) {
-            ELOG("Could not get output from string \"%s\"\n", name);
-            ysuccess(false);
+            yerror("Could not get output from string \"%s\"", name);
             return;
         }
 
         bool success = workspace_move_to_output(ws, target_output);
         if (!success) {
-            ELOG("Failed to move workspace to output.\n");
-            ysuccess(false);
+            yerror("Failed to move workspace to output.");
             return;
         }
     }
 
     cmd_output->needs_tree_render = true;
-    // XXX: default reply for now, make this a better reply
     ysuccess(true);
 }
 
@@ -1212,8 +1189,7 @@ void cmd_kill(I3_CMD, const char *kill_mode_str) {
     else if (strcmp(kill_mode_str, "client") == 0)
         kill_mode = KILL_CLIENT;
     else {
-        ELOG("BUG: called with kill_mode=%s\n", kill_mode_str);
-        ysuccess(false);
+        yerror("BUG: called with kill_mode=%s", kill_mode_str);
         return;
     }
 
@@ -1239,7 +1215,6 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) {
     DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
     start_application(command, no_startup_id);
 
-    // XXX: default reply for now, make this a better reply
     ysuccess(true);
 }
 
@@ -1681,8 +1656,7 @@ void cmd_focus_output(I3_CMD, const char *name) {
     output = get_output_from_string(current_output, name);
 
     if (!output) {
-        LOG("No such output found.\n");
-        ysuccess(false);
+        yerror("No such output found.");
         return;
     }
 
@@ -1690,7 +1664,7 @@ void cmd_focus_output(I3_CMD, const char *name) {
     Con *ws = NULL;
     GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
     if (!ws) {
-        ysuccess(false);
+        yerror("BUG: No workspace found on output.");
         return;
     }
 
@@ -1859,7 +1833,11 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
 
     owindow *match = TAILQ_FIRST(&owindows);
     if (match == NULL) {
-        DLOG("No match found for swapping.\n");
+        yerror("No match found for swapping.");
+        return;
+    }
+    if (match->con == NULL) {
+        yerror("Match %p has no container.", match);
         return;
     }
 
@@ -1867,7 +1845,7 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
     if (strcmp(mode, "id") == 0) {
         long target;
         if (!parse_long(arg, &target, 0)) {
-            yerror("Failed to parse %s into a window id.\n", arg);
+            yerror("Failed to parse %s into a window id.", arg);
             return;
         }
 
@@ -1875,7 +1853,7 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
     } else if (strcmp(mode, "con_id") == 0) {
         long target;
         if (!parse_long(arg, &target, 0)) {
-            yerror("Failed to parse %s into a container id.\n", arg);
+            yerror("Failed to parse %s into a container id.", arg);
             return;
         }
 
@@ -1883,29 +1861,24 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
     } else if (strcmp(mode, "mark") == 0) {
         con = con_by_mark(arg);
     } else {
-        yerror("Unhandled swap mode \"%s\". This is a bug.\n", mode);
+        yerror("Unhandled swap mode \"%s\". This is a bug.", mode);
         return;
     }
 
     if (con == NULL) {
-        yerror("Could not find container for %s = %s\n", mode, arg);
+        yerror("Could not find container for %s = %s", mode, arg);
         return;
     }
 
     if (match != TAILQ_LAST(&owindows, owindows_head)) {
-        DLOG("More than one container matched the swap command, only using the first one.");
-    }
-
-    if (match->con == NULL) {
-        DLOG("Match %p has no container.\n", match);
-        ysuccess(false);
-        return;
+        LOG("More than one container matched the swap command, only using the first one.");
     }
 
     DLOG("Swapping %p with %p.\n", match->con, con);
     bool result = con_swap(match->con, con);
 
     cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
     ysuccess(result);
 }
 
@@ -1958,8 +1931,7 @@ void cmd_title_format(I3_CMD, const char *format) {
  */
 void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
     if (strncasecmp(new_name, "__", strlen("__")) == 0) {
-        LOG("Cannot rename workspace to \"%s\": names starting with __ are i3-internal.\n", new_name);
-        ysuccess(false);
+        yerror("Cannot rename workspace to \"%s\": names starting with __ are i3-internal.", new_name);
         return;
     }
     if (old_name) {
@@ -2183,7 +2155,7 @@ void cmd_shmlog(I3_CMD, const char *argument) {
     else {
         long new_size = 0;
         if (!parse_long(argument, &new_size, 0)) {
-            yerror("Failed to parse %s into a shmlog size.\n", argument);
+            yerror("Failed to parse %s into a shmlog size.", argument);
             return;
         }
         /* If shm logging now, restart logging with the new size. */
diff --git a/testcases/t/132-move-workspace.t b/testcases/t/132-move-workspace.t
index 1a4beacc..60705f96 100644
--- a/testcases/t/132-move-workspace.t
+++ b/testcases/t/132-move-workspace.t
@@ -339,4 +339,15 @@ $ws = get_ws($tmp2);
 is_num_children($tmp2, 0, 'no regular nodes on second workspace');
 is(@{$ws->{floating_nodes}}, 1, 'one floating node on second workspace');
 
+###################################################################
+# Check that moving an empty workspace using criteria doesn't
+# create unfocused empty workspace.
+###################################################################
+$tmp2 = get_unused_workspace();
+$tmp = fresh_workspace();
+cmd 'mark a';
+cmd "[con_mark=a] move to workspace $tmp2";
+
+is (get_ws($tmp2), undef, 'No empty workspace created');
+
 done_testing;

From a66048a95625a10784c3f577258b24e6efdbe2a4 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Wed, 12 Sep 2018 14:11:08 +0300
Subject: [PATCH 159/218] i3-save-tree: Exclude unsupported "transient_for"
 property

Even if i3 supported matching windows through "transient_for", it
wouldn't be useful for the purpose of i3-save-tree.
---
 i3-save-tree | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/i3-save-tree b/i3-save-tree
index 1e56a045..da5e6ded 100755
--- a/i3-save-tree
+++ b/i3-save-tree
@@ -123,9 +123,7 @@ sub strip_containers {
     delete $tree->{current_border_width} if $tree->{current_border_width} == -1;
 
     for my $key (keys %$tree) {
-        next if exists($allowed_keys{$key});
-
-        delete $tree->{$key};
+        delete $tree->{$key} unless exists($allowed_keys{$key});
     }
 
     for my $key (qw(nodes floating_nodes)) {
@@ -169,7 +167,8 @@ sub dump_containers {
     if (leaf_node($tree)) {
         my $swallows = {};
         for my $property (keys %{$tree->{window_properties}}) {
-            $swallows->{$property} = '^' . quotemeta($tree->{window_properties}->{$property}) . '$';
+            $swallows->{$property} = '^' . quotemeta($tree->{window_properties}->{$property}) . '$'
+                if $property ne 'transient_for';
         }
         $tree->{swallows} = [ $swallows ];
     }

From 68628e153f4ce5b656786e915bb781e22477e020 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 13 Sep 2018 14:09:35 +0300
Subject: [PATCH 160/218] Make t/156-fullscreen-focus.t easier to work with

- Add routine that will refocus the expected window on test failure.
Thus, failure on one test will not make others fail.
- Remove some redundant commands, prefer fresh_workspace for screen
changing.
- Kill previous windows between sections if the next section does not
depend on the previous layout.
---
 testcases/t/156-fullscreen-focus.t | 188 +++++++++++++----------------
 1 file changed, 82 insertions(+), 106 deletions(-)

diff --git a/testcases/t/156-fullscreen-focus.t b/testcases/t/156-fullscreen-focus.t
index 9c396f40..62711915 100644
--- a/testcases/t/156-fullscreen-focus.t
+++ b/testcases/t/156-fullscreen-focus.t
@@ -29,70 +29,55 @@ EOT
 # | S1 | S2 |
 # +----+----+
 
-my $tmp = fresh_workspace;
+sub verify_focus {
+    # Report original line
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    my ($expected, $msg) = @_;
+    $expected = $expected->id;
+
+    # Ensure the expected focus if the test fails.
+    cmd "[id=$expected] focus" unless is($x->input_focus, $expected, $msg);
+}
 
 ################################################################################
-# Open the left window.
+# Verify that window opened behind fullscreen window will get focus after the
+# fullscreen window gets moved to a different workspace.
 ################################################################################
 
-my $left = open_window({ background_color => '#ff0000' });
-
-is($x->input_focus, $left->id, 'left window focused');
-
+fresh_workspace;
+my $left = open_window;
+verify_focus($left, 'left window focused');
 diag("left = " . $left->id);
 
-################################################################################
-# Open the right window.
-################################################################################
-
-my $right = open_window({ background_color => '#00ff00' });
-
+my $right = open_window;
+cmd 'fullscreen';
 diag("right = " . $right->id);
 
-################################################################################
-# Set the right window to fullscreen.
-################################################################################
-
-cmd 'nop setting fullscreen';
-cmd 'fullscreen';
-
-################################################################################
-# Open a third window. Since we're fullscreen, the window won't be # mapped, so
+# Open a third window. Since we're fullscreen, the window won't be mapped, so
 # don't wait for it to be mapped. Instead, just send the map request and sync
 # with i3 to make sure i3 recognizes it.
-################################################################################
-
-my $third = open_window({
-        background_color => '#0000ff',
-        name => 'Third window',
-        dont_map => 1,
-    });
-
+my $third = open_window({dont_map => 1});
 $third->map;
-
 sync_with_i3;
-
 diag("third = " . $third->id);
 
-################################################################################
 # Move the window to a different workspace, and verify that the third window now
 # gets focused in the current workspace.
-################################################################################
-
 my $tmp2 = get_unused_workspace;
-
 cmd "move workspace $tmp2";
+verify_focus($third, 'third window focused');
 
-is($x->input_focus, $third->id, 'third window focused');
+kill_all_windows;
 
 ################################################################################
 # Ensure that moving a window to a workspace which has a fullscreen window does
 # not focus it (otherwise the user cannot get out of fullscreen mode anymore).
 ################################################################################
 
-$tmp = fresh_workspace;
+my $tmp = fresh_workspace;
 
-my $fullscreen_window = open_window;
+open_window;
 cmd 'fullscreen';
 
 my $nodes = get_ws_content($tmp);
@@ -111,32 +96,18 @@ is(scalar @$nodes, 2, 'precisely two windows');
 is($nodes->[0]->{id}, $old_id, 'id unchanged');
 is($nodes->[0]->{focused}, 1, 'fullscreen window focused');
 
+kill_all_windows;
+
 ################################################################################
 # Ensure it's possible to change focus if it doesn't escape the fullscreen
 # container with fullscreen global. We can't even focus a container in a
 # different workspace.
 ################################################################################
 
-cmd 'fullscreen';
-
-# Focus screen 1
-sync_with_i3;
-$x->root->warp_pointer(1025, 0);
-sync_with_i3;
-
-$tmp = fresh_workspace;
-cmd "workspace $tmp";
+$tmp = fresh_workspace(output => 1);
 my $diff_ws = open_window;
 
-# Focus screen 0
-sync_with_i3;
-$x->root->warp_pointer(0, 0);
-sync_with_i3;
-
-$tmp2 = fresh_workspace;
-cmd "workspace $tmp2";
-cmd 'split h';
-
+$tmp2 = fresh_workspace(output => 0);
 $left = open_window;
 my $right1 = open_window;
 cmd 'split v';
@@ -146,97 +117,99 @@ cmd 'focus parent';
 cmd 'fullscreen global';
 
 cmd '[id="' . $right1->id . '"] focus';
-is($x->input_focus, $right1->id, 'upper right window focused');
+verify_focus($right1, 'upper right window focused');
 
 cmd '[id="' . $right2->id . '"] focus';
-is($x->input_focus, $right2->id, 'bottom right window focused');
+verify_focus($right2, 'bottom right window focused');
 
 cmd 'focus parent';
 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');
+verify_focus($right2, 'bottom right window focused again');
 
 cmd 'focus up';
-is($x->input_focus, $right1->id, 'allowed focus up');
+verify_focus($right1, 'allowed focus up');
 
 cmd 'focus down';
-is($x->input_focus, $right2->id, 'allowed focus down');
+verify_focus($right2, 'allowed focus down');
 
 cmd 'focus left';
-is($x->input_focus, $right2->id, 'prevented focus left');
+verify_focus($right2, 'prevented focus left');
 
 cmd 'focus right';
-is($x->input_focus, $right2->id, 'prevented focus right');
+verify_focus($right2, 'prevented focus right');
 
 cmd 'focus down';
-is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
+verify_focus($right1, 'allowed focus wrap (down)');
 
 cmd 'focus up';
-is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
+verify_focus($right2, 'allowed focus wrap (up)');
 
 ################################################################################
+# (depends on previous layout)
 # Same tests when we're in non-global fullscreen mode. It should now be possible
 # to focus a container in a different workspace.
 ################################################################################
 
 cmd 'focus parent';
-cmd 'fullscreen global';
+cmd 'fullscreen disable';
 cmd 'fullscreen';
 
 cmd '[id="' . $right1->id . '"] focus';
-is($x->input_focus, $right1->id, 'upper right window focused');
+verify_focus($right1, 'upper right window focused');
 
 cmd '[id="' . $right2->id . '"] focus';
-is($x->input_focus, $right2->id, 'bottom right window focused');
+verify_focus($right2, 'bottom right window focused');
 
 cmd 'focus parent';
 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');
+verify_focus($right2, 'bottom right window focused again');
 
 cmd 'focus up';
-is($x->input_focus, $right1->id, 'allowed focus up');
+verify_focus($right1, 'allowed focus up');
 
 cmd 'focus down';
-is($x->input_focus, $right2->id, 'allowed focus down');
+verify_focus($right2, 'allowed focus down');
 
 cmd 'focus down';
-is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
+verify_focus($right1, 'allowed focus wrap (down)');
 
 cmd 'focus up';
-is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
+verify_focus($right2, 'allowed focus wrap (up)');
 
 cmd 'focus left';
-is($x->input_focus, $right2->id, 'focus left wrapped (no-op)');
+verify_focus($right2, 'focus left wrapped (no-op)');
 
 cmd 'focus right';
-is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws');
+verify_focus($diff_ws, 'allowed focus change to different ws');
 
 cmd 'focus left';
-is($x->input_focus, $right2->id, 'focused back into fullscreen container');
+verify_focus($right2, 'focused back into fullscreen container');
 
 cmd '[id="' . $diff_ws->id . '"] focus';
-is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws by id');
+verify_focus($diff_ws, 'allowed focus change to different ws by id');
 
 ################################################################################
+# (depends on previous layout)
 # More testing of the interaction between wrapping and the fullscreen focus
 # restrictions.
 ################################################################################
 
 cmd '[id="' . $right1->id . '"] focus';
-is($x->input_focus, $right1->id, 'upper right window focused');
+verify_focus($right1, 'upper right window focused');
 
 cmd 'focus parent';
-cmd 'fullscreen';
+cmd 'fullscreen disable';
 cmd 'focus child';
 
 cmd 'split v';
 my $right12 = open_window;
 
 cmd 'focus down';
-is($x->input_focus, $right2->id, 'bottom right window focused');
+verify_focus($right2, 'bottom right window focused');
 
 cmd 'split v';
 my $right22 = open_window;
@@ -246,18 +219,19 @@ cmd 'fullscreen';
 cmd 'focus child';
 
 cmd 'focus down';
-is($x->input_focus, $right2->id, 'focus did not leave parent container (1)');
+verify_focus($right2, 'focus did not leave parent container (1)');
 
 cmd 'focus down';
-is($x->input_focus, $right22->id, 'focus did not leave parent container (2)');
+verify_focus($right22, 'focus did not leave parent container (2)');
 
 cmd 'focus up';
-is($x->input_focus, $right2->id, 'focus did not leave parent container (3)');
+verify_focus($right2, 'focus did not leave parent container (3)');
 
 cmd 'focus up';
-is($x->input_focus, $right22->id, 'focus did not leave parent container (4)');
+verify_focus($right22, 'focus did not leave parent container (4)');
 
 ################################################################################
+# (depends on previous layout)
 # Ensure that moving in a direction doesn't violate the focus restrictions.
 ################################################################################
 
@@ -281,6 +255,7 @@ cmd 'move up';
 verify_move(2, 'prevented move up');
 
 ################################################################################
+# (depends on previous layout)
 # Moving to a different workspace is allowed with per-output fullscreen
 # containers.
 ################################################################################
@@ -296,6 +271,7 @@ cmd "move to workspace prev";
 verify_move(1, 'did not prevent move to workspace by position');
 
 ################################################################################
+# (depends on previous layout)
 # Ensure that is not allowed with global fullscreen containers.
 ################################################################################
 
@@ -304,7 +280,7 @@ cmd "move to workspace $tmp2";
 cmd "workspace $tmp2";
 
 cmd 'focus parent';
-cmd 'fullscreen';
+cmd 'fullscreen disable';
 cmd 'fullscreen global';
 cmd 'focus child';
 
@@ -314,6 +290,8 @@ verify_move(2, 'prevented move to workspace by name');
 cmd "move to workspace prev";
 verify_move(2, 'prevented move to workspace by position');
 
+kill_all_windows;
+
 ################################################################################
 # Ensure it's possible to focus a window using the focus command despite
 # fullscreen window blocking it. Fullscreen window should lose its fullscreen
@@ -321,96 +299,94 @@ verify_move(2, 'prevented move to workspace by position');
 ################################################################################
 
 # 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');
+verify_focus($second, 'fullscreen window focused');
 is_num_fullscreen($tmp, 1, '1 fullscreen window');
 
-cmd '[id="'. $first->id .'"] focus';
+cmd '[id="' . $first->id . '"] focus';
 sync_with_i3;
 
-is($x->input_focus, $first->id, 'correctly focused using id');
+verify_focus($first, 'correctly focused using id');
 is_num_fullscreen($tmp, 0, 'no fullscreen windows');
 
-# first floating, second tiling, focus using 'focus floating'
 kill_all_windows;
 
+# first floating, second tiling, focus using 'focus floating'
 $tmp = fresh_workspace;
 $first = open_floating_window;
 $second = open_window;
 cmd 'fullscreen';
-is($x->input_focus, $second->id, 'fullscreen window focused');
+verify_focus($second, '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');
+verify_focus($first, 'correctly focused using focus floating');
 is_num_fullscreen($tmp, 0, 'no fullscreen windows');
 
-# first tiling, second floating, focus using 'focus tiling'
 kill_all_windows;
 
+# first tiling, second floating, focus using 'focus tiling'
 $tmp = fresh_workspace;
 $first = open_window;
 $second = open_floating_window;
 cmd 'fullscreen';
-is($x->input_focus, $second->id, 'fullscreen window focused');
+verify_focus($second, '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');
+verify_focus($first, 'correctly focused using focus tiling');
 is_num_fullscreen($tmp, 0, 'no fullscreen windows');
 
+kill_all_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;
 $first = open_window;
 
 $tmp2 = fresh_workspace;
 $second = open_window;
 cmd 'fullscreen';
-is($x->input_focus, $second->id, 'fullscreen window focused');
+verify_focus($second, 'fullscreen window focused');
 is_num_fullscreen($tmp2, 1, '1 fullscreen window');
 
-cmd '[id="'. $first->id .'"] focus';
+cmd '[id="' . $first->id . '"] focus';
 sync_with_i3;
 
-is($x->input_focus, $first->id, 'correctly focused using focus id');
+verify_focus($first, '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');
 
+kill_all_windows;
+
 ################################################################################
 # 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');
+verify_focus($second, 'global window focused');
 is_num_fullscreen($tmp2, 1, '1 fullscreen window');
 
-cmd '[id="'. $first->id .'"] focus';
+cmd '[id="' . $first->id . '"] focus';
 sync_with_i3;
 
-is($x->input_focus, $first->id, 'correctly focused using focus id');
+verify_focus($first, 'correctly focused using focus id');
 is_num_fullscreen($tmp2, 0, 'no fullscreen windows');
 
 

From 702e83c95ed75e6debbff8729177a8560f373956 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 14 Sep 2018 14:04:54 +0300
Subject: [PATCH 161/218] dump-asy.pl: Include floating_nodes

---
 contrib/dump-asy.pl | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl
index 2c244752..5af4c72f 100755
--- a/contrib/dump-asy.pl
+++ b/contrib/dump-asy.pl
@@ -46,7 +46,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} or "[Empty]");
+    my $na = ($n->{name} or ($n->{type} eq "floating_con" ? "[Floating con]" : "[Empty]"));
     $na =~ s/#/\\#/g;
     $na =~ s/\$/\\\$/g;
     $na =~ s/&/\\&/g;
@@ -64,7 +64,8 @@ sub dump_node {
     print $tmp "n" . $parent->{id} . ", " if defined($parent);
     print $tmp "\"" . $name . "\");\n";
 
-	dump_node($_, $n) for @{$n->{nodes}};
+    dump_node($_, $n) for @{$n->{nodes}};
+    dump_node($_, $n) for @{$n->{floating_nodes}};
 }
 
 sub find_node_with_name {

From d407393d0da3dce2382b60c6abf6540a174798a1 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 14 Sep 2018 16:09:45 +0300
Subject: [PATCH 162/218] _con_move_to_con: Move upwards only on
 CT_FLOATING_CON

If target is inside a floating container but not its direct child, the
move can be completed as is.

Fixes #3402.
---
 src/con.c                      | 12 ++++++------
 testcases/t/243-move-to-mark.t | 21 +++++++++++++++++++++
 2 files changed, 27 insertions(+), 6 deletions(-)

diff --git a/src/con.c b/src/con.c
index ca65a150..a620a571 100644
--- a/src/con.c
+++ b/src/con.c
@@ -1171,13 +1171,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
         target = target->parent;
     }
 
-    /* 3: if the target container is floating, we get the workspace instead.
-     * Only tiling windows need to get inserted next to the current container.
-     * */
-    Con *floatingcon = con_inside_floating(target);
-    if (floatingcon != NULL) {
+    /* 3: if the original target is the direct child of a floating container, we
+     * can't move con next to it - floating containers have only one child - so
+     * we get the workspace instead. */
+    if (target->type == CT_FLOATING_CON) {
         DLOG("floatingcon, going up even further\n");
-        target = floatingcon->parent;
+        orig_target = target;
+        target = target->parent;
     }
 
     if (con->type == CT_FLOATING_CON) {
diff --git a/testcases/t/243-move-to-mark.t b/testcases/t/243-move-to-mark.t
index c6b67f35..5e806cd4 100644
--- a/testcases/t/243-move-to-mark.t
+++ b/testcases/t/243-move-to-mark.t
@@ -321,6 +321,27 @@ sync_with_i3;
 ($nodes, $focus) = get_ws_content($target_ws);
 is(@{$nodes}, 1, 'tiling container moved to the target workspace');
 
+###############################################################################
+# Given 'S' and 'M' where 'M' is inside a floating container but not its direct
+# child, when 'S' is moved to 'M', i3 should not crash.
+# See issue: #3402
+###############################################################################
+
+$target_ws = fresh_workspace;
+$S = open_window;
+open_window;
+cmd 'splitv';
+$M = open_window;
+cmd 'mark target';
+cmd 'focus parent, floating enable, focus child';
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+does_i3_live;
+
+# Note: this is not actively supported behavior.
+$nodes = get_ws($target_ws)->{floating_nodes}->[0]->{nodes}->[0]->{nodes};
+is(1, (grep { $_->{window} == $S->{id} } @{$nodes}), 'tiling container moved inside floating container');
+
 ###############################################################################
 # Given 'S' and 'M' are the same container, when 'S' is moved to 'M', then
 # the command is ignored.

From 9936d049cf7c5acf0af24f335b3bc6cca49ddbee Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Sat, 15 Sep 2018 13:27:18 +0300
Subject: [PATCH 163/218] i3bar: Include sb_hoff_px only when needed

Fixes #3404.
---
 i3bar/src/xcb.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c
index ae6c0abc..0719576d 100644
--- a/i3bar/src/xcb.c
+++ b/i3bar/src/xcb.c
@@ -501,7 +501,7 @@ void handle_button(xcb_button_press_event_t *event) {
          * check if a status block has been clicked. */
         int tray_width = get_tray_width(walk->trayclients);
         int block_x = 0, last_block_x;
-        int offset = walk->rect.w - walk->statusline_width - tray_width - logical_px(sb_hoff_px);
+        int offset = walk->rect.w - walk->statusline_width - tray_width - logical_px((tray_width > 0) * sb_hoff_px);
         int32_t statusline_x = x - offset;
 
         if (statusline_x >= 0 && statusline_x < walk->statusline_width) {
@@ -2005,7 +2005,8 @@ void draw_bars(bool unhide) {
             DLOG("Printing statusline!\n");
 
             int tray_width = get_tray_width(outputs_walk->trayclients);
-            uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - 2 * logical_px(sb_hoff_px);
+            uint32_t hoff = logical_px(((workspace_width > 0) + (tray_width > 0)) * sb_hoff_px);
+            uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - hoff;
             uint32_t clip_left = 0;
             uint32_t statusline_width = full_statusline_width;
             bool use_short_text = false;
@@ -2019,7 +2020,7 @@ void draw_bars(bool unhide) {
             }
 
             int16_t visible_statusline_width = MIN(statusline_width, max_statusline_width);
-            int x_dest = outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width;
+            int x_dest = outputs_walk->rect.w - tray_width - logical_px((tray_width > 0) * sb_hoff_px) - visible_statusline_width;
 
             draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text);
             draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,

From 515dd45f59db6feee4e46f94bd5e616c49d07c62 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Sat, 15 Sep 2018 12:07:10 +0300
Subject: [PATCH 164/218] match_matches_window: Check if focused->window exists

Fixes #3406.
---
 src/match.c                                | 76 +++++++---------------
 testcases/t/251-command-criteria-focused.t | 10 +++
 2 files changed, 35 insertions(+), 51 deletions(-)

diff --git a/src/match.c b/src/match.c
index b3136ab9..83e37327 100644
--- a/src/match.c
+++ b/src/match.c
@@ -87,31 +87,30 @@ void match_copy(Match *dest, Match *src) {
 bool match_matches_window(Match *match, i3Window *window) {
     LOG("Checking window 0x%08x (class %s)\n", window->id, window->class_class);
 
-    if (match->class != NULL) {
-        if (window->class_class == NULL)
-            return false;
-        if (strcmp(match->class->pattern, "__focused__") == 0 &&
-            strcmp(window->class_class, focused->window->class_class) == 0) {
-            LOG("window class matches focused window\n");
-        } else if (regex_matches(match->class, window->class_class)) {
-            LOG("window class matches (%s)\n", window->class_class);
-        } else {
-            return false;
-        }
-    }
+#define GET_FIELD_str(field) (field)
+#define GET_FIELD_i3string(field) (i3string_as_utf8(field))
+#define CHECK_WINDOW_FIELD(match_field, window_field, type)                                       \
+    do {                                                                                          \
+        if (match->match_field != NULL) {                                                         \
+            if (window->window_field == NULL) {                                                   \
+                return false;                                                                     \
+            }                                                                                     \
+                                                                                                  \
+            const char *window_field_str = GET_FIELD_##type(window->window_field);                \
+            if (strcmp(match->match_field->pattern, "__focused__") == 0 &&                        \
+                focused && focused->window && focused->window->window_field &&                    \
+                strcmp(window_field_str, GET_FIELD_##type(focused->window->window_field)) == 0) { \
+                LOG("window " #match_field " matches focused window\n");                          \
+            } else if (regex_matches(match->match_field, window_field_str)) {                     \
+                LOG("window " #match_field " matches (%s)\n", window_field_str);                  \
+            } else {                                                                              \
+                return false;                                                                     \
+            }                                                                                     \
+        }                                                                                         \
+    } while (0)
 
-    if (match->instance != NULL) {
-        if (window->class_instance == NULL)
-            return false;
-        if (strcmp(match->instance->pattern, "__focused__") == 0 &&
-            strcmp(window->class_instance, focused->window->class_instance) == 0) {
-            LOG("window instance matches focused window\n");
-        } else if (regex_matches(match->instance, window->class_instance)) {
-            LOG("window instance matches (%s)\n", window->class_instance);
-        } else {
-            return false;
-        }
-    }
+    CHECK_WINDOW_FIELD(class, class_class, str);
+    CHECK_WINDOW_FIELD(instance, class_instance, str);
 
     if (match->id != XCB_NONE) {
         if (window->id == match->id) {
@@ -122,33 +121,8 @@ bool match_matches_window(Match *match, i3Window *window) {
         }
     }
 
-    if (match->title != NULL) {
-        if (window->name == NULL)
-            return false;
-
-        const char *title = i3string_as_utf8(window->name);
-        if (strcmp(match->title->pattern, "__focused__") == 0 &&
-            strcmp(title, i3string_as_utf8(focused->window->name)) == 0) {
-            LOG("window title matches focused window\n");
-        } else if (regex_matches(match->title, title)) {
-            LOG("title matches (%s)\n", title);
-        } else {
-            return false;
-        }
-    }
-
-    if (match->window_role != NULL) {
-        if (window->role == NULL)
-            return false;
-        if (strcmp(match->window_role->pattern, "__focused__") == 0 &&
-            strcmp(window->role, focused->window->role) == 0) {
-            LOG("window role matches focused window\n");
-        } else if (regex_matches(match->window_role, window->role)) {
-            LOG("window_role matches (%s)\n", window->role);
-        } else {
-            return false;
-        }
-    }
+    CHECK_WINDOW_FIELD(title, name, i3string);
+    CHECK_WINDOW_FIELD(window_role, role, str);
 
     if (match->window_type != UINT32_MAX) {
         if (window->window_type == match->window_type) {
diff --git a/testcases/t/251-command-criteria-focused.t b/testcases/t/251-command-criteria-focused.t
index 225394f7..a880f591 100644
--- a/testcases/t/251-command-criteria-focused.t
+++ b/testcases/t/251-command-criteria-focused.t
@@ -105,6 +105,16 @@ is(@{get_ws($ws)->{nodes}}, 2, 'sanity check: workspace contains two windows');
 cmd '[workspace=__focused__] move to workspace trash';
 is(@{get_ws($ws)->{nodes}}, 0, '__focused__ works for workspace');
 
+###############################################################################
+# 6: Test that __focused__ in command criteria when no window is focused does
+# not crash i3.
+# See issue: #3406
+###############################################################################
+
+fresh_workspace;
+cmd '[class=__focused__] focus';
+does_i3_live;
+
 ###############################################################################
 
 done_testing;

From fed059a2b77a6a12f4b1fe65c8e4914e4a61b372 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 6 Sep 2018 02:21:10 +0300
Subject: [PATCH 165/218] Fix "relative_x" and "width" of click events

Now clicks begin at the start of the "actual" block, offsets and
separators don't trigger click events. The width property is now just
the width of the block, including border.

Fixes #3380.
---
 i3bar/src/xcb.c | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c
index 0719576d..f4509c26 100644
--- a/i3bar/src/xcb.c
+++ b/i3bar/src/xcb.c
@@ -500,13 +500,12 @@ void handle_button(xcb_button_press_event_t *event) {
         /* If the child asked for click events,
          * check if a status block has been clicked. */
         int tray_width = get_tray_width(walk->trayclients);
-        int block_x = 0, last_block_x;
+        int last_block_x = 0;
         int offset = walk->rect.w - walk->statusline_width - tray_width - logical_px((tray_width > 0) * sb_hoff_px);
         int32_t statusline_x = x - offset;
 
         if (statusline_x >= 0 && statusline_x < walk->statusline_width) {
             struct status_block *block;
-            int sep_offset_remainder = 0;
 
             TAILQ_FOREACH(block, &statusline_head, blocks) {
                 i3String *text = block->full_text;
@@ -519,16 +518,14 @@ void handle_button(xcb_button_press_event_t *event) {
                 if (i3string_get_num_bytes(text) == 0)
                     continue;
 
-                last_block_x = block_x;
-                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) {
+                const int relative_x = statusline_x - last_block_x;
+                if (relative_x >= 0 && (uint32_t)relative_x <= render->width) {
                     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);
+                                       event->root_x, event->root_y, relative_x, event->event_y, render->width, bar_height);
                     return;
                 }
 
-                sep_offset_remainder = block->sep_block_width - get_sep_offset(block);
+                last_block_x += render->width + render->x_append + render->x_offset + block->sep_block_width;
             }
         }
     }

From 9b06f1ab054964a4645e865aab5e9a837c1f4544 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Sun, 16 Sep 2018 15:04:20 +0300
Subject: [PATCH 166/218] docs/i3bar-protocol: Mention skipping blocks with
 empty full_text

Closes #3405.
---
 docs/i3bar-protocol | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol
index 8b7d2af7..62706f74 100644
--- a/docs/i3bar-protocol
+++ b/docs/i3bar-protocol
@@ -118,7 +118,8 @@ click_events::
 
 full_text::
 	The +full_text+ will be displayed by i3bar on the status line. This is the
-	only required key.
+	only required key. If +full_text+ is an empty string, the block will be
+	skipped.
 short_text::
 	Where appropriate, the +short_text+ (string) entry should also be
 	provided. It will be used in case the status line needs to be shortened

From 690e6d1f0e62437c06918ee21484cfafbce336ac Mon Sep 17 00:00:00 2001
From: Thomas Fischer 
Date: Thu, 13 Sep 2018 16:16:45 -0700
Subject: [PATCH 167/218] Respect max size in WM_NORMAL_HINTS

---
 include/data.h                         |  4 ++
 src/floating.c                         | 12 ++++
 src/handlers.c                         |  9 +++
 src/manage.c                           |  6 ++
 testcases/t/189-floating-constraints.t | 79 ++++++++++++++++++++++++++
 5 files changed, 110 insertions(+)

diff --git a/include/data.h b/include/data.h
index 32fb098f..f55e003d 100644
--- a/include/data.h
+++ b/include/data.h
@@ -477,6 +477,10 @@ struct Window {
     int min_width;
     int min_height;
 
+    /* Maximum size specified for the window. */
+    int max_width;
+    int max_height;
+
     /* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */
     double aspect_ratio;
 };
diff --git a/src/floating.c b/src/floating.c
index fac14a7a..be514ce2 100644
--- a/src/floating.c
+++ b/src/floating.c
@@ -96,6 +96,18 @@ void floating_check_size(Con *floating_con) {
             floating_con->rect.height += border_rect.height;
         }
 
+        if (focused_con->window->max_width) {
+            floating_con->rect.width -= border_rect.width;
+            floating_con->rect.width = min(floating_con->rect.width, focused_con->window->max_width);
+            floating_con->rect.width += border_rect.width;
+        }
+
+        if (focused_con->window->max_height) {
+            floating_con->rect.height -= border_rect.height;
+            floating_con->rect.height = min(floating_con->rect.height, focused_con->window->max_height);
+            floating_con->rect.height += border_rect.height;
+        }
+
         if (focused_con->window->height_increment &&
             floating_con->rect.height >= focused_con->window->base_height + border_rect.height) {
             floating_con->rect.height -= focused_con->window->base_height + border_rect.height;
diff --git a/src/handlers.c b/src/handlers.c
index d1b3a73c..d2232965 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -981,9 +981,18 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
         con->window->min_height = size_hints.min_height;
     }
 
+    if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) {
+        DLOG("Maximum size: %d (width) x %d (height)\n", size_hints.max_width, size_hints.max_height);
+
+        con->window->max_width = size_hints.max_width;
+        con->window->max_height = size_hints.max_height;
+    }
+
     if (con_is_floating(con)) {
         win_width = MAX(win_width, con->window->min_width);
         win_height = MAX(win_height, con->window->min_height);
+        win_width = MIN(win_width, con->window->max_width);
+        win_height = MIN(win_height, con->window->max_height);
     }
 
     bool changed = false;
diff --git a/src/manage.c b/src/manage.c
index f97ecc62..4031ade6 100644
--- a/src/manage.c
+++ b/src/manage.c
@@ -520,6 +520,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
         nc->window->min_height = wm_size_hints.min_height;
     }
 
+    if (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) {
+        DLOG("Window specifies maximum size %d x %d\n", wm_size_hints.max_width, wm_size_hints.max_height);
+        nc->window->max_width = wm_size_hints.max_width;
+        nc->window->max_height = wm_size_hints.max_height;
+    }
+
     /* Store the requested geometry. The width/height gets raised to at least
      * 75x50 when entering floating mode, which is the minimum size for a
      * window to be useful (smaller windows are usually overlays/toolbars/…
diff --git a/testcases/t/189-floating-constraints.t b/testcases/t/189-floating-constraints.t
index 6b082bfd..4ac95e15 100644
--- a/testcases/t/189-floating-constraints.t
+++ b/testcases/t/189-floating-constraints.t
@@ -21,6 +21,7 @@
 #
 
 use i3test i3_autostart => 0;
+use X11::XCB qw/PROP_MODE_REPLACE/;
 
 ################################################################################
 # 1: check floating_minimum_size (with non-default limits)
@@ -218,4 +219,82 @@ is($rect->{height}, 70, 'height did not exceed minimum height');
 
 exit_gracefully($pid);
 
+################################################################################
+# 8: check minimum_size and maximum_size set by WM_NORMAL_HINTS
+################################################################################
+
+$config = < sub {
+            my ($window) = @_;
+
+            my $atomname = $x->atom(name => 'WM_NORMAL_HINTS');
+            my $atomtype = $x->atom(name => 'WM_SIZE_HINTS');
+            $x->change_property(
+                PROP_MODE_REPLACE,
+                $window->id,
+                $atomname->id,
+                $atomtype->id,
+                32,
+                13,
+                pack('C5N8', $flags, $pad, $pad, $pad, $pad, 0, 0, 0, $min_width, $min_height, $max_width, $max_height),
+            );
+        },
+    );
+
+    return $window;
+}
+
+my sub check_minsize {
+    is($window->rect->{width}, $min_width, 'width = min_width');
+    is($window->rect->{height}, $min_height, 'height = min_height');
+}
+
+my sub check_maxsize {
+    is($window->rect->{width}, $max_width, 'width = max_width');
+    is($window->rect->{height}, $max_height, 'height = max_height');
+}
+
+$pid = launch_with_config($config);
+
+$window = open_with_max_size;
+cmd 'floating enable';
+cmd 'border none';
+sync_with_i3;
+
+cmd "resize set $min_width px $min_height px";
+check_minsize;
+
+# Try to resize below minimum width
+cmd 'resize set ' . ($min_width - 10) . ' px ' . ($min_height - 50) . ' px';
+check_minsize;
+
+cmd "resize set $max_width px $max_height px";
+check_maxsize;
+
+# Try to resize above maximum width
+cmd 'resize set ' . ($max_width + 150) . ' px ' . ($max_height + 500) . ' px';
+check_maxsize;
+
+exit_gracefully($pid);
+
 done_testing;

From bf9da466db23b274d7eb1873c46e52fddf95368b Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 20 Sep 2018 19:37:50 +0300
Subject: [PATCH 168/218] 530-bug-229.t: Get rid of smartmatch

---
 testcases/t/530-bug-2229.t | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/testcases/t/530-bug-2229.t b/testcases/t/530-bug-2229.t
index 91a9ef86..a177ee3c 100644
--- a/testcases/t/530-bug-2229.t
+++ b/testcases/t/530-bug-2229.t
@@ -24,8 +24,6 @@ fake-outputs 400x400+0+0,400x400+400+0
 workspace_auto_back_and_forth no
 EOT
 
-my $i3 = i3(get_socket_path());
-
 # Set it up such that workspace 3 is on the left output and
 # workspace 4 is on the right output
 cmd 'focus output fake-0';
@@ -37,10 +35,6 @@ open_window;
 
 cmd 'move workspace to output left';
 
-# ensure that workspace 3 has now vanished
-my $get_ws = $i3->get_workspaces->recv;
-my @ws_names = map { $_->{name} } @$get_ws;
-# TODO get rid of smartmatch
-ok(!('3' ~~ @ws_names), 'workspace 3 has been closed');
+ok(!workspace_exists('3'), 'workspace 3 has been closed');
 
 done_testing;

From d080f58299ebed9300ca6f79d30c955d3e928421 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Sun, 23 Sep 2018 17:02:21 +0300
Subject: [PATCH 169/218] i3-nagbar & i3-config-wizard: check sncontext != NULL

From
https://github.com/freedesktop/startup-notification/blob/07237ff25d6171e1b548118442ddba4259a53ba5/libsn/sn-common.c#L87-L171
it appears that SnDisplay can't be NULL, so I skipped the check.

Fixes #3419
---
 i3-config-wizard/main.c | 12 ++++++++----
 i3-nagbar/main.c        | 12 ++++++++----
 2 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c
index 013d1053..0c8c705a 100644
--- a/i3-config-wizard/main.c
+++ b/i3-config-wizard/main.c
@@ -853,6 +853,7 @@ int main(int argc, char *argv[]) {
     /* Init startup notification. */
     SnDisplay *sndisplay = sn_xcb_display_new(conn, NULL, NULL);
     SnLauncheeContext *sncontext = sn_launchee_context_new_from_environment(sndisplay, screen);
+    sn_display_unref(sndisplay);
 
     root_screen = xcb_aux_get_screen(conn, screen);
     root = root_screen->root;
@@ -886,7 +887,9 @@ int main(int argc, char *argv[]) {
             0, /* back pixel: black */
             XCB_EVENT_MASK_EXPOSURE |
                 XCB_EVENT_MASK_BUTTON_PRESS});
-    sn_launchee_context_setup_window(sncontext, win);
+    if (sncontext) {
+        sn_launchee_context_setup_window(sncontext, win);
+    }
 
     /* Map the window (make it visible) */
     xcb_map_window(conn, win);
@@ -949,9 +952,10 @@ int main(int argc, char *argv[]) {
     }
 
     /* Startup complete. */
-    sn_launchee_context_complete(sncontext);
-    sn_launchee_context_unref(sncontext);
-    sn_display_unref(sndisplay);
+    if (sncontext) {
+        sn_launchee_context_complete(sncontext);
+        sn_launchee_context_unref(sncontext);
+    }
 
     xcb_flush(conn);
 
diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c
index 1d1fcc51..1d600c02 100644
--- a/i3-nagbar/main.c
+++ b/i3-nagbar/main.c
@@ -421,6 +421,7 @@ int main(int argc, char *argv[]) {
     /* Init startup notification. */
     SnDisplay *sndisplay = sn_xcb_display_new(conn, NULL, NULL);
     SnLauncheeContext *sncontext = sn_launchee_context_new_from_environment(sndisplay, screens);
+    sn_display_unref(sndisplay);
 
     root_screen = xcb_aux_get_screen(conn, screens);
     root = root_screen->root;
@@ -491,7 +492,9 @@ int main(int argc, char *argv[]) {
                 XCB_EVENT_MASK_BUTTON_PRESS |
                 XCB_EVENT_MASK_BUTTON_RELEASE,
             cursor});
-    sn_launchee_context_setup_window(sncontext, win);
+    if (sncontext) {
+        sn_launchee_context_setup_window(sncontext, win);
+    }
 
     /* Map the window (make it visible) */
     xcb_map_window(conn, win);
@@ -553,9 +556,10 @@ int main(int argc, char *argv[]) {
     draw_util_surface_init(conn, &bar, win, get_visualtype(root_screen), win_pos.width, win_pos.height);
 
     /* Startup complete. */
-    sn_launchee_context_complete(sncontext);
-    sn_launchee_context_unref(sncontext);
-    sn_display_unref(sndisplay);
+    if (sncontext) {
+        sn_launchee_context_complete(sncontext);
+        sn_launchee_context_unref(sncontext);
+    }
 
     /* Grab the keyboard to get all input */
     xcb_flush(conn);

From aa8215194cecd6e791f1b560799e9bd641b431f4 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Thu, 20 Sep 2018 13:02:54 +0300
Subject: [PATCH 170/218] Fix typo: terminaison -> termination

---
 include/libi3.h | 2 +-
 libi3/string.c  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/include/libi3.h b/include/libi3.h
index 91395328..9a276571 100644
--- a/include/libi3.h
+++ b/include/libi3.h
@@ -196,7 +196,7 @@ i3String *i3string_from_markup(const char *from_markup);
 
 /**
  * Build an i3String from an UTF-8 encoded string with fixed length.
- * To be used when no proper NUL-terminaison is available.
+ * To be used when no proper NULL-termination is available.
  * Returns the newly-allocated i3String.
  *
  */
diff --git a/libi3/string.c b/libi3/string.c
index f78a140f..a078b33e 100644
--- a/libi3/string.c
+++ b/libi3/string.c
@@ -48,7 +48,7 @@ i3String *i3string_from_markup(const char *from_markup) {
 
 /*
  * Build an i3String from an UTF-8 encoded string with fixed length.
- * To be used when no proper NUL-terminaison is available.
+ * To be used when no proper NULL-termination is available.
  * Returns the newly-allocated i3String.
  *
  */

From daf5ca111f59e3fda32fd938cf8750886be977a1 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Fri, 21 Sep 2018 01:50:51 +0300
Subject: [PATCH 171/218] Provide g_utf8_make_valid if not available

See #3415 for licensing discussion.

Fixes Airblader/i3#236
---
 Makefile.am               |  1 +
 include/libi3.h           |  5 +++
 libi3/g_utf8_make_valid.c | 93 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 99 insertions(+)
 create mode 100644 libi3/g_utf8_make_valid.c

diff --git a/Makefile.am b/Makefile.am
index 09bbb6d5..7ca32506 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -301,6 +301,7 @@ libi3_a_SOURCES = \
 	libi3/fake_configure_notify.c \
 	libi3/font.c \
 	libi3/format_placeholders.c \
+	libi3/g_utf8_make_valid.c \
 	libi3/get_colorpixel.c \
 	libi3/get_config_path.c \
 	libi3/get_exe_path.c \
diff --git a/include/libi3.h b/include/libi3.h
index 9a276571..d27437ba 100644
--- a/include/libi3.h
+++ b/include/libi3.h
@@ -320,6 +320,11 @@ int ipc_recv_message(int sockfd, uint32_t *message_type,
  */
 void fake_configure_notify(xcb_connection_t *conn, xcb_rectangle_t r, xcb_window_t window, int border_width);
 
+#define HAS_G_UTF8_MAKE_VALID GLIB_CHECK_VERSION(2, 52, 0)
+#if !HAS_G_UTF8_MAKE_VALID
+gchar *g_utf8_make_valid(const gchar *str, gssize len);
+#endif
+
 /**
  * Returns the colorpixel to use for the given hex color (think of HTML). Only
  * works for true-color (vast majority of cases) at the moment, avoiding a
diff --git a/libi3/g_utf8_make_valid.c b/libi3/g_utf8_make_valid.c
new file mode 100644
index 00000000..b15873b3
--- /dev/null
+++ b/libi3/g_utf8_make_valid.c
@@ -0,0 +1,93 @@
+/* g_utf8_make_valid.c - Coerce string into UTF-8
+ *
+ * Copyright (C) 1999 Tom Tromey
+ * Copyright (C) 2000 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+
+#include "libi3.h"
+
+#include 
+#include 
+
+/* Copied from:
+ * https://gitlab.gnome.org/GNOME/glib/blob/f928dfdf57bf92c883b53b16d7a9d49add504f52/glib/gutf8.c#L1752-1815 */
+/* clang-format off */
+#if !HAS_G_UTF8_MAKE_VALID
+/**
+ * g_utf8_make_valid:
+ * @str: string to coerce into UTF-8
+ * @len: the maximum length of @str to use, in bytes. If @len < 0,
+ *     then the string is nul-terminated.
+ *
+ * If the provided string is valid UTF-8, return a copy of it. If not,
+ * return a copy in which bytes that could not be interpreted as valid Unicode
+ * are replaced with the Unicode replacement character (U+FFFD).
+ *
+ * For example, this is an appropriate function to use if you have received
+ * a string that was incorrectly declared to be UTF-8, and you need a valid
+ * UTF-8 version of it that can be logged or displayed to the user, with the
+ * assumption that it is close enough to ASCII or UTF-8 to be mostly
+ * readable as-is.
+ *
+ * Returns: (transfer full): a valid UTF-8 string whose content resembles @str
+ *
+ * Since: 2.52
+ */
+gchar *
+g_utf8_make_valid (const gchar *str,
+                   gssize       len)
+{
+  GString *string;
+  const gchar *remainder, *invalid;
+  gsize remaining_bytes, valid_bytes;
+
+  g_return_val_if_fail (str != NULL, NULL);
+
+  if (len < 0)
+    len = strlen (str);
+
+  string = NULL;
+  remainder = str;
+  remaining_bytes = len;
+
+  while (remaining_bytes != 0)
+    {
+      if (g_utf8_validate (remainder, remaining_bytes, &invalid))
+	break;
+      valid_bytes = invalid - remainder;
+
+      if (string == NULL)
+	string = g_string_sized_new (remaining_bytes);
+
+      g_string_append_len (string, remainder, valid_bytes);
+      /* append U+FFFD REPLACEMENT CHARACTER */
+      g_string_append (string, "\357\277\275");
+
+      remaining_bytes -= valid_bytes + 1;
+      remainder = invalid + 1;
+    }
+
+  if (string == NULL)
+    return g_strndup (str, len);
+
+  g_string_append_len (string, remainder, remaining_bytes);
+  g_string_append_c (string, '\0');
+
+  g_assert (g_utf8_validate (string->str, -1, NULL));
+
+  return g_string_free (string, FALSE);
+}
+#endif

From 6877205ac159dcc0bb2824ffe9fc007eeeb1fd4e Mon Sep 17 00:00:00 2001
From: Dan Elkouby 
Date: Tue, 25 Sep 2018 14:45:05 +0300
Subject: [PATCH 172/218] Reject requests for WM_STATE_ICONIC

For compatiblity reasons, Wine will request iconic state and cannot
ensure that the WM has agreed on it; immediately revert to normal to
avoid being stuck in a paused state.
---
 include/atoms_rest.xmacro |  1 +
 src/handlers.c            | 12 ++++++++++++
 2 files changed, 13 insertions(+)

diff --git a/include/atoms_rest.xmacro b/include/atoms_rest.xmacro
index d461dc08..b65a81d8 100644
--- a/include/atoms_rest.xmacro
+++ b/include/atoms_rest.xmacro
@@ -17,3 +17,4 @@ xmacro(I3_FLOATING_WINDOW)
 xmacro(_NET_REQUEST_FRAME_EXTENTS)
 xmacro(_NET_FRAME_EXTENTS)
 xmacro(_MOTIF_WM_HINTS)
+xmacro(WM_CHANGE_STATE)
diff --git a/src/handlers.c b/src/handlers.c
index d2232965..2cd45b79 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -806,6 +806,18 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             XCB_ATOM_CARDINAL, 32, 4,
             &r);
         xcb_flush(conn);
+    } else if (event->type == A_WM_CHANGE_STATE) {
+        /* http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4 */
+        if (event->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) {
+            /* For compatiblity reasons, Wine will request iconic state and cannot ensure that the WM has agreed on it;
+             * immediately revert to normal to avoid being stuck in a paused state. */
+            DLOG("Client has requested iconic state, rejecting. (window = %d)\n", event->window);
+            long data[] = {XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE};
+            xcb_change_property(conn, XCB_PROP_MODE_REPLACE, event->window,
+                                A_WM_STATE, A_WM_STATE, 32, 2, data);
+        } else {
+            DLOG("Not handling WM_CHANGE_STATE request. (window = %d, state = %d)\n", event->window, event->data.data32[0]);
+        }
     } else if (event->type == A__NET_CURRENT_DESKTOP) {
         /* This request is used by pagers and bars to change the current
          * desktop likely as a result of some user action. We interpret this as

From 5e1d327e4304438b8e0112e4d47e56f9748ef3f5 Mon Sep 17 00:00:00 2001
From: Orestis Floros 
Date: Wed, 26 Sep 2018 19:56:55 +0300
Subject: [PATCH 173/218] con_num_windows: Count floating windows

Fixes #3423.
---
 src/con.c                  | 4 ++++
 testcases/t/242-no-focus.t | 9 ++++++++-
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/con.c b/src/con.c
index a620a571..d50c29be 100644
--- a/src/con.c
+++ b/src/con.c
@@ -936,6 +936,10 @@ int con_num_windows(Con *con) {
         num += con_num_windows(current);
     }
 
+    TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
+        num += con_num_windows(current);
+    }
+
     return num;
 }
 
diff --git a/testcases/t/242-no-focus.t b/testcases/t/242-no-focus.t
index 5b507a78..6fd27fe0 100644
--- a/testcases/t/242-no-focus.t
+++ b/testcases/t/242-no-focus.t
@@ -68,7 +68,7 @@ is($x->input_focus, $first->id, 'input focus has not changed');
 exit_gracefully($pid);
 
 #####################################################################
-## 3: no_focus doesn't affect the first window opened on a workspace
+# 3: no_focus doesn't affect the first window opened on a workspace
 #####################################################################
 
 $config = < 'focusme');
 sync_with_i3;
 is($x->input_focus, $first->id, 'input focus has changed');
 
+# Also check that it counts floating windows
+# See issue #3423.
+open_floating_window(wm_class => 'focusme');
+
+sync_with_i3;
+is($x->input_focus, $first->id, 'input focus didn\'t change to floating window');
+
 exit_gracefully($pid);
 
 #####################################################################

From 0ede8b9365ead4785172cdbb8a95b9795ea7867f Mon Sep 17 00:00:00 2001
From: Thomas Fischer 
Date: Fri, 28 Sep 2018 17:34:18 -0700
Subject: [PATCH 174/218] Correctly calculate max_aspect

---
 src/handlers.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/handlers.c b/src/handlers.c
index 2cd45b79..c76f1935 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -1072,7 +1072,7 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
 
     /* Convert numerator/denominator to a double */
     double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
-    double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den;
+    double max_aspect = (double)size_hints.max_aspect_num / size_hints.max_aspect_den;
 
     DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
     DLOG("width = %f, height = %f\n", width, height);

From d722d1b0e605be6b84f37053d05d43d40fe6d88c Mon Sep 17 00:00:00 2001
From: Vivien Didelot 
Date: Tue, 2 Oct 2018 18:13:06 -0400
Subject: [PATCH 175/218] i3-msg: check reply in quiet mode

i3-msg currently exits right after sending the IPC message if the quiet
flag is set. This means that if an error occurred when issuing a
command, e.g. "i3-msg -q foobar", it gets silently ignored.

What we really want is to just skip printing but still check the reply.

At the same time, explicitly print the reply when we need to, instead of
using an exit label.
---
 i3-msg/main.c | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/i3-msg/main.c b/i3-msg/main.c
index 96edb2c3..9b34b062 100644
--- a/i3-msg/main.c
+++ b/i3-msg/main.c
@@ -246,9 +246,6 @@ int main(int argc, char *argv[]) {
         err(EXIT_FAILURE, "IPC: write()");
     free(payload);
 
-    if (quiet)
-        return 0;
-
     uint32_t reply_length;
     uint32_t reply_type;
     uint8_t *reply;
@@ -275,8 +272,9 @@ int main(int argc, char *argv[]) {
                 errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
         }
 
-        /* NB: We still fall-through and print the reply, because even if one
-         * command failed, that doesn’t mean that all commands failed. */
+        if (!quiet) {
+            printf("%.*s\n", reply_length, reply);
+        }
     } else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) {
         yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
         yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
@@ -289,12 +287,12 @@ int main(int argc, char *argv[]) {
             case yajl_status_error:
                 errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
         }
-
-        goto exit;
+    } else {
+        if (!quiet) {
+            printf("%.*s\n", reply_length, reply);
+        }
     }
-    printf("%.*s\n", reply_length, reply);
 
-exit:
     free(reply);
 
     close(sockfd);

From cff4fadd7275dd20b9de383c2f83e1e06a27b7a4 Mon Sep 17 00:00:00 2001
From: Vivien Didelot 
Date: Fri, 5 Sep 2014 16:47:27 -0400
Subject: [PATCH 176/218] i3-msg: add support for SUBSCRIBE message type

If i3-msg is invoked with -t subscribe, it will wait for the first event
matching the given payload, before exiting.

For instance, get the number of the next focused workspace with:

    i3-msg -t subscribe '[ "workspace" ]' | jshon -e current -e num

Like inotifywait, the -m flag allows to wait indefinitely for events,
instead of exiting right after receiving the first one.

For example, continuously monitor the names of focused windows with:

    i3-msg -t subscribe -m '[ "window" ]' | jq .container.name
---
 i3-msg/main.c  | 35 ++++++++++++++++++++++++++++++++---
 man/i3-msg.man | 13 +++++++++++++
 2 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/i3-msg/main.c b/i3-msg/main.c
index 9b34b062..fe111416 100644
--- a/i3-msg/main.c
+++ b/i3-msg/main.c
@@ -166,16 +166,18 @@ int main(int argc, char *argv[]) {
     uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND;
     char *payload = NULL;
     bool quiet = false;
+    bool monitor = false;
 
     static struct option long_options[] = {
         {"socket", required_argument, 0, 's'},
         {"type", required_argument, 0, 't'},
         {"version", no_argument, 0, 'v'},
         {"quiet", no_argument, 0, 'q'},
+        {"monitor", no_argument, 0, 'm'},
         {"help", no_argument, 0, 'h'},
         {0, 0, 0, 0}};
 
-    char *options_string = "s:t:vhq";
+    char *options_string = "s:t:vhqm";
 
     while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
         if (o == 's') {
@@ -204,25 +206,34 @@ int main(int argc, char *argv[]) {
                 message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
             } else if (strcasecmp(optarg, "send_tick") == 0) {
                 message_type = I3_IPC_MESSAGE_TYPE_SEND_TICK;
+            } else if (strcasecmp(optarg, "subscribe") == 0) {
+                message_type = I3_IPC_MESSAGE_TYPE_SUBSCRIBE;
             } 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, send_tick\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, subscribe\n");
                 exit(EXIT_FAILURE);
             }
         } else if (o == 'q') {
             quiet = true;
+        } else if (o == 'm') {
+            monitor = true;
         } else if (o == 'v') {
             printf("i3-msg " I3_VERSION "\n");
             return 0;
         } else if (o == 'h') {
             printf("i3-msg " I3_VERSION "\n");
-            printf("i3-msg [-s ] [-t ] \n");
+            printf("i3-msg [-s ] [-t ] [-m] \n");
             return 0;
         } else if (o == '?') {
             exit(EXIT_FAILURE);
         }
     }
 
+    if (monitor && message_type != I3_IPC_MESSAGE_TYPE_SUBSCRIBE) {
+        fprintf(stderr, "The monitor option -m is used with -t SUBSCRIBE exclusively.\n");
+        exit(EXIT_FAILURE);
+    }
+
     /* 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 */
@@ -287,6 +298,24 @@ int main(int argc, char *argv[]) {
             case yajl_status_error:
                 errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
         }
+    } else if (reply_type == I3_IPC_REPLY_TYPE_SUBSCRIBE) {
+        do {
+            free(reply);
+            if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
+                if (ret == -1)
+                    err(EXIT_FAILURE, "IPC: read()");
+                exit(1);
+            }
+
+            if (!(reply_type & I3_IPC_EVENT_MASK)) {
+                errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected an event", reply_type);
+            }
+
+            if (!quiet) {
+                fprintf(stdout, "%.*s\n", reply_length, reply);
+                fflush(stdout);
+            }
+        } while (monitor);
     } else {
         if (!quiet) {
             printf("%.*s\n", reply_length, reply);
diff --git a/man/i3-msg.man b/man/i3-msg.man
index 04c71900..625131de 100644
--- a/man/i3-msg.man
+++ b/man/i3-msg.man
@@ -31,6 +31,11 @@ with an error.
 *-t* 'type'::
 Send ipc message, see below. This option defaults to "command".
 
+*-m*, *--monitor*::
+Instead of exiting right after receiving the first subscribed event,
+wait indefinitely for all of them. Can only be used with "-t subscribe".
+See the "subscribe" IPC message type below for details.
+
 *message*::
 Send ipc message, see below.
 
@@ -75,6 +80,11 @@ Gets the currently loaded i3 configuration.
 send_tick::
 Sends a tick to all IPC connections which subscribe to tick events.
 
+subscribe::
+The payload of the message describes the events to subscribe to.
+Upon reception, each event will be dumped as a JSON-encoded object.
+See the -m option for continuous monitoring.
+
 == DESCRIPTION
 
 i3-msg is a sample implementation for a client using the unix socket IPC
@@ -91,6 +101,9 @@ i3-msg border normal
 
 # Dump the layout tree
 i3-msg -t get_tree
+
+# Monitor window changes
+i3-msg -t subscribe -m '[ "window" ]'
 ------------------------------------------------
 
 == ENVIRONMENT

From acea46e16a9d840b95397127cfb18d2bca93aee9 Mon Sep 17 00:00:00 2001
From: Michael Stapelberg 
Date: Sun, 7 Oct 2018 20:09:35 +0200
Subject: [PATCH 177/218] configure.ac: add conditionals for building docs/mans

fixes #3378
---
 configure.ac | 27 +++++++++++++++++++++------
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/configure.ac b/configure.ac
index 1784fa83..5cc9d4a6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -110,12 +110,27 @@ AC_PROG_MAKE_SET
 AC_PROG_RANLIB
 AC_PROG_LN_S
 
-AC_PATH_PROG([PATH_ASCIIDOC], [asciidoc])
-AC_PATH_PROG([PATH_XMLTO], [xmlto])
-AC_PATH_PROG([PATH_POD2MAN], [pod2man])
-
-AM_CONDITIONAL([BUILD_MANS], [test x$PATH_ASCIIDOC != x && test x$PATH_XMLTO != x && test x$PATH_POD2MAN != x])
-AM_CONDITIONAL([BUILD_DOCS], [test x$PATH_ASCIIDOC != x])
+AC_ARG_ENABLE(docs,
+  AS_HELP_STRING(
+    [--disable-docs],
+    [disable building documentation]),
+  [ax_docs=$enableval],
+  [ax_docs=yes])
+AC_ARG_ENABLE(mans,
+  AS_HELP_STRING(
+    [--disable-mans],
+    [disable building manual pages]),
+  [ax_mans=$enableval],
+  [ax_mans=yes])
+AS_IF([test x$ax_docs = xyes || test x$ax_mans = xyes], [
+  AC_PATH_PROG([PATH_ASCIIDOC], [asciidoc])
+])
+AS_IF([test x$ax_mans = xyes], [
+  AC_PATH_PROG([PATH_XMLTO], [xmlto])
+  AC_PATH_PROG([PATH_POD2MAN], [pod2man])
+])
+AM_CONDITIONAL([BUILD_MANS], [test x$ax_mans = xyes && test x$PATH_ASCIIDOC != x && test x$PATH_XMLTO != x && test x$PATH_POD2MAN != x])
+AM_CONDITIONAL([BUILD_DOCS], [test x$ax_docs = xyes && test x$PATH_ASCIIDOC != x])
 
 AM_PROG_AR
 

From 18dbfe699a61b64239f7397246e015c9ebfb36ce Mon Sep 17 00:00:00 2001
From: Orestis 
Date: Sun, 7 Oct 2018 21:24:09 +0300
Subject: [PATCH 178/218] userguide: Mention know issues for assign (#3434)

Fixes #3222
Fixes #3293
Related to #2060
---
 docs/userguide          | 25 +++++++++++++++++--------
 src/config_directives.c | 10 ++++++++++
 2 files changed, 27 insertions(+), 8 deletions(-)

diff --git a/docs/userguide b/docs/userguide
index 9c601e88..da5d9873 100644
--- a/docs/userguide
+++ b/docs/userguide
@@ -755,14 +755,23 @@ set_from_resource $black i3wm.color0 #000000
 
 To automatically make a specific window show up on a specific workspace, you
 can use an *assignment*. You can match windows by using any criteria,
-see <>. It is recommended that you match on window classes
-(and instances, when appropriate) instead of window titles whenever possible
-because some applications first create their window, and then worry about
-setting the correct title. Firefox with Vimperator comes to mind. The window
-starts up being named Firefox, and only when Vimperator is loaded does the
-title change. As i3 will get the title as soon as the application maps the
-window (mapping means actually displaying it on the screen), you’d need to have
-to match on 'Firefox' in this case.
+see <>. The difference between +assign+ and
++for_window  move to workspace+ is that the former will only be
+executed when the application maps the window (mapping means actually displaying
+it on the screen) but the latter will be executed whenever a window changes its
+properties to something that matches the specified criteria.
+
+Thus, it is recommended that you match on window classes (and instances, when
+appropriate) instead of window titles whenever possible because some
+applications first create their window, and then worry about setting the correct
+title. Firefox with Vimperator comes to mind. The window starts up being named
+Firefox, and only when Vimperator is loaded does the title change. As i3 will
+get the title as soon as the application maps the window, you’d need to have to
+match on 'Firefox' in this case.
+Another known issue is with Spotify, which doesn't set the class hints when
+mapping the window, meaning you'll have to use a +for_window+ rule to assign
+Spotify to a specific workspace.
+Finally, using +assign [tiling]+ and +assign [floating]+ is not supported.
 
 You can also assign a window to show up on a specific output. You can use RandR
 names such as +VGA1+ or names relative to the output with the currently focused
diff --git a/src/config_directives.c b/src/config_directives.c
index dfbb52d8..5c85197f 100644
--- a/src/config_directives.c
+++ b/src/config_directives.c
@@ -406,6 +406,11 @@ CFGFUN(assign_output, const char *output) {
         return;
     }
 
+    if (current_match->window_mode != WM_ANY) {
+        ELOG("Assignments using window mode (floating/tiling) is not supported\n");
+        return;
+    }
+
     DLOG("New assignment, using above criteria, to output \"%s\".\n", output);
     Assignment *assignment = scalloc(1, sizeof(Assignment));
     match_copy(&(assignment->match), current_match);
@@ -420,6 +425,11 @@ CFGFUN(assign, const char *workspace, bool is_number) {
         return;
     }
 
+    if (current_match->window_mode != WM_ANY) {
+        ELOG("Assignments using window mode (floating/tiling) is not supported\n");
+        return;
+    }
+
     if (is_number && ws_name_to_number(workspace) == -1) {
         ELOG("Could not parse initial part of \"%s\" as a number.\n", workspace);
         return;

From 2be4975f18f6e49b53ae683a30b11e435a5515dd Mon Sep 17 00:00:00 2001
From: Orestis 
Date: Sun, 7 Oct 2018 21:26:37 +0300
Subject: [PATCH 179/218] resolve_tilde: strncpy + strlen is pointless (#3436)

strlen already assumes that the string is NULL-terminated.

Like in https://github.com/i3/i3status/pull/312 but for whatever reason
gcc didn't warn about this here.
---
 libi3/resolve_tilde.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/libi3/resolve_tilde.c b/libi3/resolve_tilde.c
index 51d642db..6dbf132f 100644
--- a/libi3/resolve_tilde.c
+++ b/libi3/resolve_tilde.c
@@ -35,9 +35,10 @@ char *resolve_tilde(const char *path) {
     } else {
         head = globbuf.gl_pathv[0];
         result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1, 1);
-        strncpy(result, head, strlen(head));
-        if (tail)
-            strncat(result, tail, strlen(tail));
+        strcpy(result, head);
+        if (tail) {
+            strcat(result, tail);
+        }
     }
     globfree(&globbuf);
 

From 824d6916400e83d6b4dd93940238ca75dee4ef82 Mon Sep 17 00:00:00 2001
From: Michael Stapelberg 
Date: Sun, 7 Oct 2018 20:41:42 +0200
Subject: [PATCH 180/218] add specific GitHub issue templates

I learnt about this from the GitHub blog:
https://blog.github.com/2018-05-02-issue-template-improvements/
---
 .github/ISSUE_TEMPLATE.md                 | 10 +++-
 .github/ISSUE_TEMPLATE/bug_report.md      | 71 +++++++++++++++++++++++
 .github/ISSUE_TEMPLATE/feature_request.md | 47 +++++++++++++++
 3 files changed, 126 insertions(+), 2 deletions(-)
 create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md
 create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md

diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 96f68d90..f2c55972 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -12,10 +12,16 @@ PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATI
 
## Current Behavior - + ## Expected Behavior - + ## Reproduction Instructions + +## I'm submitting a… + +
+[x] Bug
+[ ] Feature Request
+[ ] Documentation Request
+[ ] Other (Please describe in detail)
+
+ +## Current Behavior + + +## Expected Behavior + + +## Reproduction Instructions + + +## Environment + +Output of `i3 --moreversion 2>&-`: +
+i3 version: 
+
+ + +
+
+ + +
+Logfile URL:
+
+ + +
+- Linux Distribution & Version:
+- Are you using a compositor (e.g., xcompmgr or compton):
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..e1c169a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,47 @@ +--- +name: Feature request +about: Suggest an idea for this project +--- + + + +## I'm submitting a… + +
+[ ] Bug
+[x] Feature Request
+[ ] Documentation Request
+[ ] Other (Please describe in detail)
+
+ +## Current Behavior + + +## Desired Behavior + + +## Environment + +Output of `i3 --moreversion 2>&-`: +
+i3 version: 
+
+ + +
+- Linux Distribution & Version:
+- Are you using a compositor (e.g., xcompmgr or compton):
+
From dfe89cc4f1706a6fae0ae3816787d0fb22dadd7d Mon Sep 17 00:00:00 2001 From: Orestis Date: Sun, 7 Oct 2018 23:43:24 +0300 Subject: [PATCH 181/218] i3-nagbar: add option for button that runs commands without a terminal (#3258) Fixes #2199. --- etc/config | 2 +- etc/config.keycodes | 2 +- i3-nagbar/main.c | 14 +++++++++++--- man/i3-nagbar.man | 8 +++++++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/etc/config b/etc/config index 3be9831d..da51d570 100644 --- a/etc/config +++ b/etc/config @@ -147,7 +147,7 @@ bindsym Mod1+Shift+c reload # restart i3 inplace (preserves your layout/session, can be used to upgrade i3) bindsym Mod1+Shift+r restart # exit i3 (logs you out of your X session) -bindsym Mod1+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'" +bindsym Mod1+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -B 'Yes, exit i3' 'i3-msg exit'" # resize window (you can also use the mouse for that) mode "resize" { diff --git a/etc/config.keycodes b/etc/config.keycodes index 2d56876c..6fc19426 100644 --- a/etc/config.keycodes +++ b/etc/config.keycodes @@ -133,7 +133,7 @@ bindcode $mod+Shift+54 reload # restart i3 inplace (preserves your layout/session, can be used to upgrade i3) bindcode $mod+Shift+27 restart # exit i3 (logs you out of your X session) -bindcode $mod+Shift+26 exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'" +bindcode $mod+Shift+26 exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -B 'Yes, exit i3' 'i3-msg exit'" # resize window (you can also use the mouse for that) mode "resize" { diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 1d600c02..fd7acd6e 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -55,6 +55,7 @@ typedef struct { char *action; int16_t x; uint16_t width; + bool terminal; } button_t; static xcb_window_t win; @@ -187,7 +188,11 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve } char *terminal_cmd; - sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path); + if (button->terminal) { + sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path); + } else { + terminal_cmd = sstrdup(link_path); + } printf("argv0 = %s\n", argv0); printf("terminal_cmd = %s\n", terminal_cmd); @@ -361,12 +366,13 @@ int main(int argc, char *argv[]) { {"version", no_argument, 0, 'v'}, {"font", required_argument, 0, 'f'}, {"button", required_argument, 0, 'b'}, + {"button-no-terminal", required_argument, 0, 'B'}, {"help", no_argument, 0, 'h'}, {"message", required_argument, 0, 'm'}, {"type", required_argument, 0, 't'}, {0, 0, 0, 0}}; - char *options_string = "b:f:m:t:vh"; + char *options_string = "b:B:f:m:t:vh"; prompt = i3string_from_utf8("Please do not run this program."); @@ -388,12 +394,14 @@ int main(int argc, char *argv[]) { break; case 'h': printf("i3-nagbar " I3_VERSION "\n"); - printf("i3-nagbar [-m ] [-b