]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'next' into master
authorMichael Stapelberg <michael@stapelberg.de>
Sat, 10 Mar 2018 17:29:21 +0000 (18:29 +0100)
committerMichael Stapelberg <michael@stapelberg.de>
Sat, 10 Mar 2018 17:29:21 +0000 (18:29 +0100)
141 files changed:
.editorconfig [new file with mode: 0644]
.travis.yml
AnyEvent-I3/lib/AnyEvent/I3.pm
I3_VERSION
Makefile.am
README.md [new file with mode: 0644]
RELEASE-NOTES-4.15 [new file with mode: 0644]
configure.ac
contrib/dump-asy.pl
contrib/per-workspace-layout.pl
debian/changelog
debian/i3-wm.install
debian/rules
docs/debugging
docs/i3bar-protocol
docs/ipc
docs/testsuite
docs/userguide
etc/config
etc/config.keycodes
generate-command-parser.pl
i3-config-wizard/main.c
i3-dump-log/main.c
i3-input/i3-input.h
i3-input/main.c
i3-msg/main.c
i3-nagbar/i3-nagbar.h
i3-nagbar/main.c
i3-sensible-editor
i3-sensible-terminal
i3bar/include/child.h
i3bar/include/configuration.h
i3bar/include/outputs.h
i3bar/include/util.h
i3bar/include/xcb_atoms.def
i3bar/src/child.c
i3bar/src/config.c
i3bar/src/ipc.c
i3bar/src/main.c
i3bar/src/outputs.c
i3bar/src/xcb.c
include/all.h
include/commands.h
include/con.h
include/config_directives.h
include/configuration.h
include/data.h
include/i3.h
include/i3/ipc.h
include/ipc.h
include/resize.h
include/util.h
libi3/dpi.c
libi3/ipc_connect.c
libi3/ipc_recv_message.c
man/i3-sensible-editor.man
man/i3-sensible-terminal.man
parser-specs/commands.spec
parser-specs/config.spec
release.sh
src/bindings.c
src/click.c
src/commands.c
src/con.c
src/config.c
src/config_directives.c
src/config_parser.c
src/display_version.c
src/floating.c
src/handlers.c
src/ipc.c
src/load_layout.c
src/main.c
src/manage.c
src/move.c
src/output.c
src/randr.c
src/resize.c
src/restore_layout.c
src/scratchpad.c
src/tree.c
src/util.c
src/workspace.c
src/x.c
testcases/complete-run.pl.in
testcases/inject_randr1.5.c
testcases/lib/StartXServer.pm
testcases/lib/i3test.pm.in
testcases/lib/i3test/Test.pm
testcases/lib/i3test/XTEST.pm
testcases/t/100-fullscreen.t
testcases/t/113-urgent.t
testcases/t/115-ipc-workspaces.t
testcases/t/156-fullscreen-focus.t
testcases/t/166-assign.t
testcases/t/167-workspace_layout.t
testcases/t/183-config-variables.t
testcases/t/185-scratchpad.t
testcases/t/189-floating-constraints.t
testcases/t/195-net-active-window.t
testcases/t/199-ipc-mode-event.t
testcases/t/201-config-parser.t
testcases/t/205-ipc-windows.t
testcases/t/206-fullscreen-scratchpad.t
testcases/t/207-shmlog.t
testcases/t/219-ipc-window-focus.t
testcases/t/220-ipc-window-title.t
testcases/t/225-ipc-window-fullscreen.t
testcases/t/227-ipc-workspace-empty.t
testcases/t/231-ipc-floating-event.t
testcases/t/238-ipc-binding-event.t
testcases/t/240-focus-on-window-activation.t
testcases/t/252-floating-size.t
testcases/t/257-keypress-group1-fallback.t
testcases/t/258-keypress-release.t
testcases/t/263-config-reload-reverts-bind-mode.t
testcases/t/265-ipc-mark.t
testcases/t/268-ipc-config.t
testcases/t/275-ipc-window-close.t
testcases/t/276-ipc-window-move.t
testcases/t/277-ipc-window-urgent.t
testcases/t/286-root-window-mouse-binding.t
testcases/t/289-ipc-shutdown-event.t
testcases/t/290-keypress-numlock.t
testcases/t/291-swap.t
testcases/t/293-focus-follows-mouse.t [new file with mode: 0644]
testcases/t/293-sticky-output-crash.t [new file with mode: 0644]
testcases/t/294-focus-order.t [new file with mode: 0644]
testcases/t/294-update-ewmh-atoms.t [new file with mode: 0644]
testcases/t/504-move-workspace-to-output.t
testcases/t/510-focus-across-outputs.t
testcases/t/514-ipc-workspace-multi-monitor.t
testcases/t/517-regress-move-direction-ipc.t
testcases/t/525-i3bar-mouse-bindings.t
testcases/t/539-disable_focus_wrapping.t [new file with mode: 0644]
testcases/t/540-sigterm-cleanup.t [new file with mode: 0644]
testcases/t/541-resize-set-tiling.t [new file with mode: 0644]
travis/clang-analyze.sh [deleted file]
travis/deploy-github-pages.sh
travis/run-tests.sh
travis/travis-base.Dockerfile

diff --git a/.editorconfig b/.editorconfig
new file mode 100644 (file)
index 0000000..c25745f
--- /dev/null
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+
+[*.{c,h}]
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
index de9ff3fcd43874b14130c356ae7a0b5778935ee7..87c996fb53bc2c5c29684aa67d00e08ccc3655a1 100644 (file)
@@ -42,7 +42,6 @@ script:
   - ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_UBUNTU} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
   - ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_386} linux32 ./travis/debian-build.sh deb/debian-i386/DIST
   - ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_UBUNTU_386} linux32 ./travis/debian-build.sh deb/ubuntu-i386/DIST
-  - ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/clang-analyze.sh
   - ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/docs.sh
   - ./travis/skip-pkg.sh || travis/prep-bintray.sh
 
index 53ad821552ed1176c0ca95af8762d6b0560055c2..198c41c9a5eef1164a128150ec26496783134e69 100644 (file)
@@ -9,6 +9,7 @@ use AnyEvent::Socket;
 use AnyEvent;
 use Encode;
 use Scalar::Util qw(tainted);
+use Carp;
 
 =head1 NAME
 
@@ -98,11 +99,12 @@ use constant TYPE_GET_BAR_CONFIG => 6;
 use constant TYPE_GET_VERSION => 7;
 use constant TYPE_GET_BINDING_MODES => 8;
 use constant TYPE_GET_CONFIG => 9;
+use constant TYPE_SEND_TICK => 10;
 
 our %EXPORT_TAGS = ( 'all' => [
     qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
        TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION
-       TYPE_GET_BINDING_MODES TYPE_GET_CONFIG)
+       TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK)
 ] );
 
 our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
@@ -119,6 +121,7 @@ my %events = (
     barconfig_update => ($event_mask | 4),
     binding => ($event_mask | 5),
     shutdown => ($event_mask | 6),
+    tick => ($event_mask | 7),
     _error => 0xFFFFFFFF,
 );
 
@@ -187,7 +190,7 @@ sub new {
         # We use getpwuid() instead of $ENV{HOME} because the latter is tainted
         # and thus produces warnings when running tests with perl -T
         my $home = (getpwuid($<))[7];
-        die "Could not get home directory" unless $home and -d $home;
+        confess "Could not get home directory" unless $home and -d $home;
         $path =~ s/~/$home/g;
     }
 
@@ -331,9 +334,9 @@ scalar), if specified.
 sub message {
     my ($self, $type, $content) = @_;
 
-    die "No message type specified" unless defined($type);
+    confess "No message type specified" unless defined($type);
 
-    die "No connection to i3" unless defined($self->{ipchdl});
+    confess "No connection to i3" unless defined($self->{ipchdl});
 
     my $payload = "";
     if ($content) {
@@ -374,7 +377,7 @@ sub _ensure_connection {
 
     return if defined($self->{ipchdl});
 
-    $self->connect->recv or die "Unable to connect to i3 (socket path " . $self->{path} . ")";
+    $self->connect->recv or confess "Unable to connect to i3 (socket path " . $self->{path} . ")";
 }
 
 =head2 get_workspaces
@@ -518,6 +521,18 @@ sub get_config {
     $self->message(TYPE_GET_CONFIG);
 }
 
+=head2 send_tick
+
+Sends a tick event. Requires i3 >= 4.15
+
+=cut
+sub send_tick {
+    my ($self, $payload) = @_;
+
+    $self->_ensure_connection;
+
+    $self->message(TYPE_SEND_TICK, $payload);
+}
 
 =head2 command($content)
 
index 2c940203d58b1dea9c4da9dd5c64647995831e1f..0d5ece5828451b7849b41ede6dd28ef00a076958 100644 (file)
@@ -1 +1 @@
-4.14.1-non-git
+4.15-non-git
index b94d3e901afd73cebd174005ff0a111c19689e81..184b07343f8cc0b143c1eeaa8feba3d969b1b74c 100644 (file)
@@ -78,6 +78,11 @@ EXTRA_DIST = \
        AnyEvent-I3/t/manifest.t \
        AnyEvent-I3/t/pod-coverage.t \
        AnyEvent-I3/t/pod.t \
+       contrib/dump-asy.pl \
+       contrib/gtk-tree-watch.pl \
+       contrib/i3-wsbar \
+       contrib/per-workspace-layout.pl \
+       contrib/trivial-bar-script.sh \
        docs/asciidoc-git.conf \
        docs/bigpicture.png \
        docs/i3-pod2html \
@@ -113,7 +118,7 @@ EXTRA_DIST = \
        I3_VERSION \
        LICENSE \
        PACKAGE-MAINTAINER \
-       RELEASE-NOTES-4.14.1 \
+       RELEASE-NOTES-4.15 \
        generate-command-parser.pl \
        parser-specs/commands.spec \
        parser-specs/config.spec \
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..21b2e24
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+![Logo](docs/logo-30.png) i3: A tiling window manager
+=====================================================
+
+[![Build Status](https://travis-ci.org/i3/i3.svg?branch=next)](https://travis-ci.org/i3/i3)
+[![Issue Stats](http://www.issuestats.com/github/i3/i3/badge/issue?style=flat)](http://www.issuestats.com/github/i3/i3)
+[![Pull Request Stats](http://www.issuestats.com/github/i3/i3/badge/pr?style=flat)](http://www.issuestats.com/github/i3/i3)
+
+i3 is a tiling window manager for X11.
+
+For more information about i3, please see [the project's website](https://i3wm.org/) and [online documentation](https://i3wm.org/docs/).
+
+For information about contributing to i3, please see [CONTRIBUTING.md](.github/CONTRIBUTING.md).
diff --git a/RELEASE-NOTES-4.15 b/RELEASE-NOTES-4.15
new file mode 100644 (file)
index 0000000..0e1f81e
--- /dev/null
@@ -0,0 +1,113 @@
+
+ ┌────────────────────────────┐
+ │ Release notes for i3 v4.15 │
+ └────────────────────────────┘
+
+This is i3 v4.15. This version is considered stable. All users of i3 are
+strongly encouraged to upgrade.
+
+Aside from a number of fixes and documentation improvements, a number of
+commands have been extended to be more complete (e.g. “assign”, “resize”).
+
+ ┌────────────────────────────┐
+ │ Changes in i3 v4.15        │
+ └────────────────────────────┘
+
+  • build: AnyEvent::I3 moved to the i3 repository, so that its main consumer,
+    the i3 testsuite, can use new features immediately (such as the tick event,
+    in this case).
+  • docs/hacking-howto: promote “using git / sending patches” and “how to
+    build?” sections
+  • docs/i3bar-protocol: document that pango markup only works with pango fonts
+  • docs/ipc: document focus, nodes, floating_nodes
+  • docs/ipc: urgent: complete the list of container types
+  • docs/ipc: document how to detect i3’s byte order in memory-safe languages
+  • docs/ipc: document the GET_CONFIG request
+  • docs/userguide: fix formatting issue
+  • docs/userguide: explain why Mod4 is usually preferred as a modifier
+  • docs/userguide: use more idiomatic english (full-size, so-called)
+  • docs/userguide: switch from removed goto command to focus
+  • docs/userguide: mention <criteria> in focus
+  • docs/userguide: remove outdated 2013 last-modified date
+  • dump-asy: add prerequisite checks
+  • dump-asy: fix warnings about empty container names
+  • i3-dump-log: enable shmlog on demand
+  • i3-sensible-terminal: add “kitty”, “guake”, “tilda”
+  • i3-sensible-editor: add “gvim”
+  • i3bar: add --release flag for bindsym in bar blocks
+  • i3bar: add relative coordinates in JSON for click events
+  • ipc: rename COMMAND to RUN_COMMAND for consistency
+  • ipc: implement tick event for less flaky tests
+  • ipc: add error reply to “focus <window_mode>”
+  • ipc: send success response for nop
+  • default config: add $mod+r to toggle resize mode
+  • default config: use variables for workspace names to avoid repetition
+  • introduce “assign <criteria> [→] [workspace] [number] <workspace>”
+  • introduce “assign <criteria> [→] output left|right|up|down|primary|<output>”
+  • introduce a “focus_wrapping” option (subsumes “force_focus_wrapping”)
+  • introduce percentage point resizing for floating containers:
+    “resize set <width> [px | ppt] <height> [px | ppt]”
+  • introduce “resize set <width> ppt <height> ppt” for tiling windows
+  • rename “new_window” and “new_float” to “default_border” and
+    “default_floating_border” (the old names keep working)
+  • output names (e.g. “DP2”) can now be used as synonyms for monitor names
+    (e.g. “Dell UP2414Q”).
+  • the “swap” command now works with fullscreen windows
+  • raise floating windows to top when they are focused programmatically
+  • _NET_ACTIVE_WINDOW: invalidate focus to force SetInputFocus call
+  • make focus handling consistent when changing focus between outputs
+  • round non-integer Xft.dpi values
+  • tiling resize: remove minimum size
+
+ ┌────────────────────────────┐
+ │ Bugfixes                   │
+ └────────────────────────────┘
+
+  • i3bar: fix various memory leaks
+  • i3bar: fix crash when no status_command is provided
+  • fix uninitialized variables in init_dpi_end, tree_restore
+  • fix incorrectly set up signal handling
+  • fix “swap” debug log message
+  • fix crash when specifying invalid con_id for “swap”
+  • fix crash upon restart with window marks
+  • fix crash when config file does not end in a newline
+  • fix crash in append_layout
+  • fix crash in layout toggle command
+  • fix crash when switching monitors
+  • fix use-after-free in randr_init error path
+  • fix move accidentally moving windows across outputs
+  • fix crash when floating window is tiled while being resized
+  • fix out-of-bounds memory read
+  • fix memory leak when config conversion fails
+  • fix layout toggle split, which didn’t work until enabling tabbed/stack mode
+    once
+  • move XCB event handling into xcb_prepare_cb
+  • avert endless loop on unexpected EOF in ipc messages
+  • perform proper cleanup for signals with Term action
+  • don’t match containers in the scratchpad with criteria
+  • fix “workspace show” related issues
+  • fix config file conversion with long variable names
+  • fix config file conversion memory initialization
+  • prevent access of freed workspace in _workspace_show
+  • disable fullscreen when required when programmatically focusing windows
+  • free last_motion_notify
+  • don’t raise floating windows when focused because of focus_follows_mouse
+  • correctly set EWMH atoms when closing a workspace
+  • don’t raise floating windows when workspace is shown
+  • keep focus order when encapsulating workspaces
+  • validate layout files before loading
+
+ ┌────────────────────────────┐
+ │ Thanks!                    │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+  Alex Lu, Ben Creasy, Bennett Piater, Cast, chressie, clonejo, Dan Elkouby,
+  Daniel Mueller, DebianWall, Diki Ananta, Edward Betts, hwangcc23, Ingo Bürk,
+  Jan Alexander Steffens, Johannes Lange, Kent Fredric, livanh, Martin
+  T. H. Sandsmark, Michael Siegel, Orestis Floros, Pallav Agarwal, Pawel
+  S. Veselov, Pietro Cerutti, Theo Buehler, Thomas Praxl, Tyler Brazier,
+  Vladimir Panteleev, walker0643, Wes Roberts, xzfc
+
+-- Michael Stapelberg, 2018-03-10
index 01574dd98823b7757e81be100081d18e9c5e876b..8dce4f9f7846ffcefb0fabbfce894ce602ed216f 100644 (file)
@@ -2,7 +2,7 @@
 # Run autoreconf -fi to generate a configure script from this file.
 
 AC_PREREQ([2.69])
-AC_INIT([i3], [4.14.1], [https://github.com/i3/i3/issues])
+AC_INIT([i3], [4.15], [https://github.com/i3/i3/issues])
 # For AX_EXTEND_SRCDIR
 AX_ENABLE_BUILDDIR
 AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
index 3ebdb858e9ef275d830765b1a54a75639790b6c4..9bb2db3aa4641385d2f6c7ba05fe8bbe5d5d1924 100755 (executable)
@@ -13,7 +13,14 @@ use warnings;
 use Data::Dumper;
 use AnyEvent::I3;
 use File::Temp;
+use File::Basename;
 use v5.10;
+use IPC::Cmd qw[can_run];
+
+# prerequisites check so we can be specific about failures caused
+# by not having these tools in the path
+can_run('asy') or die 'Please install asymptote';
+can_run('gv') or die 'Please install gv';
 
 my $i3 = i3();
 
@@ -30,7 +37,7 @@ sub dump_node {
 
     my $o = ($n->{orientation} eq 'none' ? "u" : ($n->{orientation} eq 'horizontal' ? "h" : "v"));
     my $w = (defined($n->{window}) ? $n->{window} : "N");
-    my $na = $n->{name};
+    my $na = ($n->{name} or "[Empty]");
     $na =~ s/#/\\#/g;
     $na =~ s/\$/\\\$/g;
     $na =~ s/&/\\&/g;
@@ -38,7 +45,7 @@ sub dump_node {
     $na =~ s/~/\\textasciitilde{}/g;
     my $type = 'leaf';
     if (!defined($n->{window})) {
-        $type = $n->{orientation} . '-split';
+        $type = $n->{layout};
     }
     my $name = qq|``$na'' ($type)|;
 
@@ -75,4 +82,5 @@ say $tmp "draw(n" . $root->{id} . ", (0, 0));";
 close($tmp);
 my $rep = "$tmp";
 $rep =~ s/asy$/eps/;
-system("cd /tmp && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep");
+my $tmp_dir = dirname($rep);
+system("cd $tmp_dir && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep");
index 48590456af74561868a9225e8b3f368b4fcba7fc..4a2b4b9e2745e01edec43c87d6c3b2b4a21428be 100644 (file)
@@ -14,6 +14,7 @@ use warnings;
 use AnyEvent;
 use AnyEvent::I3;
 use v5.10;
+use utf8;
 
 my %layouts = (
     '4' => 'tabbed',
index dae55d7e8bdf2eb6b0407f03223da9b4ea956844..8dd823ebb37577973eca30a4d7f0ada9e6e14964 100644 (file)
@@ -1,3 +1,15 @@
+i3-wm (4.14.2-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Mon, 25 Sep 2017 08:55:22 +0200
+
+i3-wm (4.14.1-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Sun, 24 Sep 2017 19:21:15 +0200
+
 i3-wm (4.14-1) unstable; urgency=medium
 
   * New upstream release.
@@ -308,7 +320,7 @@ i3-wm (4.0.2-1) unstable; urgency=low
   * Bugfix: Use correct format string in load_layout (fixes crash in restart)
   * Bugfix: Fix border rendering (border lines were "cutting" through)
   * Bugfix: Raise floating windows immediately when dragging/resizing
-  * Bugfix: Make focus switching work accross outputs again
+  * Bugfix: Make focus switching work across outputs again
   * Bugfix: migration-script: handle resize top/bottom correctly
   * Bugfix: Fix focus issue when moving containers to workspaces
   * Bugfix: Warp cursor when changing outputs again
index bff5028cbe849ef5bb8c468fa25d4623c8b8573a..823d409795b0bf065467fb118503543c132ecdba 100644 (file)
@@ -1,2 +1,7 @@
 debian/tmp/etc
 debian/tmp/usr
+contrib/dump-asy.pl usr/share/doc/i3-wm/examples/
+contrib/gtk-tree-watch.pl usr/share/doc/i3-wm/examples/
+contrib/i3-wsbar usr/share/doc/i3-wm/examples/
+contrib/per-workspace-layout.pl usr/share/doc/i3-wm/examples/
+contrib/trivial-bar-script.sh usr/share/doc/i3-wm/examples/
index e4da3db851845af34ca767d7dafafac37364cd21..d5c306867cd6e8f23a243690f6a63c570aa18db3 100755 (executable)
@@ -17,5 +17,9 @@ override_dh_auto_configure:
        # The default is /usr/share/doc/i3
        dh_auto_configure -- --docdir=/usr/share/doc/i3-wm
 
+override_dh_builddeb:
+       # bintray does not support xz currently.
+       dh_builddeb -- -Zgzip
+
 %:
        dh $@ --parallel --builddirectory=build --with=autoreconf
index 07bc13a04a0f51bcc5b804212de99c9d5e475ec3..dd26f98d924b3783a79a09fb9b0e64ce15a25d94 100644 (file)
@@ -153,7 +153,7 @@ When sending bug reports, please attach the *whole* log file. Even if you think
 you found the section which clearly highlights the problem, additional
 information might be necessary to completely diagnose the problem.
 
-When debugging with us in IRC, be prepared to use a so called nopaste service
+When debugging with us in IRC, be prepared to use a so-called nopaste service
 such as https://pastebin.com because pasting large amounts of text in IRC
 sometimes leads to incomplete lines (servers have line length limitations) or
 flood kicks.
index b8c2b5ad774be7c700632fb359e2ef5e7c3e92d3..cf86531cc5f399774e983770719245729693c17a 100644 (file)
@@ -177,7 +177,8 @@ separator_block_width::
 markup::
        A string that indicates how the text of the block should be parsed. Set to
        +"pango"+ to use https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup].
-       Set to +"none"+ to not use any markup (default).
+       Set to +"none"+ to not use any markup (default). Pango markup only works
+       if you use a pango font.
 
 If you want to put in your own entries into a block, prefix the key with an
 underscore (_). i3bar will ignore all keys it doesn’t understand, and prefixing
@@ -236,6 +237,11 @@ x, y::
        X11 root window coordinates where the click occurred
 button::
        X11 button ID (for example 1 to 3 for left/middle/right mouse button)
+relative_x, relative_y::
+    Coordinates where the click occurred, with respect to the top left corner
+    of the block
+width, height::
+    Width and height (in px) of the block
 
 *Example*:
 ------------------------------------------
@@ -244,6 +250,10 @@ button::
  "instance": "eth0",
  "button": 1,
  "x": 1320,
- "y": 1400
+ "y": 1400,
+ "relative_x": 12,
+ "relative_y": 8,
+ "width": 50,
+ "height": 22
 }
 ------------------------------------------
index 997a3055fb1c22b5393e4409d8fc18e450f08372..8b767adebffa127bab432c2c9607b57ce491567b 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -64,6 +64,7 @@ to do that).
 | 7 | +GET_VERSION+ | <<_version_reply,VERSION>> | Gets the i3 version.
 | 8 | +GET_BINDING_MODES+ | <<_binding_modes_reply,BINDING_MODES>> | Gets the names of all currently configured binding modes.
 | 9 | +GET_CONFIG+ | <<_config_reply,CONFIG>> | Returns the last loaded i3 config.
+| 10 | +SEND_TICK+ | <<_tick_reply,TICK>> | Sends a tick event with the specified payload.
 |======================================================
 
 So, a typical message could look like this:
@@ -126,6 +127,8 @@ BINDING_MODES (8)::
         Reply to the GET_BINDING_MODES message.
 GET_CONFIG (9)::
        Reply to the GET_CONFIG message.
+TICK (10)::
+       Reply to the SEND_TICK message.
 
 [[_command_reply]]
 === COMMAND reply
@@ -637,6 +640,19 @@ which is a string containing the config file as loaded by i3 most recently.
 { "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" }
 -------------------
 
+[[_tick_reply]]
+=== TICK reply
+
+The reply is a map containing the "success" member. After the reply was
+received, the tick event has been written to all IPC connections which subscribe
+to tick events. UNIX sockets are usually buffered, but you can be certain that
+once you receive the tick event you just triggered, you must have received all
+events generated prior to the +SEND_TICK+ message (happened-before relation).
+
+*Example:*
+-------------------
+{ "success": true }
+-------------------
 
 == Events
 
@@ -694,6 +710,10 @@ binding (5)::
        mouse
 shutdown (6)::
        Sent when the ipc shuts down because of a restart or exit by user command
+tick (7)::
+       Sent when the ipc client subscribes to the tick event (with +"first":
+       true+) or when any ipc client sends a SEND_TICK message (with +"first":
+       false+).
 
 *Example:*
 --------------------------------------------------------------------
@@ -866,6 +886,27 @@ because of a user action such as a +restart+ or +exit+ command. The +change
 }
 ---------------------------
 
+=== tick event
+
+This event is triggered by a subscription to tick events or by a +SEND_TICK+
+message.
+
+*Example (upon subscription):*
+--------------------------------------------------------------------------------
+{
+ "first": true,
+ "payload": ""
+}
+--------------------------------------------------------------------------------
+
+*Example (upon +SEND_TICK+ with a payload of +arbitrary string+):*
+--------------------------------------------------------------------------------
+{
+ "first": false,
+ "payload": "arbitrary string"
+}
+--------------------------------------------------------------------------------
+
 == See also (existing libraries)
 
 [[libraries]]
@@ -881,6 +922,7 @@ C++::
        * https://github.com/drmgc/i3ipcpp
 Go::
        * https://github.com/mdirkse/i3ipc-go
+       * https://github.com/i3/go-i3
 JavaScript::
        * https://github.com/acrisci/i3ipc-gjs
 Lua::
@@ -958,3 +1000,6 @@ detect the byte order i3 is using:
      payload. Then, receive the pending +COMMAND+ message reply in big endian.
 
 5. From here on out, send/receive all messages using the detected byte order.
+
+Find an example implementation of this technique in
+https://github.com/i3/go-i3/blob/master/byteorder.go
index bf85cb1fbe34411cffa0c19b71d79dd4b6ea6193..b535e7c141ca994b9f453771097868e9150c8104 100644 (file)
@@ -113,10 +113,8 @@ containing the appropriate i3 logfile for each testcase. The latest folder can
 always be found under the symlink +latest/+. Unless told differently, it will
 run the tests on a separate X server instance (using Xephyr).
 
-Xephyr will open a window where you can inspect the running test. You can run
-the tests without an X session with Xvfb, such as with +xvfb-run
-./complete-run+. This will also speed up the tests significantly especially on
-machines without a powerful video card.
+Xephyr will open a window where you can inspect the running test. By default,
+tests are run under Xvfb.
 
 .Example invocation of +complete-run.pl+
 ---------------------------------------
index ebd0a881cf03a153732475bb26c85439aaf743a4..ba314af111a62493477dad54d4f030d743fdd0b4 100644 (file)
@@ -1,7 +1,6 @@
 i3 User’s Guide
 ===============
 Michael Stapelberg <michael@i3wm.org>
-March 2013
 
 This document contains all the information you need to configure and use the i3
 window manager. If it does not, please check https://www.reddit.com/r/i3wm/
@@ -11,7 +10,7 @@ mailing list.
 == Default keybindings
 
 For the "too long; didn’t read" people, here is an overview of the default
-keybindings (click to see the full size image):
+keybindings (click to see the full-size image):
 
 *Keys to use with $mod (Alt):*
 
@@ -35,7 +34,8 @@ above, just decline i3-config-wizard’s offer and base your config on
 
 Throughout this guide, the keyword +$mod+ will be used to refer to the
 configured modifier. This is the Alt key (+Mod1+) by default, with the Windows
-key (+Mod4+) being a popular alternative.
+key (+Mod4+) being a popular alternative that largely prevents conflicts with
+application-defined shortcuts.
 
 === Opening terminals and moving around
 
@@ -196,7 +196,7 @@ out to be complicated to use (snapping), understand and implement.
 
 === The tree consists of Containers
 
-The building blocks of our tree are so called +Containers+. A +Container+ can
+The building blocks of our tree are so-called +Containers+. A +Container+ can
 host a window (meaning an X11 window, one that you can actually see and use,
 like a browser). Alternatively, it could contain one or more +Containers+. A
 simple example is the workspace: When you start i3 with a single monitor, a
@@ -509,7 +509,7 @@ mode "$mode_launcher" {
 === The floating modifier
 
 To move floating windows with your mouse, you can either grab their titlebar
-or configure the so called floating modifier which you can then press and
+or configure the so-called floating modifier which you can then press and
 click anywhere in the window itself to move it. The most common setup is to
 use the same key you use for managing windows (Mod1 for example). Then
 you can press Mod1, click into a window using your left mouse button, and drag
@@ -585,23 +585,26 @@ workspace_layout default|stacking|tabbed
 workspace_layout tabbed
 ---------------------
 
-=== Border style for new windows
+=== Default border style for new windows
 
 This option determines which border style new windows will have. The default is
-+normal+. Note that new_float applies only to windows which are starting out as
++normal+. Note that default_floating_border applies only to windows which are starting out as
 floating windows, e.g., dialog windows, but not windows that are floated later on.
 
 *Syntax*:
 ---------------------------------------------
-new_window normal|none|pixel
-new_window normal|pixel <px>
-new_float normal|none|pixel
-new_float normal|pixel <px>
+default_border normal|none|pixel
+default_border normal|pixel <px>
+default_floating_border normal|none|pixel
+default_floating_border normal|pixel <px>
 ---------------------------------------------
 
+Please note that +new_window+ and +new_float+ have been deprecated in favor of the above options
+and will be removed in a future release. We strongly recommend using the new options instead.
+
 *Example*:
 ---------------------
-new_window pixel
+default_border pixel
 ---------------------
 
 The "normal" and "pixel" border styles support an optional border width in
@@ -609,11 +612,11 @@ pixels:
 
 *Example*:
 ---------------------
-# The same as new_window none
-new_window pixel 0
+# The same as default_border none
+default_border pixel 0
 
 # A 3 px border
-new_window pixel 3
+default_border pixel 3
 ---------------------
 
 
@@ -760,13 +763,18 @@ 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.
 
+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
+workspace such as +left+ and +down+.
+
 Assignments are processed by i3 in the order in which they appear in the config
 file. The first one which matches the window wins and later assignments are not
 considered.
 
 *Syntax*:
 ------------------------------------------------------------
-assign <criteria> [→] [workspace] <workspace>
+assign <criteria> [→] [workspace] [number] <workspace>
+assign <criteria> [→] output left|right|up|down|primary|<output>
 ------------------------------------------------------------
 
 *Examples*:
@@ -783,11 +791,28 @@ assign [class="^URxvt$"] → 2
 # Assignment to a named workspace
 assign [class="^URxvt$"] → work
 
+# Assign to the workspace with number 2, regardless of name
+assign [class="^URxvt$"] → number 2
+
+# You can also specify a number + name. If the workspace with number 2 exists, assign will skip the text part.
+assign [class="^URxvt$"] → number "2: work"
+
 # Start urxvt -name irssi
 assign [class="^URxvt$" instance="^irssi$"] → 3
+
+# Assign urxvt to the output right of the current one
+assign [class="^URxvt$"] → output right
+
+# Assign urxvt to the primary output
+assign [class="^URxvt$"] → output primary
 ----------------------
 
-Note that the arrow is not required, it just looks good :-). If you decide to
+Note that you might not have a primary output configured yet. To do so, run:
+-------------------------
+xrandr --output <output> --primary
+-------------------------
+
+Also, the arrow is not required, it just looks good :-). If you decide to
 use it, it has to be a UTF-8 encoded arrow, not `->` or something like that.
 
 To get the class and instance, you can use +xprop+. After clicking on the
@@ -1033,26 +1058,39 @@ popup_during_fullscreen smart
 
 === Focus wrapping
 
-When being in a tabbed or stacked container, the first container will be
-focused when you use +focus down+ on the last container -- the focus wraps. If
-however there is another stacked/tabbed container in that direction, focus will
-be set on that container. This is the default behavior so you can navigate to
-all your windows without having to use +focus parent+.
+By default, when in a container with several windows or child containers, the
+opposite window will be focused when trying to move the focus over the edge of
+a container (and there are no other containers in that direction) -- the focus
+wraps.
+
+If desired, you can disable this behavior by setting the +focus_wrapping+
+configuration directive to the value +no+.
+
+When enabled, focus wrapping does not occur by default if there is another
+window or container in the specified direction, and focus will instead be set
+on that window or container. This is the default behavior so you can navigate
+to all your windows without having to use +focus parent+.
 
 If you want the focus to *always* wrap and you are aware of using +focus
-parent+ to switch to different containers, you can use the
-+force_focus_wrapping+ configuration directive. After enabling it, the focus
-will always wrap.
+parent+ to switch to different containers, you can instead set +focus_wrapping+
+to the value +force+.
 
 *Syntax*:
 ---------------------------
-force_focus_wrapping yes|no
----------------------------
+focus_wrapping yes|no|force
 
-*Example*:
-------------------------
+# Legacy syntax, equivalent to "focus_wrapping force"
 force_focus_wrapping yes
-------------------------
+---------------------------
+
+*Examples*:
+-----------------
+# Disable focus wrapping
+focus_wrapping no
+
+# Force focus wrapping
+focus_wrapping force
+-----------------
 
 === Forcing Xinerama
 
@@ -1341,7 +1379,7 @@ and will be removed in a future release. We strongly recommend using the more ge
 
 *Syntax*:
 ----------------------------
-bindsym button<n> <command>
+bindsym [--release] button<n> <command>
 ----------------------------
 
 *Example*:
@@ -1349,6 +1387,8 @@ bindsym button<n> <command>
 bar {
     # disable clicking on workspace buttons
     bindsym button1 nop
+    # Take a screenshot by right clicking on the bar
+    bindsym --release button3 exec --no-startup-id import /tmp/latest-screenshot.png
     # execute custom script when scrolling downwards
     bindsym button5 exec ~/.i3/scripts/custom_wheel_down
 }
@@ -1913,6 +1953,9 @@ bindsym $mod+t floating toggle
 To change focus, you can use the +focus+ command. The following options are
 available:
 
+<criteria>::
+    Sets focus to the container that matches the specified criteria.
+    See <<command_criteria>>.
 left|right|up|down::
         Sets focus to the nearest container in the given direction.
 parent::
@@ -1932,6 +1975,7 @@ output::
 
 *Syntax*:
 ----------------------------------------------
+<criteria> focus
 focus left|right|down|up
 focus parent|child|floating|tiling|mode_toggle
 focus output left|right|up|down|primary|<output>
@@ -1939,6 +1983,9 @@ focus output left|right|up|down|primary|<output>
 
 *Examples*:
 -------------------------------------------------
+# Focus firefox
+bindsym $mod+F1 [class="Firefox"] focus
+
 # Focus container on the left, bottom, top, right
 bindsym $mod+j focus left
 bindsym $mod+k focus down
@@ -2232,7 +2279,6 @@ bindsym $mod+x move container to output VGA1
 bindsym $mod+x move container to output primary
 --------------------------------------------------------
 
--------------------------------
 Note that you might not have a primary output configured yet. To do so, run:
 -------------------------
 xrandr --output <output> --primary
@@ -2267,7 +2313,7 @@ If you want to resize containers/windows using your keyboard, you can use the
 *Syntax*:
 -------------------------------------------------------
 resize grow|shrink <direction> [<px> px [or <ppt> ppt]]
-resize set <width> [px] <height> [px]
+resize set <width> [px | ppt] <height> [px | ppt]
 -------------------------------------------------------
 
 Direction can either be one of +up+, +down+, +left+ or +right+. Or you can be
@@ -2276,8 +2322,11 @@ space from all the other containers. The optional pixel argument specifies by
 how many pixels a *floating container* should be grown or shrunk (the default
 is 10 pixels). The ppt argument means percentage points and specifies by how
 many percentage points a *tiling container* should be grown or shrunk (the
-default is 10 percentage points). Note that +resize set+ will only work for
-floating containers.
+default is 10 percentage points).
+
+Notes about +resize set+: a value of 0 for <width> or <height> means "do
+not resize in this direction", and resizing a tiling container by +px+ is not
+implemented.
 
 It is recommended to define bindings for resizing in a dedicated binding mode.
 See <<binding_modes>> and the example in the i3
@@ -2363,10 +2412,10 @@ TODO: make i3-input replace %s
 *Examples*:
 ---------------------------------------
 # Read 1 character and mark the current window with this character
-bindsym $mod+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
+bindsym $mod+m exec i3-input -F 'mark %s' -l 1 -P 'Mark: '
 
 # Read 1 character and go to the window with the character
-bindsym $mod+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
+bindsym $mod+g exec i3-input -F '[con_mark="%s"] focus' -l 1 -P 'Goto: '
 ---------------------------------------
 
 Alternatively, if you do not want to mess with +i3-input+, you could create
index 483694c1c78d6d5c26661a0990f7b492cfb39e92..3be9831dd9004e00e3469268dc2cb1e5d2f6678d 100644 (file)
@@ -22,7 +22,7 @@ font pango:monospace 8
 # The font above is very space-efficient, that is, it looks good, sharp and
 # clear in small sizes. However, its unicode glyph coverage is limited, the old
 # X core fonts rendering does not support right-to-left and this being a bitmap
-# font, it doesnt scale on retina/hidpi displays.
+# font, it doesn't scale on retina/hidpi displays.
 
 # use these keys for focus, movement, and resize directions when reaching for
 # the arrows is not convenient
@@ -104,29 +104,43 @@ bindsym Mod1+Shift+minus move scratchpad
 # If there are multiple scratchpad windows, this command cycles through them.
 bindsym Mod1+minus scratchpad show
 
+# Define names for default workspaces for which we configure key bindings later on.
+# We use variables to avoid repeating the names in multiple places.
+set $ws1 "1"
+set $ws2 "2"
+set $ws3 "3"
+set $ws4 "4"
+set $ws5 "5"
+set $ws6 "6"
+set $ws7 "7"
+set $ws8 "8"
+set $ws9 "9"
+set $ws10 "10"
+
+
 # switch to workspace
-bindsym Mod1+1 workspace 1
-bindsym Mod1+2 workspace 2
-bindsym Mod1+3 workspace 3
-bindsym Mod1+4 workspace 4
-bindsym Mod1+5 workspace 5
-bindsym Mod1+6 workspace 6
-bindsym Mod1+7 workspace 7
-bindsym Mod1+8 workspace 8
-bindsym Mod1+9 workspace 9
-bindsym Mod1+0 workspace 10
+bindsym Mod1+1 workspace $ws1
+bindsym Mod1+2 workspace $ws2
+bindsym Mod1+3 workspace $ws3
+bindsym Mod1+4 workspace $ws4
+bindsym Mod1+5 workspace $ws5
+bindsym Mod1+6 workspace $ws6
+bindsym Mod1+7 workspace $ws7
+bindsym Mod1+8 workspace $ws8
+bindsym Mod1+9 workspace $ws9
+bindsym Mod1+0 workspace $ws10
 
 # move focused container to workspace
-bindsym Mod1+Shift+1 move container to workspace 1
-bindsym Mod1+Shift+2 move container to workspace 2
-bindsym Mod1+Shift+3 move container to workspace 3
-bindsym Mod1+Shift+4 move container to workspace 4
-bindsym Mod1+Shift+5 move container to workspace 5
-bindsym Mod1+Shift+6 move container to workspace 6
-bindsym Mod1+Shift+7 move container to workspace 7
-bindsym Mod1+Shift+8 move container to workspace 8
-bindsym Mod1+Shift+9 move container to workspace 9
-bindsym Mod1+Shift+0 move container to workspace 10
+bindsym Mod1+Shift+1 move container to workspace $ws1
+bindsym Mod1+Shift+2 move container to workspace $ws2
+bindsym Mod1+Shift+3 move container to workspace $ws3
+bindsym Mod1+Shift+4 move container to workspace $ws4
+bindsym Mod1+Shift+5 move container to workspace $ws5
+bindsym Mod1+Shift+6 move container to workspace $ws6
+bindsym Mod1+Shift+7 move container to workspace $ws7
+bindsym Mod1+Shift+8 move container to workspace $ws8
+bindsym Mod1+Shift+9 move container to workspace $ws9
+bindsym Mod1+Shift+0 move container to workspace $ws10
 
 # reload the configuration file
 bindsym Mod1+Shift+c reload
@@ -154,9 +168,10 @@ mode "resize" {
         bindsym Up          resize shrink height 10 px or 10 ppt
         bindsym Right       resize grow width 10 px or 10 ppt
 
-        # back to normal: Enter or Escape
+        # back to normal: Enter or Escape or Mod1+r
         bindsym Return mode "default"
         bindsym Escape mode "default"
+        bindsym Mod1+r mode "default"
 }
 
 bindsym Mod1+r mode "resize"
index 6d10fad293fdda9a3e83709af64a03873200b460..2d56876c249b4d39d72fd19743b9d9b105731fe8 100644 (file)
@@ -91,29 +91,42 @@ bindcode $mod+38 focus parent
 # focus the child container
 #bindsym $mod+d focus child
 
+# Define names for default workspaces for which we configure key bindings later on.
+# We use variables to avoid repeating the names in multiple places.
+set $ws1 "1"
+set $ws2 "2"
+set $ws3 "3"
+set $ws4 "4"
+set $ws5 "5"
+set $ws6 "6"
+set $ws7 "7"
+set $ws8 "8"
+set $ws9 "9"
+set $ws10 "10"
+
 # switch to workspace
-bindcode $mod+10 workspace 1
-bindcode $mod+11 workspace 2
-bindcode $mod+12 workspace 3
-bindcode $mod+13 workspace 4
-bindcode $mod+14 workspace 5
-bindcode $mod+15 workspace 6
-bindcode $mod+16 workspace 7
-bindcode $mod+17 workspace 8
-bindcode $mod+18 workspace 9
-bindcode $mod+19 workspace 10
+bindcode $mod+10 workspace $ws1
+bindcode $mod+11 workspace $ws2
+bindcode $mod+12 workspace $ws3
+bindcode $mod+13 workspace $ws4
+bindcode $mod+14 workspace $ws5
+bindcode $mod+15 workspace $ws6
+bindcode $mod+16 workspace $ws7
+bindcode $mod+17 workspace $ws8
+bindcode $mod+18 workspace $ws9
+bindcode $mod+19 workspace $ws10
 
 # move focused container to workspace
-bindcode $mod+Shift+10 move container to workspace 1
-bindcode $mod+Shift+11 move container to workspace 2
-bindcode $mod+Shift+12 move container to workspace 3
-bindcode $mod+Shift+13 move container to workspace 4
-bindcode $mod+Shift+14 move container to workspace 5
-bindcode $mod+Shift+15 move container to workspace 6
-bindcode $mod+Shift+16 move container to workspace 7
-bindcode $mod+Shift+17 move container to workspace 8
-bindcode $mod+Shift+18 move container to workspace 9
-bindcode $mod+Shift+19 move container to workspace 10
+bindcode $mod+Shift+10 move container to workspace $ws1
+bindcode $mod+Shift+11 move container to workspace $ws2
+bindcode $mod+Shift+12 move container to workspace $ws3
+bindcode $mod+Shift+13 move container to workspace $ws4
+bindcode $mod+Shift+14 move container to workspace $ws5
+bindcode $mod+Shift+15 move container to workspace $ws6
+bindcode $mod+Shift+16 move container to workspace $ws7
+bindcode $mod+Shift+17 move container to workspace $ws8
+bindcode $mod+Shift+18 move container to workspace $ws9
+bindcode $mod+Shift+19 move container to workspace $ws10
 
 # reload the configuration file
 bindcode $mod+Shift+54 reload
@@ -141,9 +154,10 @@ mode "resize" {
         bindcode 111 resize shrink height 10 px or 10 ppt
         bindcode 114 resize grow width 10 px or 10 ppt
 
-        # back to normal: Enter or Escape
+        # back to normal: Enter or Escape or $mod+r
         bindcode 36 mode "default"
         bindcode 9 mode "default"
+        bindcode $mod+27 mode "default"
 }
 
 bindcode $mod+27 mode "resize"
index a7687c7bc480ab91d9e63c0cc13c2c94bec434ae..4c45b6ed614f3bb3252566e401d971a9e74f1649 100755 (executable)
@@ -116,17 +116,16 @@ my @keys = sort { (length($b) <=> length($a)) or ($a cmp $b) } keys %states;
 
 open(my $enumfh, '>', "GENERATED_${prefix}_enums.h");
 
-# XXX: we might want to have a way to do this without a trailing comma, but gcc
-# seems to eat it.
 my %statenum;
 say $enumfh 'typedef enum {';
 my $cnt = 0;
 for my $state (@keys, '__CALL') {
-    say $enumfh "    $state = $cnt,";
+    say $enumfh ',' if $cnt > 0;
+    print $enumfh "    $state = $cnt";
     $statenum{$state} = $cnt;
     $cnt++;
 }
-say $enumfh '} cmdp_state;';
+say $enumfh "\n} cmdp_state;";
 close($enumfh);
 
 # Third step: Generate the call function.
@@ -225,7 +224,7 @@ for my $state (@keys) {
             $next_state = '__CALL';
         }
         my $identifier = $token->{identifier};
-        say $tokfh qq|    { "$token_name", "$identifier", $next_state, { $call_identifier } }, |;
+        say $tokfh qq|    { "$token_name", "$identifier", $next_state, { $call_identifier } },|;
     }
     say $tokfh '};';
 }
index dd58fd124547df7dcee0ce40785784eb1e43be4d..b368921f2a9b896c5b23b343da99f42fd48026f2 100644 (file)
 #error "SYSCONFDIR not defined"
 #endif
 
-#define FREE(pointer)          \
-    do {                       \
-        if (pointer != NULL) { \
-            free(pointer);     \
-            pointer = NULL;    \
-        }                      \
+#define FREE(pointer)   \
+    do {                \
+        free(pointer);  \
+        pointer = NULL; \
     } while (0)
 
 #include "xcb.h"
@@ -94,7 +92,7 @@ static xcb_get_modifier_mapping_reply_t *modmap_reply;
 static i3Font font;
 static i3Font bold_font;
 static int char_width;
-static char *socket_path;
+static char *socket_path = NULL;
 static xcb_window_t win;
 static surface_t surface;
 static xcb_key_symbols_t *symbols;
@@ -744,7 +742,6 @@ static void finish() {
 
 int main(int argc, char *argv[]) {
     char *xdg_config_home;
-    socket_path = getenv("I3SOCK");
     char *pattern = "pango:monospace 8";
     char *patternbold = "pango:monospace bold 8";
     int o, option_index = 0;
@@ -824,12 +821,6 @@ int main(int argc, char *argv[]) {
                                     &xkb_base_error) != 1)
         errx(EXIT_FAILURE, "Could not setup XKB extension.");
 
-    if (socket_path == NULL)
-        socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);
-
-    if (socket_path == NULL)
-        socket_path = "/tmp/i3-ipc.sock";
-
     keysyms = xcb_key_symbols_alloc(conn);
     xcb_get_modifier_mapping_cookie_t modmap_cookie;
     modmap_cookie = xcb_get_modifier_mapping(conn);
index 478af310c32318e42c12cedcae5730ad2100058c..e9901f8ee3f2c90c0e9aa37a864a6f96e08aad63 100644 (file)
@@ -25,6 +25,7 @@
 #include <fcntl.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
+#include <signal.h>
 
 #include "libi3.h"
 #include "shmlog.h"
@@ -38,6 +39,29 @@ static uint32_t wrap_count;
 static i3_shmlog_header *header;
 static char *logbuffer,
     *walk;
+static int ipcfd = -1;
+
+static volatile bool interrupted = false;
+
+static void sighandler(int signal) {
+    interrupted = true;
+}
+
+static void disable_shmlog(void) {
+    const char *disablecmd = "debuglog off; shmlog off";
+    if (ipc_send_message(ipcfd, strlen(disablecmd),
+                         I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)disablecmd) != 0)
+        err(EXIT_FAILURE, "IPC send");
+
+    /* Ensure the command was sent by waiting for the reply: */
+    uint32_t reply_length = 0;
+    uint8_t *reply = NULL;
+    if (ipc_recv_message(ipcfd, I3_IPC_REPLY_TYPE_COMMAND,
+                         &reply_length, &reply) != 0) {
+        err(EXIT_FAILURE, "IPC recv");
+    }
+    free(reply);
+}
 
 static int check_for_wrap(void) {
     if (wrap_count == header->wrap_count)
@@ -59,6 +83,14 @@ static void print_till_end(void) {
     walk += len;
 }
 
+void errorlog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+}
+
 int main(int argc, char *argv[]) {
     int o, option_index = 0;
     bool verbose = false;
@@ -123,15 +155,35 @@ int main(int argc, char *argv[]) {
             exit(1);
         }
         if (root_atom_contents("I3_CONFIG_PATH", conn, screen) != NULL) {
-            fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled.\n\n");
-            if (!is_debug_build()) {
+            fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled. Enabling SHM log until cancelled\n\n");
+            ipcfd = ipc_connect(NULL);
+            const char *enablecmd = "debuglog on; shmlog 5242880";
+            if (ipc_send_message(ipcfd, strlen(enablecmd),
+                                 I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)enablecmd) != 0)
+                err(EXIT_FAILURE, "IPC send");
+            /* By the time we receive a reply, I3_SHMLOG_PATH is set: */
+            uint32_t reply_length = 0;
+            uint8_t *reply = NULL;
+            if (ipc_recv_message(ipcfd, I3_IPC_REPLY_TYPE_COMMAND,
+                                 &reply_length, &reply) != 0) {
+                err(EXIT_FAILURE, "IPC recv");
+            }
+            free(reply);
+
+            atexit(disable_shmlog);
+
+            /* Retry: */
+            shmname = root_atom_contents("I3_SHMLOG_PATH", NULL, 0);
+            if (shmname == NULL && !is_debug_build()) {
                 fprintf(stderr, "You seem to be using a release version of i3:\n  %s\n\n", I3_VERSION);
                 fprintf(stderr, "Release versions do not use SHM logging by default,\ntherefore i3-dump-log does not work.\n\n");
                 fprintf(stderr, "Please follow this guide instead:\nhttps://i3wm.org/docs/debugging-release-version.html\n");
                 exit(1);
             }
         }
-        errx(EXIT_FAILURE, "Cannot get I3_SHMLOG_PATH atom contents. Is i3 running on this display?");
+        if (shmname == NULL) {
+            errx(EXIT_FAILURE, "Cannot get I3_SHMLOG_PATH atom contents. Is i3 running on this display?");
+        }
     }
 
     if (*shmname == '\0')
@@ -182,22 +234,32 @@ int main(int argc, char *argv[]) {
     print_till_end();
 
 #if !defined(__OpenBSD__)
-    if (follow) {
-        /* Since pthread_cond_wait() expects a mutex, we need to provide one.
+    if (!follow) {
+        return 0;
+    }
+
+    /* Handle SIGINT gracefully to invoke atexit handlers, if any. */
+    struct sigaction action;
+    action.sa_handler = sighandler;
+    sigemptyset(&action.sa_mask);
+    action.sa_flags = 0;
+    sigaction(SIGINT, &action, NULL);
+
+    /* Since pthread_cond_wait() expects a mutex, we need to provide one.
          * To not lock i3 (that’s bad, mhkay?) we just define one outside of
          * the shared memory. */
-        pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
-        pthread_mutex_lock(&dummy_mutex);
-        while (1) {
-            pthread_cond_wait(&(header->condvar), &dummy_mutex);
-            /* If this was not a spurious wakeup, print the new lines. */
-            if (header->offset_next_write != offset_next_write) {
-                offset_next_write = header->offset_next_write;
-                print_till_end();
-            }
+    pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
+    pthread_mutex_lock(&dummy_mutex);
+    while (!interrupted) {
+        pthread_cond_wait(&(header->condvar), &dummy_mutex);
+        /* If this was not a spurious wakeup, print the new lines. */
+        if (header->offset_next_write != offset_next_write) {
+            offset_next_write = header->offset_next_write;
+            print_till_end();
         }
     }
-#endif
 
+#endif
+    exit(0);
     return 0;
 }
index d347506fe61e69907bc9c13fbc5672ff3fd8113c..d7aae66b5f8ffb1033f93155d5e74eed20c2146a 100644 (file)
@@ -5,12 +5,10 @@
 #include <err.h>
 
 #define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
-#define FREE(pointer)          \
-    do {                       \
-        if (pointer != NULL) { \
-            free(pointer);     \
-            pointer = NULL;    \
-        }                      \
+#define FREE(pointer)   \
+    do {                \
+        free(pointer);  \
+        pointer = NULL; \
     } while (0)
 
 extern xcb_window_t root;
index 785a133fc10e7de57b307821b1c3302ee0c2069b..efb7b20c2b87a48183e7796427c0e4c60e5349b0 100644 (file)
@@ -41,7 +41,6 @@
  * the command will be sent to i3 */
 static char *format;
 
-static char *socket_path;
 static int sockfd;
 static xcb_key_symbols_t *symbols;
 static bool modeswitch_active = false;
@@ -374,7 +373,7 @@ free_resources:
 
 int main(int argc, char *argv[]) {
     format = sstrdup("%s");
-    socket_path = getenv("I3SOCK");
+    char *socket_path = NULL;
     char *pattern = sstrdup("pango:monospace 8");
     int o, option_index = 0;
 
@@ -438,12 +437,6 @@ int main(int argc, char *argv[]) {
     if (!conn || xcb_connection_has_error(conn))
         die("Cannot open display\n");
 
-    if (socket_path == NULL)
-        socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);
-
-    if (socket_path == NULL)
-        socket_path = "/tmp/i3-ipc.sock";
-
     sockfd = ipc_connect(socket_path);
 
     root_screen = xcb_aux_get_screen(conn, screen);
index 8907a6f7adf80cf68729983d264748505040b732..91a714e56637b5df408885509f680ad14e6c4b48 100644 (file)
@@ -38,8 +38,6 @@
 
 #include <i3/ipc.h>
 
-static char *socket_path;
-
 /*
  * Having verboselog() and errorlog() is necessary when using libi3.
  *
@@ -161,11 +159,7 @@ int main(int argc, char *argv[]) {
     if (pledge("stdio rpath unix", NULL) == -1)
         err(EXIT_FAILURE, "pledge");
 #endif
-    char *env_socket_path = getenv("I3SOCK");
-    if (env_socket_path)
-        socket_path = sstrdup(env_socket_path);
-    else
-        socket_path = NULL;
+    char *socket_path = NULL;
     int o, option_index = 0;
     uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND;
     char *payload = NULL;
@@ -183,8 +177,7 @@ int main(int argc, char *argv[]) {
 
     while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
         if (o == 's') {
-            if (socket_path != NULL)
-                free(socket_path);
+            free(socket_path);
             socket_path = sstrdup(optarg);
         } else if (o == 't') {
             if (strcasecmp(optarg, "command") == 0) {
@@ -207,9 +200,11 @@ int main(int argc, char *argv[]) {
                 message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION;
             } else if (strcasecmp(optarg, "get_config") == 0) {
                 message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
+            } else if (strcasecmp(optarg, "send_tick") == 0) {
+                message_type = I3_IPC_MESSAGE_TYPE_SEND_TICK;
             } else {
                 printf("Unknown message type\n");
-                printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n");
+                printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config, send_tick\n");
                 exit(EXIT_FAILURE);
             }
         } else if (o == 'q') {
@@ -226,13 +221,6 @@ int main(int argc, char *argv[]) {
         }
     }
 
-    if (socket_path == NULL)
-        socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
-
-    /* Fall back to the default socket path */
-    if (socket_path == NULL)
-        socket_path = sstrdup("/tmp/i3-ipc.sock");
-
     /* Use all arguments, separated by whitespace, as payload.
      * This way, you don’t have to do i3-msg 'mark foo', you can use
      * i3-msg mark foo */
@@ -251,17 +239,7 @@ int main(int argc, char *argv[]) {
     if (!payload)
         payload = sstrdup("");
 
-    int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
-    if (sockfd == -1)
-        err(EXIT_FAILURE, "Could not create socket");
-
-    struct sockaddr_un addr;
-    memset(&addr, 0, sizeof(struct sockaddr_un));
-    addr.sun_family = AF_LOCAL;
-    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
-    if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
-        err(EXIT_FAILURE, "Could not connect to i3 on socket \"%s\"", socket_path);
-
+    int sockfd = ipc_connect(socket_path);
     if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1)
         err(EXIT_FAILURE, "IPC: write()");
     free(payload);
index c5e94cc6140fb554d3fa48f1f6b26c1c2107f1db..cb672bead868f59460f5735f94c611e444b4783f 100644 (file)
@@ -5,12 +5,10 @@
 #include <err.h>
 
 #define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
-#define FREE(pointer)          \
-    do {                       \
-        if (pointer != NULL) { \
-            free(pointer);     \
-            pointer = NULL;    \
-        }                      \
+#define FREE(pointer)   \
+    do {                \
+        free(pointer);  \
+        pointer = NULL; \
     } while (0)
 
 #define xmacro(atom) xcb_atom_t A_##atom;
index 7d38f73140d741c635aa0de25bd0e5809d94df75..e4628e303452feae75294e0de9208cc26ba5bc8e 100644 (file)
@@ -575,7 +575,9 @@ int main(int argc, char *argv[]) {
 
             case XCB_CONFIGURE_NOTIFY: {
                 xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event;
-                draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height);
+                if (configure_notify->width > 0 && configure_notify->height > 0) {
+                    draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height);
+                }
                 break;
             }
         }
index ad3f6bddb433d3101636baefaaa402f6c9578d5f..dc95865ded332a403eb25e2d82bd08a5898b0bf6 100755 (executable)
@@ -9,7 +9,7 @@
 # mechanism to find the preferred editor
 
 # Hopefully one of these is installed (no flamewars about preference please!):
-for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mcedit; do
+for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mcedit gvim; do
     if command -v "$editor" > /dev/null 2>&1; then
         exec "$editor" "$@"
     fi
index f92ff224a8989d3fbef991257d0a9ccd8deb56b7..f1eb256ecdf4c0a6cf9543cd9c47222d3a31412f 100755 (executable)
@@ -8,7 +8,7 @@
 # We welcome patches that add distribution-specific mechanisms to find the
 # preferred terminal emulator. On Debian, there is the x-terminal-emulator
 # symlink for example.
-for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole; do
+for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda; do
     if command -v "$terminal" > /dev/null 2>&1; then
         exec "$terminal" "$@"
     fi
index 0871c7f49efa563529d22aacd56877d9483885fd..9479fac1df8bdfecd0c005000bfd76b7dd10a6e5 100644 (file)
@@ -85,4 +85,4 @@ bool child_want_click_events(void);
  * Generates a click event, if enabled.
  *
  */
-void send_block_clicked(int button, const char *name, const char *instance, int x, int y);
+void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int width, int height);
index e77e891b9f3bdf03afd548839b9d6508162a3b5a..61cac7f6bdcab344de7cbaaa9dfc5f1a8c4fc1f3 100644 (file)
@@ -27,6 +27,7 @@ typedef enum { M_DOCK = 0,
 typedef struct binding_t {
     int input_code;
     char *command;
+    bool release;
 
     TAILQ_ENTRY(binding_t)
     bindings;
index de960270e500186dce9c032e1a548b5d486f1bd7..29a7bcd3825e0b62fb354f233326dde25ea695b7 100644 (file)
@@ -33,6 +33,12 @@ void parse_outputs_json(char* json);
  */
 void init_outputs(void);
 
+/*
+ * free() all outputs data structures.
+ *
+ */
+void free_outputs(void);
+
 /*
  * Returns the output with the given name
  *
index 3af79ed779c9704495b7fe400f5dacd482979420..1f56361106dfc97670bc412bf5d6e2ad90fb6450 100644 (file)
 #define STARTS_WITH(string, len, needle) (((len) >= strlen((needle))) && strncasecmp((string), (needle), strlen((needle))) == 0)
 
 /* Securely free p */
-#define FREE(p)          \
-    do {                 \
-        if (p != NULL) { \
-            free(p);     \
-            p = NULL;    \
-        }                \
+#define FREE(p)   \
+    do {          \
+        free(p);  \
+        p = NULL; \
     } while (0)
 
 /* Securely free single-linked list */
index 65a147c4718aeb44fcd57d5580cca4c55925dd1a..453dd0ceb3eb9f90dc6e09b10607ca557795a521 100644 (file)
@@ -9,4 +9,5 @@ ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
 ATOM_DO(_NET_SYSTEM_TRAY_COLORS)
 ATOM_DO(_XEMBED_INFO)
 ATOM_DO(_XEMBED)
+ATOM_DO(I3_SYNC)
 #undef ATOM_DO
index fe989c44c25a639c8f51e6f8589d2fa787db8abd..1cd7d512aa0e6c128e66996b508d7696add950c1 100644 (file)
@@ -106,7 +106,7 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
     va_list args;
     va_start(args, format);
     if (vasprintf(&message, format, args) == -1) {
-        return;
+        goto finish;
     }
 
     struct status_block *err_block = scalloc(1, sizeof(struct status_block));
@@ -124,6 +124,7 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
     TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks);
     TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
 
+finish:
     FREE(message);
     va_end(args);
 }
@@ -595,7 +596,7 @@ void child_click_events_key(const char *key) {
  * Generates a click event, if enabled.
  *
  */
-void send_block_clicked(int button, const char *name, const char *instance, int x, int y) {
+void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int width, int height) {
     if (!child.click_events) {
         return;
     }
@@ -623,6 +624,18 @@ void send_block_clicked(int button, const char *name, const char *instance, int
     child_click_events_key("y");
     yajl_gen_integer(gen, y);
 
+    child_click_events_key("relative_x");
+    yajl_gen_integer(gen, x_rel);
+
+    child_click_events_key("relative_y");
+    yajl_gen_integer(gen, y_rel);
+
+    child_click_events_key("width");
+    yajl_gen_integer(gen, width);
+
+    child_click_events_key("height");
+    yajl_gen_integer(gen, height);
+
     yajl_gen_map_close(gen);
     child_write_output();
 }
index cbe84d5073e55b714a2d564fd4d666fdfb872e1a..a58b9bf80c06b69425872bfe74eb69602a2d8215 100644 (file)
@@ -107,34 +107,34 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
 
     if (!strcmp(cur_key, "mode")) {
         DLOG("mode = %.*s, len = %d\n", len, val, len);
-        config.hide_on_modifier = (len == 4 && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK
-                                                                                                   : (len == 4 && !strncmp((const char *)val, "hide", strlen("hide")) ? M_HIDE
-                                                                                                                                                                      : M_INVISIBLE));
+        config.hide_on_modifier = (len == strlen("dock") && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK
+                                                                                                                : (len == strlen("hide") && !strncmp((const char *)val, "hide", strlen("hide")) ? M_HIDE
+                                                                                                                                                                                                : M_INVISIBLE));
         return 1;
     }
 
     if (!strcmp(cur_key, "hidden_state")) {
         DLOG("hidden_state = %.*s, len = %d\n", len, val, len);
-        config.hidden_state = (len == 4 && !strncmp((const char *)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW);
+        config.hidden_state = (len == strlen("hide") && !strncmp((const char *)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW);
         return 1;
     }
 
     if (!strcmp(cur_key, "modifier")) {
         DLOG("modifier = %.*s\n", len, val);
-        if (len == 4 && !strncmp((const char *)val, "none", strlen("none"))) {
+        if (len == strlen("none") && !strncmp((const char *)val, "none", strlen("none"))) {
             config.modifier = XCB_NONE;
             return 1;
         }
 
-        if (len == 5 && !strncmp((const char *)val, "shift", strlen("shift"))) {
+        if (len == strlen("shift") && !strncmp((const char *)val, "shift", strlen("shift"))) {
             config.modifier = ShiftMask;
             return 1;
         }
-        if (len == 4 && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
+        if (len == strlen("ctrl") && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
             config.modifier = ControlMask;
             return 1;
         }
-        if (len == 4 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
+        if (len == strlen("Mod") + 1 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
             switch (val[3]) {
                 case '1':
                     config.modifier = Mod1Mask;
@@ -179,7 +179,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
 
     if (!strcmp(cur_key, "position")) {
         DLOG("position = %.*s\n", len, val);
-        config.position = (len == 3 && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT);
+        config.position = (len == strlen("top") && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT);
         return 1;
     }
 
@@ -192,6 +192,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
 
     if (!strcmp(cur_key, "font")) {
         DLOG("font = %.*s\n", len, val);
+        FREE(config.fontname);
         sasprintf(&config.fontname, "%.*s", len, val);
         return 1;
     }
@@ -263,6 +264,21 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
  *
  */
 static int config_boolean_cb(void *params_, int val) {
+    if (parsing_bindings) {
+        if (strcmp(cur_key, "release") == 0) {
+            binding_t *binding = TAILQ_LAST(&(config.bindings), bindings_head);
+            if (binding == NULL) {
+                ELOG("There is no binding to put the current command onto. This is a bug in i3.\n");
+                return 0;
+            }
+
+            binding->release = val;
+            return 1;
+        }
+
+        ELOG("Unknown key \"%s\" while parsing bar bindings.\n", cur_key);
+    }
+
     if (!strcmp(cur_key, "binding_mode_indicator")) {
         DLOG("binding_mode_indicator = %d\n", val);
         config.disable_binding_mode_indicator = !val;
index c932aaf7d3b2536d864aa3dbedc6c47ff6824908..cc3563ec0ad0039cc4867f79759c55e16a03e504 100644 (file)
@@ -64,17 +64,14 @@ void got_subscribe_reply(char *reply) {
  */
 void got_output_reply(char *reply) {
     DLOG("Clearing old output configuration...\n");
-    i3_output *o_walk;
-    SLIST_FOREACH(o_walk, outputs, slist) {
-        destroy_window(o_walk);
-    }
-    FREE_SLIST(outputs, i3_output);
+    free_outputs();
 
     DLOG("Parsing outputs JSON...\n");
     parse_outputs_json(reply);
     DLOG("Reconfiguring windows...\n");
     reconfig_windows(false);
 
+    i3_output *o_walk;
     SLIST_FOREACH(o_walk, outputs, slist) {
         kick_tray_clients(o_walk);
     }
@@ -177,7 +174,7 @@ void got_bar_config_update(char *event) {
 
     /* update the configuration with the received settings */
     DLOG("Received bar config update \"%s\"\n", event);
-    char *old_command = sstrdup(config.command);
+    char *old_command = config.command ? sstrdup(config.command) : NULL;
     bar_display_mode_t old_mode = config.hide_on_modifier;
     parse_config_json(event);
     if (old_mode != config.hide_on_modifier) {
@@ -189,7 +186,7 @@ void got_bar_config_update(char *event) {
     init_colors(&(config.colors));
 
     /* restart status command process */
-    if (strcmp(old_command, config.command) != 0) {
+    if (old_command && strcmp(old_command, config.command) != 0) {
         kill_child();
         start_child(config.command);
     }
index 910e952482f0d788b46747ac0b9bab396242f7c5..069803d4f944289afac72323bf15acd8d24fae03 100644 (file)
@@ -182,7 +182,5 @@ int main(int argc, char **argv) {
     clean_xcb();
     ev_default_destroy();
 
-    free_workspaces();
-
     return 0;
 }
index bd056a700d231a49eadfcee3120bc156364a9d6b..4c9ce6513e9d082d365b0f9264a4ecf6072dc0ce 100644 (file)
@@ -173,6 +173,12 @@ static int outputs_start_map_cb(void *params_) {
     return 1;
 }
 
+static void clear_output(i3_output *output) {
+    FREE(output->name);
+    FREE(output->workspaces);
+    FREE(output->trayclients);
+}
+
 /*
  * We hit the end of a map (rect or a new output)
  *
@@ -199,9 +205,7 @@ static int outputs_end_map_cb(void *params_) {
         if (!handle_output) {
             DLOG("Ignoring output \"%s\", not configured to handle it.\n",
                  params->outputs_walk->name);
-            FREE(params->outputs_walk->name);
-            FREE(params->outputs_walk->workspaces);
-            FREE(params->outputs_walk->trayclients);
+            clear_output(params->outputs_walk);
             FREE(params->outputs_walk);
             FREE(params->cur_key);
             return 1;
@@ -217,6 +221,9 @@ static int outputs_end_map_cb(void *params_) {
         target->primary = params->outputs_walk->primary;
         target->ws = params->outputs_walk->ws;
         target->rect = params->outputs_walk->rect;
+
+        clear_output(params->outputs_walk);
+        FREE(params->outputs_walk);
     }
     return 1;
 }
@@ -260,7 +267,6 @@ void init_outputs(void) {
  */
 void parse_outputs_json(char *json) {
     struct outputs_json_params params;
-
     params.outputs_walk = NULL;
     params.cur_key = NULL;
     params.json = json;
@@ -286,6 +292,27 @@ void parse_outputs_json(char *json) {
     yajl_free(handle);
 }
 
+/*
+ * free() all outputs data structures.
+ *
+ */
+void free_outputs(void) {
+    free_workspaces();
+
+    i3_output *outputs_walk;
+    if (outputs == NULL) {
+        return;
+    }
+    SLIST_FOREACH(outputs_walk, outputs, slist) {
+        destroy_window(outputs_walk);
+        if (outputs_walk->trayclients != NULL && !TAILQ_EMPTY(outputs_walk->trayclients)) {
+            FREE_TAILQ(outputs_walk->trayclients, trayclient);
+        }
+        clear_output(outputs_walk);
+    }
+    FREE_SLIST(outputs, i3_output);
+}
+
 /*
  * Returns the output with the given name
  *
index fed969df7823ed34266bd1bb3c10b9071f67f2ae..542c86c3c645a4c117d15f22775d73fb900476f6 100644 (file)
@@ -83,7 +83,6 @@ int mod_pressed = 0;
 
 /* Event watchers, to interact with the user */
 ev_prepare *xcb_prep;
-ev_check *xcb_chk;
 ev_io *xcb_io;
 ev_io *xkb_io;
 
@@ -440,6 +439,18 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
     xcb_flush(xcb_connection);
 }
 
+static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_release) {
+    binding_t *binding;
+    TAILQ_FOREACH(binding, &(config.bindings), bindings) {
+        if ((binding->input_code != input_code) || (binding->release != event_is_release))
+            continue;
+
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
+        return true;
+    }
+    return false;
+}
+
 /*
  * Handle a button press event (i.e. a mouse click on one of our bars).
  * We determine, whether the click occurred on a workspace button or if the scroll-
@@ -461,10 +472,16 @@ void handle_button(xcb_button_press_event_t *event) {
         return;
     }
 
-    int32_t x = event->event_x >= 0 ? event->event_x : 0;
-
     DLOG("Got button %d\n", event->detail);
 
+    /* During button release events, only check for custom commands. */
+    const bool event_is_release = (event->response_type & ~0x80) == XCB_BUTTON_RELEASE;
+    if (event_is_release) {
+        execute_custom_command(event->detail, event_is_release);
+        return;
+    }
+
+    int32_t x = event->event_x >= 0 ? event->event_x : 0;
     int workspace_width = 0;
     i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
 
@@ -506,7 +523,8 @@ void handle_button(xcb_button_press_event_t *event) {
                 block_x += render->width + render->x_offset + render->x_append + get_sep_offset(block) + sep_offset_remainder;
 
                 if (statusline_x <= block_x && statusline_x >= last_block_x) {
-                    send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y);
+                    send_block_clicked(event->detail, block->name, block->instance,
+                                       event->root_x, event->root_y, statusline_x - last_block_x, event->event_y, block_x - last_block_x, bar_height);
                     return;
                 }
 
@@ -517,12 +535,7 @@ void handle_button(xcb_button_press_event_t *event) {
 
     /* If a custom command was specified for this mouse button, it overrides
      * the default behavior. */
-    binding_t *binding;
-    TAILQ_FOREACH(binding, &(config.bindings), bindings) {
-        if (binding->input_code != event->detail)
-            continue;
-
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
+    if (execute_custom_command(event->detail, event_is_release)) {
         return;
     }
 
@@ -678,8 +691,26 @@ static void configure_trayclients(void) {
  *
  */
 static void handle_client_message(xcb_client_message_event_t *event) {
-    if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
-        event->format == 32) {
+    if (event->type == atoms[I3_SYNC]) {
+        xcb_window_t window = event->data.data32[0];
+        uint32_t rnd = event->data.data32[1];
+        DLOG("[i3 sync protocol] Forwarding random value %d, X11 window 0x%08x to i3\n", rnd, window);
+
+        void *reply = scalloc(32, 1);
+        xcb_client_message_event_t *ev = reply;
+
+        ev->response_type = XCB_CLIENT_MESSAGE;
+        ev->window = window;
+        ev->type = atoms[I3_SYNC];
+        ev->format = 32;
+        ev->data.data32[0] = window;
+        ev->data.data32[1] = rnd;
+
+        xcb_send_event(conn, false, xcb_root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)ev);
+        xcb_flush(conn);
+        free(reply);
+    } else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
+               event->format == 32) {
         DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
         /* event->data.data32[0] is the timestamp */
         uint32_t op = event->data.data32[1];
@@ -1057,21 +1088,11 @@ static void handle_resize_request(xcb_resize_request_event_t *event) {
 }
 
 /*
- * This function is called immediately before the main loop locks. We flush xcb
- * then (and only then)
+ * This function is called immediately before the main loop locks. We check for
+ * events from X11, handle them, then flush our outgoing queue.
  *
  */
 void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
-    xcb_flush(xcb_connection);
-}
-
-/*
- * This function is called immediately after the main loop locks, so when one
- * of the watchers registered an event.
- * We check whether an X-Event arrived and handle it.
- *
- */
-void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
     xcb_generic_event_t *event;
 
     if (xcb_connection_has_error(xcb_connection)) {
@@ -1157,6 +1178,7 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
                 }
 
                 break;
+            case XCB_BUTTON_RELEASE:
             case XCB_BUTTON_PRESS:
                 /* Button press events are mouse buttons clicked on one of our bars */
                 handle_button((xcb_button_press_event_t *)event);
@@ -1192,6 +1214,8 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
         }
         free(event);
     }
+
+    xcb_flush(xcb_connection);
 }
 
 /*
@@ -1249,21 +1273,12 @@ char *init_xcb_early() {
     /* The various watchers to communicate with xcb */
     xcb_io = smalloc(sizeof(ev_io));
     xcb_prep = smalloc(sizeof(ev_prepare));
-    xcb_chk = smalloc(sizeof(ev_check));
 
     ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
     ev_prepare_init(xcb_prep, &xcb_prep_cb);
-    ev_check_init(xcb_chk, &xcb_chk_cb);
-
-    /* Within an event loop iteration, run the xcb_chk watcher last: other
-     * watchers might call xcb_flush(), which, unexpectedly, can also read
-     * events into the queue (see _xcb_conn_wait). Hence, we need to drain xcb’s
-     * queue last, otherwise we risk dead-locking. */
-    ev_set_priority(xcb_chk, EV_MINPRI);
 
     ev_io_start(main_loop, xcb_io);
     ev_prepare_start(main_loop, xcb_prep);
-    ev_check_start(main_loop, xcb_chk);
 
     /* Now we get the atoms and save them in a nice data structure */
     get_atoms();
@@ -1499,16 +1514,7 @@ void init_tray_colors(void) {
  *
  */
 void clean_xcb(void) {
-    i3_output *o_walk;
-    free_workspaces();
-    SLIST_FOREACH(o_walk, outputs, slist) {
-        destroy_window(o_walk);
-        FREE(o_walk->trayclients);
-        FREE(o_walk->workspaces);
-        FREE(o_walk->name);
-    }
-    FREE_SLIST(outputs, i3_output);
-    FREE(outputs);
+    free_outputs();
 
     free_font();
 
@@ -1517,11 +1523,9 @@ void clean_xcb(void) {
     xcb_aux_sync(xcb_connection);
     xcb_disconnect(xcb_connection);
 
-    ev_check_stop(main_loop, xcb_chk);
     ev_prepare_stop(main_loop, xcb_prep);
     ev_io_stop(main_loop, xcb_io);
 
-    FREE(xcb_chk);
     FREE(xcb_prep);
     FREE(xcb_io);
 }
@@ -1689,7 +1693,8 @@ void reconfig_windows(bool redraw_bars) {
              * */
             values[3] = XCB_EVENT_MASK_EXPOSURE |
                         XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
-                        XCB_EVENT_MASK_BUTTON_PRESS;
+                        XCB_EVENT_MASK_BUTTON_PRESS |
+                        XCB_EVENT_MASK_BUTTON_RELEASE;
             if (config.hide_on_modifier == M_DOCK) {
                 /* If the bar is normally visible, catch visibility change events to suspend
                  * the status process when the bar is obscured by full-screened windows.  */
index c26835b90ff07e6de3742175fbd9052a7551388e..ecc875d08298f6b33b8b03340b84c8d1e33266a3 100644 (file)
@@ -28,6 +28,7 @@
 #include <errno.h>
 #include <err.h>
 #include <stdint.h>
+#include <inttypes.h>
 #include <math.h>
 #include <limits.h>
 
index 9780f788b5bb53b308609df0818017e420d77024..1057f021db5a3d2aa45034b3557f51071cf03c07 100644 (file)
@@ -63,10 +63,10 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *no_aut
 void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth);
 
 /**
- * Implementation of 'resize set <px> [px] <px> [px]'.
+ * Implementation of 'resize set <width> [px | ppt] <height> [px | ppt]'.
  *
  */
-void cmd_resize_set(I3_CMD, long cwidth, long cheight);
+void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height);
 
 /**
  * Implementation of 'resize grow|shrink <direction> [<px> px] [or <ppt> ppt]'.
index 674ee61d3b1fd177a537cbaff2a5f0ea878f5c31..58123a87f719b95518ccd48cc06fb9a462a21b1c 100644 (file)
@@ -38,6 +38,12 @@ void con_free(Con *con);
  */
 void con_focus(Con *con);
 
+/**
+ * Sets input focus to the given container and raises it to the top.
+ *
+ */
+void con_activate(Con *con);
+
 /**
  * Closes the given container.
  *
@@ -222,6 +228,22 @@ void con_unmark(Con *con, const char *name);
  */
 Con *con_for_window(Con *con, i3Window *window, Match **store_match);
 
+/**
+ * Iterate over the container's focus stack and return an array with the
+ * containers inside it, ordered from higher focus order to lowest.
+ *
+ */
+Con **get_focus_order(Con *con);
+
+/**
+ * Clear the container's focus stack and re-add it using the provided container
+ * array. The function doesn't check if the provided array contains the same
+ * containers with the previous focus stack but will not add floating containers
+ * in the new focus stack if container is not a workspace.
+ *
+ */
+void set_focus_order(Con *con, Con **focus_order);
+
 /**
  * Returns the number of children of this container.
  *
@@ -314,7 +336,16 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates,
  * visible workspace on the given output.
  *
  */
-void con_move_to_output(Con *con, Output *output);
+void con_move_to_output(Con *con, Output *output, bool fix_coordinates);
+
+/**
+ * Moves the given container to the currently focused container on the
+ * visible workspace on the output specified by the given name.
+ * The current output for the container is used to resolve relative names
+ * such as left, right, up, down.
+ *
+ */
+bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates);
 
 /**
  * Moves the given container to the given mark.
index f35666f3612000c21c4837c592a5b22261bf6d33..187b550c994230ebfc47ac72379af0fe7f3e03d1 100644 (file)
@@ -49,6 +49,7 @@ CFGFUN(workspace_layout, const char *layout);
 CFGFUN(workspace_back_and_forth, const char *value);
 CFGFUN(focus_follows_mouse, const char *value);
 CFGFUN(mouse_warping, const char *value);
+CFGFUN(focus_wrapping, const char *value);
 CFGFUN(force_focus_wrapping, const char *value);
 CFGFUN(force_xinerama, const char *value);
 CFGFUN(disable_randr15, const char *value);
@@ -57,7 +58,8 @@ CFGFUN(force_display_urgency_hint, const long duration_ms);
 CFGFUN(focus_on_window_activation, const char *mode);
 CFGFUN(show_marks, const char *value);
 CFGFUN(hide_edge_borders, const char *borders);
-CFGFUN(assign, const char *workspace);
+CFGFUN(assign_output, const char *output);
+CFGFUN(assign, const char *workspace, bool is_number);
 CFGFUN(no_focus);
 CFGFUN(ipc_socket, const char *path);
 CFGFUN(restart_state, const char *path);
@@ -65,7 +67,7 @@ CFGFUN(popup_during_fullscreen, const char *value);
 CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border);
 CFGFUN(color_single, const char *colorclass, const char *color);
 CFGFUN(floating_modifier, const char *modifiers);
-CFGFUN(new_window, const char *windowtype, const char *border, const long width);
+CFGFUN(default_border, const char *windowtype, const char *border, const long width);
 CFGFUN(workspace, const char *workspace, const char *output);
 CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command);
 
@@ -82,7 +84,7 @@ CFGFUN(bar_verbose, const char *verbose);
 CFGFUN(bar_modifier, const char *modifier);
 CFGFUN(bar_wheel_up_cmd, const char *command);
 CFGFUN(bar_wheel_down_cmd, const char *command);
-CFGFUN(bar_bindsym, const char *button, const char *command);
+CFGFUN(bar_bindsym, const char *button, const char *release, const char *command);
 CFGFUN(bar_position, const char *position);
 CFGFUN(bar_i3bar_command, const char *i3bar_command);
 CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);
index 4f6e5ce833be7e80ac36422efc8537e4e8ba68d5..ac8001590a9ff37f7f5086d25a9bed4a2d39a140 100644 (file)
@@ -137,15 +137,24 @@ struct Config {
      * comes with i3. Thus, you can turn it off entirely. */
     bool disable_workspace_bar;
 
-    /** Think of the following layout: Horizontal workspace with a tabbed
-     * con on the left of the screen and a terminal on the right of the
-     * screen. You are in the second container in the tabbed container and
-     * focus to the right. By default, i3 will set focus to the terminal on
-     * the right. If you are in the first container in the tabbed container
-     * however, focusing to the left will wrap. This option forces i3 to
-     * always wrap, which will result in you having to use "focus parent"
-     * more often. */
-    bool force_focus_wrapping;
+    /** When focus wrapping is enabled (the default), attempting to
+     * move focus past the edge of the screen (in other words, in a
+     * direction in which there are no more containers to focus) will
+     * cause the focus to wrap to the opposite edge of the current
+     * container. When it is disabled, nothing happens; the current
+     * focus is preserved.
+     *
+     * Additionally, focus wrapping may be forced. Think of the
+     * following layout: Horizontal workspace with a tabbed con on the
+     * left of the screen and a terminal on the right of the
+     * screen. You are in the second container in the tabbed container
+     * and focus to the right. By default, i3 will set focus to the
+     * terminal on the right. If you are in the first container in the
+     * tabbed container however, focusing to the left will
+     * wrap. Setting focus_wrapping to FOCUS_WRAPPING_FORCE forces i3
+     * to always wrap, which will result in you having to use "focus
+     * parent" more often. */
+    focus_wrapping_t focus_wrapping;
 
     /** By default, use the RandR API for multi-monitor setups.
      * Unfortunately, the nVidia binary graphics driver doesn't support
@@ -375,6 +384,9 @@ struct Barbinding {
     /** The command which is to be executed for this button. */
     char *command;
 
+    /** If true, the command will be executed after the button is released. */
+    bool release;
+
     TAILQ_ENTRY(Barbinding)
     bindings;
 };
index 31ef1dc1f8d9dfb6eb437dc46b396f1371696d6b..69a46e464331172172873a70940af6d348cb09bd 100644 (file)
@@ -133,6 +133,15 @@ typedef enum {
     POINTER_WARPING_NONE = 1
 } warping_t;
 
+/**
+ * Focus wrapping modes.
+ */
+typedef enum {
+    FOCUS_WRAPPING_OFF = 0,
+    FOCUS_WRAPPING_ON = 1,
+    FOCUS_WRAPPING_FORCE = 2
+} focus_wrapping_t;
+
 /**
  * Stores a rectangle, for example the size of a window, the child window etc.
  * It needs to be packed so that the compiler will not add any padding bytes.
@@ -556,7 +565,9 @@ struct Assignment {
         A_ANY = 0,
         A_COMMAND = (1 << 0),
         A_TO_WORKSPACE = (1 << 1),
-        A_NO_FOCUS = (1 << 2)
+        A_NO_FOCUS = (1 << 2),
+        A_TO_WORKSPACE_NUMBER = (1 << 3),
+        A_TO_OUTPUT = (1 << 4)
     } type;
 
     /** the criteria to check if a window matches */
@@ -566,6 +577,7 @@ struct Assignment {
     union {
         char *command;
         char *workspace;
+        char *output;
     } dest;
 
     TAILQ_ENTRY(Assignment)
index 4d13d448f1395fdd97e13e82a477178e28ee4ea0..93a7e0a34cf6ca5983fe41cf7298b369bd32d9fe 100644 (file)
@@ -74,3 +74,4 @@ extern bool xcursor_supported, xkb_supported;
 extern xcb_window_t root;
 extern struct ev_loop *main_loop;
 extern bool only_check_config;
+extern bool force_xinerama;
index 993a2a2482d1de70ed67433ae2189948c6f53f35..9e0280c9363322b48ef7f25534bfde2b9ccd9c4f 100644 (file)
@@ -60,6 +60,9 @@ typedef struct i3_ipc_header {
 /** Request the raw last loaded i3 config. */
 #define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9
 
+/** Send a tick event to all subscribers. */
+#define I3_IPC_MESSAGE_TYPE_SEND_TICK 10
+
 /*
  * Messages from i3 to clients
  *
@@ -74,6 +77,7 @@ typedef struct i3_ipc_header {
 #define I3_IPC_REPLY_TYPE_VERSION 7
 #define I3_IPC_REPLY_TYPE_BINDING_MODES 8
 #define I3_IPC_REPLY_TYPE_CONFIG 9
+#define I3_IPC_REPLY_TYPE_TICK 10
 
 /*
  * Events from i3 to clients. Events have the first bit set high.
@@ -101,3 +105,6 @@ typedef struct i3_ipc_header {
 
 /** The shutdown event will be triggered when the ipc shuts down */
 #define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6)
+
+/** The tick event will be sent upon a tick IPC message */
+#define I3_IPC_EVENT_TICK (I3_IPC_EVENT_MASK | 7)
index 7ffbf7a831f9de3418f9d4ddad304beee5cbff22..c6ad35c770745038eb99dbd370966fd489bc262f 100644 (file)
@@ -31,6 +31,10 @@ typedef struct ipc_client {
     int num_events;
     char **events;
 
+    /* For clients which subscribe to the tick event: whether the first tick
+     * event has been sent by i3. */
+    bool first_tick_sent;
+
     TAILQ_ENTRY(ipc_client)
     clients;
 } ipc_client;
index 7b33de907fb397d8348bb70d1ee23558c9781271..386341561854fcaeefa240661bf10cc59cef6245 100644 (file)
@@ -11,6 +11,6 @@
 
 #include <config.h>
 
-bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction);
+bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides);
 
 int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
index de6fa56850eb8f0ba8ba89c941b921a13796b61c..3547d8d7b50df6533fc566aec682efe0e63bc1a3 100644 (file)
         break;                            \
     }
 
-#define FREE(pointer)          \
-    do {                       \
-        if (pointer != NULL) { \
-            free(pointer);     \
-            pointer = NULL;    \
-        }                      \
+#define FREE(pointer)   \
+    do {                \
+        free(pointer);  \
+        pointer = NULL; \
     } while (0)
 
 #define CALL(obj, member, ...) obj->member(obj, ##__VA_ARGS__)
 
+#define SWAP(first, second, type) \
+    do {                          \
+        type tmp_SWAP = first;    \
+        first = second;           \
+        second = tmp_SWAP;        \
+    } while (0)
+
 int min(int a, int b);
 int max(int a, int b);
 bool rect_contains(Rect rect, uint32_t x, uint32_t y);
index 93a3c6f6a99f1074d333adb3ebdf6250f671beb7..a2c40319a52b492fcd57b6168495cc5d127257af 100644 (file)
@@ -43,12 +43,13 @@ void init_dpi(void) {
     }
 
     char *endptr;
-    dpi = strtol(resource, &endptr, 10);
-    if (dpi == LONG_MAX || dpi == LONG_MIN || dpi < 0 || *endptr != '\0' || endptr == resource) {
+    double in_dpi = strtod(resource, &endptr);
+    if (in_dpi == HUGE_VAL || dpi < 0 || *endptr != '\0' || endptr == resource) {
         ELOG("Xft.dpi = %s is an invalid number and couldn't be parsed.\n", resource);
         dpi = 0;
         goto init_dpi_end;
     }
+    dpi = (long)round(in_dpi);
 
     DLOG("Found Xft.dpi = %ld.\n", dpi);
 
index 2e6283425d47baf3853e4334f5837cd19d038496..f659a1a4720f566fe0083c5076e53d89a5969ba7 100644 (file)
  *
  */
 int ipc_connect(const char *socket_path) {
+    char *path = NULL;
+    if (socket_path != NULL) {
+        path = sstrdup(socket_path);
+    }
+
+    if (path == NULL) {
+        if ((path = getenv("I3SOCK")) != NULL) {
+            path = sstrdup(path);
+        }
+    }
+
+    if (path == NULL) {
+        path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
+    }
+
+    if (path == NULL) {
+        path = sstrdup("/tmp/i3-ipc.sock");
+    }
+
     int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
     if (sockfd == -1)
         err(EXIT_FAILURE, "Could not create socket");
@@ -31,9 +50,9 @@ int ipc_connect(const char *socket_path) {
     struct sockaddr_un addr;
     memset(&addr, 0, sizeof(struct sockaddr_un));
     addr.sun_family = AF_LOCAL;
-    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
+    strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
     if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
-        err(EXIT_FAILURE, "Could not connect to i3");
-
+        err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path);
+    free(path);
     return sockfd;
 }
index 16dda90d89b8cbcd5ec540aff00b1c3aba86d46c..84da5aa36cb90d7b913ec7c0afc87568102a9d70 100644 (file)
@@ -13,6 +13,7 @@
 #include <stdint.h>
 #include <unistd.h>
 #include <errno.h>
+#include <inttypes.h>
 
 #include <i3/ipc.h>
 
@@ -41,14 +42,21 @@ int ipc_recv_message(int sockfd, uint32_t *message_type,
         if (n == -1)
             return -1;
         if (n == 0) {
-            return -2;
+            if (read_bytes == 0) {
+                return -2;
+            } else {
+                ELOG("IPC: unexpected EOF while reading header, got %" PRIu32 " bytes, want %" PRIu32 " bytes\n",
+                     read_bytes, to_read);
+                return -3;
+            }
         }
 
         read_bytes += n;
     }
 
     if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
-        ELOG("IPC: invalid magic in reply\n");
+        ELOG("IPC: invalid magic in header, got \"%.*s\", want \"%s\"\n",
+             (int)strlen(I3_IPC_MAGIC), walk, I3_IPC_MAGIC);
         return -3;
     }
 
@@ -61,13 +69,18 @@ int ipc_recv_message(int sockfd, uint32_t *message_type,
     *reply = smalloc(*reply_length);
 
     read_bytes = 0;
-    int n;
     while (read_bytes < *reply_length) {
-        if ((n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes)) == -1) {
+        const int n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes);
+        if (n == -1) {
             if (errno == EINTR || errno == EAGAIN)
                 continue;
             return -1;
         }
+        if (n == 0) {
+            ELOG("IPC: unexpected EOF while reading payload, got %" PRIu32 " bytes, want %" PRIu32 " bytes\n",
+                 read_bytes, *reply_length);
+            return -3;
+        }
 
         read_bytes += n;
     }
index effae6c06ebcbdc52a5220024a05de0391d4b7eb..4f16d6c1e0e10098dee2645e098144142d25bc61 100644 (file)
@@ -30,6 +30,7 @@ It tries to start one of the following (in that order):
 * jed
 * gedit
 * mcedit
+* gvim
 
 Please don’t complain about the order: If the user has any preference, they will
 have $VISUAL or $EDITOR set.
index 20a6810c59bb8ed9ec4a1ab717e85de16ad63b3e..894af9120ed404da00610a8a8d797b5c92aa7e8c 100644 (file)
@@ -44,6 +44,9 @@ It tries to start one of the following (in that order):
 * tilix
 * terminix
 * konsole
+* kitty
+* guake
+* tilda
 
 Please don’t complain about the order: If the user has any preference, they will
 have $TERMINAL set or modified their i3 configuration file.
index a587332817e8b23bde3d48cd8ef434c43924b275..0289fa1ab6018a404cb2445f5fa57b61eaf283eb 100644 (file)
@@ -258,14 +258,16 @@ state RESIZE_SET:
       -> RESIZE_WIDTH
 
 state RESIZE_WIDTH:
-  'px'
+  mode_width = 'px', 'ppt'
       ->
   height = number
       -> RESIZE_HEIGHT
 
 state RESIZE_HEIGHT:
-  'px', end
-      -> call cmd_resize_set(&width, &height)
+  mode_height = 'px', 'ppt'
+      ->
+  end
+      -> call cmd_resize_set(&width, $mode_width, &height, $mode_height)
 
 # rename workspace <name> to <name>
 # rename workspace to <name>
index 4aa320bf63a1b822f8c66a3f1c2e8304058d7118..60a1fc67a329294d0ca33f0aa2b6d2c8c81b74f2 100644 (file)
@@ -29,13 +29,15 @@ state INITIAL:
   'floating_modifier'                      -> FLOATING_MODIFIER
   'default_orientation'                    -> DEFAULT_ORIENTATION
   'workspace_layout'                       -> WORKSPACE_LAYOUT
-  windowtype = 'new_window', 'new_float'   -> NEW_WINDOW
+  windowtype = 'default_border', 'new_window', 'default_floating_border', 'new_float'
+      -> DEFAULT_BORDER
   'hide_edge_borders'                      -> HIDE_EDGE_BORDERS
   'for_window'                             -> FOR_WINDOW
   'assign'                                 -> ASSIGN
   'no_focus'                               -> NO_FOCUS
   'focus_follows_mouse'                    -> FOCUS_FOLLOWS_MOUSE
   'mouse_warping'                          -> MOUSE_WARPING
+  'focus_wrapping'                         -> FOCUS_WRAPPING
   'force_focus_wrapping'                   -> FORCE_FOCUS_WRAPPING
   'force_xinerama', 'force-xinerama'       -> FORCE_XINERAMA
   'disable_randr15', 'disable-randr15'     -> DISABLE_RANDR15
@@ -104,25 +106,25 @@ state WORKSPACE_LAYOUT:
   layout = 'default', 'stacking', 'stacked', 'tabbed'
       -> call cfg_workspace_layout($layout)
 
-# new_window <normal|1pixel|none>
-# new_float <normal|1pixel|none>
-state NEW_WINDOW:
+# <default_border|new_window> <normal|1pixel|none>
+# <default_floating_border|new_float> <normal|1pixel|none>
+state DEFAULT_BORDER:
   border = 'normal', 'pixel'
-      -> NEW_WINDOW_PIXELS
+      -> DEFAULT_BORDER_PIXELS
   border = '1pixel', 'none'
-      -> call cfg_new_window($windowtype, $border, -1)
+      -> call cfg_default_border($windowtype, $border, -1)
 
-state NEW_WINDOW_PIXELS:
+state DEFAULT_BORDER_PIXELS:
   end
-      -> call cfg_new_window($windowtype, $border, 2)
+      -> call cfg_default_border($windowtype, $border, 2)
   width = number
-      -> NEW_WINDOW_PIXELS_PX
+      -> DEFAULT_BORDER_PIXELS_PX
 
-state NEW_WINDOW_PIXELS_PX:
+state DEFAULT_BORDER_PIXELS_PX:
   'px'
       ->
   end
-      -> call cfg_new_window($windowtype, $border, &width)
+      -> call cfg_default_border($windowtype, $border, &width)
 
 # hide_edge_borders <none|vertical|horizontal|both|smart>
 # also hide_edge_borders <bool> for compatibility
@@ -141,7 +143,7 @@ state FOR_WINDOW_COMMAND:
   command = string
       -> call cfg_for_window($command)
 
-# assign <criteria> [→] workspace
+# assign <criteria> [→] [workspace | output] <name>
 state ASSIGN:
   '['
       -> call cfg_criteria_init(ASSIGN_WORKSPACE); CRITERIA
@@ -149,10 +151,22 @@ state ASSIGN:
 state ASSIGN_WORKSPACE:
   '→'
       ->
+  'output'
+      -> ASSIGN_OUTPUT
   'workspace'
       ->
+  'number'
+      -> ASSIGN_WORKSPACE_NUMBER
   workspace = string
-      -> call cfg_assign($workspace)
+      -> call cfg_assign($workspace, 0)
+
+state ASSIGN_OUTPUT:
+  output = string
+      -> call cfg_assign_output($output)
+
+state ASSIGN_WORKSPACE_NUMBER:
+  number = string
+      -> call cfg_assign($number, 1)
 
 # no_focus <criteria>
 state NO_FOCUS:
@@ -197,6 +211,11 @@ state MOUSE_WARPING:
   value = 'none', 'output'
       -> call cfg_mouse_warping($value)
 
+# focus_wrapping
+state FOCUS_WRAPPING:
+  value = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive', 'force'
+      -> call cfg_focus_wrapping($value)
+
 # force_focus_wrapping
 state FORCE_FOCUS_WRAPPING:
   value = word
@@ -483,12 +502,16 @@ state BAR_WHEEL_DOWN_CMD:
       -> call cfg_bar_wheel_down_cmd($command); BAR
 
 state BAR_BINDSYM:
+  release = '--release'
+      ->
   button = word
       -> BAR_BINDSYM_COMMAND
 
 state BAR_BINDSYM_COMMAND:
+  release = '--release'
+      ->
   command = string
-      -> call cfg_bar_bindsym($button, $command); BAR
+      -> call cfg_bar_bindsym($button, $release, $command); BAR
 
 state BAR_POSITION:
   position = 'top', 'bottom'
index 8f537c0e25d32cccbbb7ab4e48b9b06f98ceefa5..cfb4beae4c6357e0db7e3bb114f9a01bee1665a3 100755 (executable)
@@ -1,9 +1,9 @@
 #!/bin/zsh
 # This script is used to prepare a new release of i3.
 
-export RELEASE_VERSION="4.13"
-export PREVIOUS_VERSION="4.12"
-export RELEASE_BRANCH="next"
+export RELEASE_VERSION="4.14.1"
+export PREVIOUS_VERSION="4.14"
+export RELEASE_BRANCH="master"
 
 if [ ! -e "../i3.github.io" ]
 then
@@ -232,9 +232,9 @@ echo ""
 echo "  cd ${TMPDIR}"
 echo "  sendmail -t < email.txt"
 echo ""
-echo "Update milestones on GitHub:"
-echo "  Set due date of ${RELEASE_VERSION} to $(date +'%Y-$m-%d') and close the milestone"
-echo "  Create milestone for the next version with unset due date"
+echo "Update milestones on GitHub (only for new major versions):"
+echo "  Set due date of ${RELEASE_VERSION} to $(date +'%Y-%m-%d') and close the milestone"
+echo "  Create milestone for the next major version with unset due date"
 echo ""
 echo "Announce on:"
 echo "  twitter"
index 42a2b79b85d279c44a35e858d278812d125d837f..c145b95602dd0b34c34a5db891a17dba30ec0c48 100644 (file)
@@ -910,7 +910,6 @@ static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) {
     int remaining = xcb_get_property_value_length(prop_reply);
     for (int i = 0; i < 5 && remaining > 0; i++) {
         const int len = strnlen(walk, remaining);
-        remaining -= len;
         switch (i) {
             case 0:
                 sasprintf((char **)&(xkb_names->rules), "%.*s", len, walk);
@@ -930,6 +929,7 @@ static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) {
         }
         DLOG("component %d of _XKB_RULES_NAMES is \"%.*s\"\n", i, len, walk);
         walk += (len + 1);
+        remaining -= (len + 1);
     }
 
     free(atom_reply);
index 78af8a0350cd067179f5aaedcf263ad631e11b84..b036c5f8033c29e84b2d91b475b57daff8d64572 100644 (file)
@@ -49,7 +49,7 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
             break;
     }
 
-    bool res = resize_find_tiling_participants(&first, &second, search_direction);
+    bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
     if (!res) {
         LOG("No second container in this direction found.\n");
         return false;
@@ -241,7 +241,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
          * The splitv container will be focused. */
         Con *focused = con->parent;
         focused = TAILQ_FIRST(&(focused->focus_head));
-        con_focus(focused);
+        con_activate(focused);
         /* To prevent scrolling from going outside the container (see ticket
          * #557), we first check if scrolling is possible at all. */
         bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
@@ -256,7 +256,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
     }
 
     /* 2: focus this con. */
-    con_focus(con);
+    con_activate(con);
 
     /* 3: For floating containers, we also want to raise them on click.
      * We will skip handling events on floating cons in fullscreen mode */
index 2697d6e1255f7e1e49b72de6d9bbc3054795af95..899bbb90c8f0854254b4d36b52e0e2a21619e034 100644 (file)
         }                                               \
     } while (0)
 
-/*
- * Returns true if a is definitely greater than b (using the given epsilon)
- *
- */
-static bool definitelyGreaterThan(float a, float b, float epsilon) {
-    return (a - b) > ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
-}
-
 /*
  * Checks whether we switched to a new workspace and returns false in that case,
  * signaling that further workspace switching should be done by the calling function
@@ -269,14 +261,20 @@ void cmd_criteria_add(I3_CMD, const char *ctype, const char *cvalue) {
     match_parse_property(current_match, ctype, cvalue);
 }
 
+static void move_matches_to_workspace(Con *ws) {
+    owindow *current;
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+        con_move_to_workspace(current->con, ws, true, false, false);
+    }
+}
+
 /*
  * Implementation of 'move [window|container] [to] workspace
  * next|prev|next_on_output|prev_on_output|current'.
  *
  */
 void cmd_move_con_to_workspace(I3_CMD, const char *which) {
-    owindow *current;
-
     DLOG("which=%s\n", which);
 
     /* We have nothing to move:
@@ -309,10 +307,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) {
         return;
     }
 
-    TAILQ_FOREACH(current, &owindows, owindows) {
-        DLOG("matching: %p / %s\n", current->con, current->con->name);
-        con_move_to_workspace(current->con, ws, true, false, false);
-    }
+    move_matches_to_workspace(ws);
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
@@ -324,11 +319,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) {
  *
  */
 void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
-    owindow *current;
-    Con *ws;
-
-    ws = workspace_back_and_forth_get();
-
+    Con *ws = workspace_back_and_forth_get();
     if (ws == NULL) {
         yerror("No workspace was previously active.");
         return;
@@ -336,10 +327,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
 
     HANDLE_EMPTY_MATCH;
 
-    TAILQ_FOREACH(current, &owindows, owindows) {
-        DLOG("matching: %p / %s\n", current->con, current->con->name);
-        con_move_to_workspace(current->con, ws, true, false, false);
-    }
+    move_matches_to_workspace(ws);
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
@@ -358,7 +346,6 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_au
     }
 
     const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
-    owindow *current;
 
     /* We have nothing to move:
      *  when criteria was specified but didn't match any window or
@@ -382,10 +369,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_au
 
     HANDLE_EMPTY_MATCH;
 
-    TAILQ_FOREACH(current, &owindows, owindows) {
-        DLOG("matching: %p / %s\n", current->con, current->con->name);
-        con_move_to_workspace(current->con, ws, true, false, false);
-    }
+    move_matches_to_workspace(ws);
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
@@ -398,7 +382,6 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_au
  */
 void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) {
     const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
-    owindow *current;
 
     /* We have nothing to move:
      *  when criteria was specified but didn't match any window or
@@ -412,7 +395,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no
 
     LOG("should move window to workspace %s\n", which);
     /* get the workspace */
-    Con *output, *workspace = NULL;
+    Con *output, *ws = NULL;
 
     long parsed_num = ws_name_to_number(which);
 
@@ -423,22 +406,19 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no
     }
 
     TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
-    GREP_FIRST(workspace, output_get_content(output),
+    GREP_FIRST(ws, output_get_content(output),
                child->num == parsed_num);
 
-    if (!workspace) {
-        workspace = workspace_get(which, NULL);
+    if (!ws) {
+        ws = workspace_get(which, NULL);
     }
 
     if (!no_auto_back_and_forth)
-        workspace = maybe_auto_back_and_forth_workspace(workspace);
+        ws = maybe_auto_back_and_forth_workspace(ws);
 
     HANDLE_EMPTY_MATCH;
 
-    TAILQ_FOREACH(current, &owindows, owindows) {
-        DLOG("matching: %p / %s\n", current->con, current->con->name);
-        con_move_to_workspace(current->con, workspace, true, false, false);
-    }
+    move_matches_to_workspace(ws);
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
@@ -511,7 +491,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c
     else
         search_direction = D_DOWN;
 
-    bool res = resize_find_tiling_participants(&first, &second, search_direction);
+    bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
     if (!res) {
         LOG("No second container in this direction found.\n");
         ysuccess(false);
@@ -525,8 +505,8 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c
     LOG("default percentage = %f\n", percentage);
 
     /* resize */
-    LOG("second->percent = %f\n", second->percent);
     LOG("first->percent before = %f\n", first->percent);
+    LOG("second->percent before = %f\n", second->percent);
     if (first->percent == 0.0)
         first->percent = percentage;
     if (second->percent == 0.0)
@@ -535,12 +515,10 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c
     double new_second_percent = second->percent - ((double)ppt / 100.0);
     LOG("new_first_percent = %f\n", new_first_percent);
     LOG("new_second_percent = %f\n", new_second_percent);
-    /* Ensure that the new percentages are positive and greater than
-     * 0.05 to have a reasonable minimum size. */
-    if (definitelyGreaterThan(new_first_percent, 0.05, DBL_EPSILON) &&
-        definitelyGreaterThan(new_second_percent, 0.05, DBL_EPSILON)) {
-        first->percent += ((double)ppt / 100.0);
-        second->percent -= ((double)ppt / 100.0);
+    /* Ensure that the new percentages are positive. */
+    if (new_first_percent > 0.0 && new_second_percent > 0.0) {
+        first->percent = new_first_percent;
+        second->percent = new_second_percent;
         LOG("first->percent after = %f\n", first->percent);
         LOG("second->percent after = %f\n", second->percent);
     } else {
@@ -552,19 +530,15 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c
 
 static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
     LOG("width/height resize\n");
-    /* get the appropriate current container (skip stacked/tabbed cons) */
-    while (current->parent->layout == L_STACKED ||
-           current->parent->layout == L_TABBED)
-        current = current->parent;
-
-    /* Then further go up until we find one with the matching orientation. */
-    orientation_t search_orientation =
-        (strcmp(direction, "width") == 0 ? HORIZ : VERT);
 
-    while (current->type != CT_WORKSPACE &&
-           current->type != CT_FLOATING_CON &&
-           (con_orientation(current->parent) != search_orientation || con_num_children(current->parent) == 1))
-        current = current->parent;
+    /* get the appropriate current container (skip stacked/tabbed cons) */
+    Con *dummy = NULL;
+    direction_t search_direction = (strcmp(direction, "width") == 0 ? D_LEFT : D_DOWN);
+    bool search_result = resize_find_tiling_participants(&current, &dummy, search_direction, true);
+    if (search_result == false) {
+        ysuccess(false);
+        return false;
+    }
 
     /* get the default percentage */
     int children = con_num_children(current->parent);
@@ -572,24 +546,6 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way
     double percentage = 1.0 / children;
     LOG("default percentage = %f\n", percentage);
 
-    orientation_t orientation = con_orientation(current->parent);
-
-    if ((orientation == HORIZ &&
-         strcmp(direction, "height") == 0) ||
-        (orientation == VERT &&
-         strcmp(direction, "width") == 0)) {
-        LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
-            (orientation == HORIZ ? "horizontal" : "vertical"));
-        ysuccess(false);
-        return false;
-    }
-
-    if (children == 1) {
-        LOG("This is the only container, cannot resize.\n");
-        ysuccess(false);
-        return false;
-    }
-
     /* Ensure all the other children have a percentage set. */
     Con *child;
     TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
@@ -602,24 +558,23 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way
     double subtract_percent = ((double)ppt / 100.0) / (children - 1);
     LOG("new_current_percent = %f\n", new_current_percent);
     LOG("subtract_percent = %f\n", subtract_percent);
-    /* Ensure that the new percentages are positive and greater than
-     * 0.05 to have a reasonable minimum size. */
+    /* Ensure that the new percentages are positive. */
     TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
         if (child == current)
             continue;
-        if (!definitelyGreaterThan(child->percent - subtract_percent, 0.05, DBL_EPSILON)) {
+        if (child->percent - subtract_percent <= 0.0) {
             LOG("Not resizing, already at minimum size (child %p would end up with a size of %.f\n", child, child->percent - subtract_percent);
             ysuccess(false);
             return false;
         }
     }
-    if (!definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON)) {
+    if (new_current_percent <= 0.0) {
         LOG("Not resizing, already at minimum size\n");
         ysuccess(false);
         return false;
     }
 
-    current->percent += ((double)ppt / 100.0);
+    current->percent = new_current_percent;
     LOG("current->percent after = %f\n", current->percent);
 
     TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
@@ -676,31 +631,97 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
 }
 
 /*
- * Implementation of 'resize set <px> [px] <px> [px]'.
+ * Implementation of 'resize set <width> [px | ppt] <height> [px | ppt]'.
  *
  */
-void cmd_resize_set(I3_CMD, long cwidth, long cheight) {
-    DLOG("resizing to %ldx%ld px\n", cwidth, cheight);
-    if (cwidth <= 0 || cheight <= 0) {
-        ELOG("Resize failed: dimensions cannot be negative (was %ldx%ld)\n", cwidth, cheight);
+void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height) {
+    DLOG("resizing to %ld %s x %ld %s\n", cwidth, mode_width, cheight, mode_height);
+    if (cwidth < 0 || cheight < 0) {
+        ELOG("Resize failed: dimensions cannot be negative (was %ld %s x %ld %s)\n", cwidth, mode_width, cheight, mode_height);
         return;
     }
 
     HANDLE_EMPTY_MATCH;
 
     owindow *current;
+    bool success = true;
     TAILQ_FOREACH(current, &owindows, owindows) {
         Con *floating_con;
         if ((floating_con = con_inside_floating(current->con))) {
+            Con *output = con_get_output(floating_con);
+            if (cwidth == 0) {
+                cwidth = output->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;
+            } else if (mode_height && strcmp(mode_height, "ppt") == 0) {
+                cheight = output->rect.height * ((double)cheight / 100.0);
+            }
             floating_resize(floating_con, cwidth, cheight);
         } else {
-            ELOG("Resize failed: %p not a floating container\n", current->con);
+            if (current->con->window && current->con->window->dock) {
+                DLOG("This is a dock window. Not resizing (con = %p)\n)", current->con);
+                continue;
+            }
+
+            if (cwidth > 0 && mode_width && strcmp(mode_width, "ppt") == 0) {
+                /* get the appropriate current container (skip stacked/tabbed cons) */
+                Con *target = current->con;
+                Con *dummy;
+                resize_find_tiling_participants(&target, &dummy, D_LEFT, true);
+
+                /* Calculate new size for the target container */
+                double current_percent = target->percent;
+                char *action_string;
+                long adjustment;
+
+                if (current_percent > cwidth) {
+                    action_string = "shrink";
+                    adjustment = (int)(current_percent * 100) - cwidth;
+                } else {
+                    action_string = "grow";
+                    adjustment = cwidth - (int)(current_percent * 100);
+                }
+
+                /* perform resizing and report failure if not possible */
+                if (!cmd_resize_tiling_width_height(current_match, cmd_output,
+                                                    target, action_string, "width", adjustment)) {
+                    success = false;
+                }
+            }
+
+            if (cheight > 0 && mode_width && strcmp(mode_width, "ppt") == 0) {
+                /* get the appropriate current container (skip stacked/tabbed cons) */
+                Con *target = current->con;
+                Con *dummy;
+                resize_find_tiling_participants(&target, &dummy, D_DOWN, true);
+
+                /* Calculate new size for the target container */
+                double current_percent = target->percent;
+                char *action_string;
+                long adjustment;
+
+                if (current_percent > cheight) {
+                    action_string = "shrink";
+                    adjustment = (int)(current_percent * 100) - cheight;
+                } else {
+                    action_string = "grow";
+                    adjustment = cheight - (int)(current_percent * 100);
+                }
+
+                /* perform resizing and report failure if not possible */
+                if (!cmd_resize_tiling_width_height(current_match, cmd_output,
+                                                    target, action_string, "height", adjustment)) {
+                    success = false;
+                }
+            }
         }
     }
 
     cmd_output->needs_tree_render = true;
-    // XXX: default reply for now, make this a better reply
-    ysuccess(true);
+    ysuccess(success);
 }
 
 /*
@@ -760,6 +781,7 @@ void cmd_nop(I3_CMD, const char *comment) {
     LOG("-------------------------------------------------\n");
     LOG("  NOP: %s\n", comment);
     LOG("-------------------------------------------------\n");
+    ysuccess(true);
 }
 
 /*
@@ -1044,25 +1066,7 @@ void cmd_move_con_to_output(I3_CMD, const char *name) {
     TAILQ_FOREACH(current, &owindows, owindows) {
         DLOG("matching: %p / %s\n", current->con, current->con->name);
 
-        Output *current_output = get_output_for_con(current->con);
-        assert(current_output != NULL);
-
-        Output *output = get_output_from_string(current_output, name);
-        if (output == NULL) {
-            ELOG("Could not find output \"%s\", skipping.\n", name);
-            had_error = true;
-            continue;
-        }
-
-        Con *ws = NULL;
-        GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
-        if (ws == NULL) {
-            ELOG("Could not find a visible workspace on output %p.\n", output);
-            had_error = true;
-            continue;
-        }
-
-        con_move_to_workspace(current->con, ws, true, false, false);
+        had_error |= !con_move_to_output_name(current->con, name, true);
     }
 
     cmd_output->needs_tree_render = true;
@@ -1132,6 +1136,10 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) {
     owindow *current;
     TAILQ_FOREACH(current, &owindows, owindows) {
         Con *ws = con_get_workspace(current->con);
+        if (con_is_internal(ws)) {
+            continue;
+        }
+
         bool success = workspace_move_to_output(ws, name);
         if (!success) {
             ELOG("Failed to move workspace to output.\n");
@@ -1257,6 +1265,20 @@ void cmd_focus_direction(I3_CMD, const char *direction) {
     ysuccess(true);
 }
 
+/*
+ * Focus a container and disable any other fullscreen container not permitting the focus.
+ *
+ */
+static void cmd_focus_force_focus(Con *con) {
+    /* Disable fullscreen container in workspace with container to be focused. */
+    Con *ws = con_get_workspace(con);
+    Con *fullscreen_on_ws = (focused && focused->fullscreen_mode == CF_GLOBAL) ? focused : con_get_fullscreen_con(ws, CF_OUTPUT);
+    if (fullscreen_on_ws && fullscreen_on_ws != con && !con_has_parent(con, fullscreen_on_ws)) {
+        con_disable_fullscreen(fullscreen_on_ws);
+    }
+    con_activate(con);
+}
+
 /*
  * Implementation of 'focus tiling|floating|mode_toggle'.
  *
@@ -1264,28 +1286,34 @@ void cmd_focus_direction(I3_CMD, const char *direction) {
 void cmd_focus_window_mode(I3_CMD, const char *window_mode) {
     DLOG("window_mode = %s\n", window_mode);
 
+    bool to_floating = false;
+    if (strcmp(window_mode, "mode_toggle") == 0) {
+        to_floating = !con_inside_floating(focused);
+    } else if (strcmp(window_mode, "floating") == 0) {
+        to_floating = true;
+    } else if (strcmp(window_mode, "tiling") == 0) {
+        to_floating = false;
+    }
+
     Con *ws = con_get_workspace(focused);
-    if (ws != NULL) {
-        if (strcmp(window_mode, "mode_toggle") == 0) {
-            if (con_inside_floating(focused))
-                window_mode = "tiling";
-            else
-                window_mode = "floating";
-        }
-        Con *current;
-        TAILQ_FOREACH(current, &(ws->focus_head), focused) {
-            if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) ||
-                (strcmp(window_mode, "tiling") == 0 && current->type == CT_FLOATING_CON))
-                continue;
+    Con *current;
+    bool success = false;
+    TAILQ_FOREACH(current, &(ws->focus_head), focused) {
+        if ((to_floating && current->type != CT_FLOATING_CON) ||
+            (!to_floating && current->type == CT_FLOATING_CON))
+            continue;
 
-            con_focus(con_descend_focused(current));
-            break;
-        }
+        cmd_focus_force_focus(con_descend_focused(current));
+        success = true;
+        break;
     }
 
-    cmd_output->needs_tree_render = true;
-    // XXX: default reply for now, make this a better reply
-    ysuccess(true);
+    if (success) {
+        cmd_output->needs_tree_render = true;
+        ysuccess(true);
+    } else {
+        yerror("Failed to find a %s container in workspace.", to_floating ? "floating" : "tiling");
+    }
 }
 
 /*
@@ -1342,13 +1370,6 @@ void cmd_focus(I3_CMD) {
         if (!ws)
             continue;
 
-        /* Check the fullscreen focus constraints. */
-        if (!con_fullscreen_permits_focusing(current->con)) {
-            LOG("Cannot change focus while in fullscreen mode (fullscreen rules).\n");
-            ysuccess(false);
-            return;
-        }
-
         /* In case this is a scratchpad window, call scratchpad_show(). */
         if (ws == __i3_scratch) {
             scratchpad_show(current->con);
@@ -1372,13 +1393,13 @@ void cmd_focus(I3_CMD) {
          * So we focus 'current' to make it the currently focused window of
          * the target workspace, then revert focus. */
         Con *currently_focused = focused;
-        con_focus(current->con);
-        con_focus(currently_focused);
+        cmd_focus_force_focus(current->con);
+        con_activate(currently_focused);
 
         /* Now switch to the workspace, then focus */
         workspace_show(ws);
         LOG("focusing %p / %s\n", current->con, current->con->name);
-        con_focus(current->con);
+        con_activate(current->con);
         count++;
     }
 
@@ -1490,7 +1511,7 @@ void cmd_move_direction(I3_CMD, const char *direction, long move_px) {
 
     /* the move command should not disturb focus */
     if (focused != initially_focused)
-        con_focus(initially_focused);
+        con_activate(initially_focused);
 
     // XXX: default reply for now, make this a better reply
     ysuccess(true);
@@ -1615,7 +1636,7 @@ void cmd_open(I3_CMD) {
     LOG("opening new container\n");
     Con *con = tree_open_con(NULL, NULL);
     con->layout = L_SPLITH;
-    con_focus(con);
+    con_activate(con);
 
     y(map_open);
     ystr("success");
@@ -2004,7 +2025,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
     }
 
     /* Restore the previous focus since con_attach messes with the focus. */
-    con_focus(previously_focused);
+    con_activate(previously_focused);
 
     cmd_output->needs_tree_render = true;
     ysuccess(true);
index fcde4c100c750bbcece6e7c3a9ddec398eb3c704..4d0c43e3562133e59920bf359b177fca974e90c3 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -252,6 +252,27 @@ void con_focus(Con *con) {
     }
 }
 
+/*
+ * Raise container to the top if it is floating or inside some floating
+ * container.
+ *
+ */
+static void con_raise(Con *con) {
+    Con *floating = con_inside_floating(con);
+    if (floating) {
+        floating_raise_con(floating);
+    }
+}
+
+/*
+ * Sets input focus to the given container and raises it to the top.
+ *
+ */
+void con_activate(Con *con) {
+    con_focus(con);
+    con_raise(con);
+}
+
 /*
  * Closes the given container.
  *
@@ -795,6 +816,62 @@ Con *con_for_window(Con *con, i3Window *window, Match **store_match) {
     return NULL;
 }
 
+static int num_focus_heads(Con *con) {
+    int focus_heads = 0;
+
+    Con *current;
+    TAILQ_FOREACH(current, &(con->focus_head), focused) {
+        focus_heads++;
+    }
+
+    return focus_heads;
+}
+
+/*
+ * Iterate over the container's focus stack and return an array with the
+ * containers inside it, ordered from higher focus order to lowest.
+ *
+ */
+Con **get_focus_order(Con *con) {
+    const int focus_heads = num_focus_heads(con);
+    Con **focus_order = smalloc(focus_heads * sizeof(Con *));
+    Con *current;
+    int idx = 0;
+    TAILQ_FOREACH(current, &(con->focus_head), focused) {
+        assert(idx < focus_heads);
+        focus_order[idx++] = current;
+    }
+
+    return focus_order;
+}
+
+/*
+ * Clear the container's focus stack and re-add it using the provided container
+ * array. The function doesn't check if the provided array contains the same
+ * containers with the previous focus stack but will not add floating containers
+ * in the new focus stack if container is not a workspace.
+ *
+ */
+void set_focus_order(Con *con, Con **focus_order) {
+    int focus_heads = 0;
+    while (!TAILQ_EMPTY(&(con->focus_head))) {
+        Con *current = TAILQ_FIRST(&(con->focus_head));
+
+        TAILQ_REMOVE(&(con->focus_head), current, focused);
+        focus_heads++;
+    }
+
+    for (int idx = 0; idx < focus_heads; idx++) {
+        /* Useful when encapsulating a workspace. */
+        if (con->type != CT_WORKSPACE && con_inside_floating(focus_order[idx])) {
+            focus_heads++;
+            continue;
+        }
+
+        TAILQ_INSERT_TAIL(&(con->focus_head), focus_order[idx], focused);
+    }
+}
+
 /*
  * Returns the number of children of this container.
  *
@@ -994,9 +1071,9 @@ void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode) {
     Con *old_focused = focused;
     if (fullscreen_mode == CF_GLOBAL && cur_ws != con_ws)
         workspace_show(con_ws);
-    con_focus(con);
+    con_activate(con);
     if (fullscreen_mode != CF_GLOBAL && cur_ws != con_ws)
-        con_focus(old_focused);
+        con_activate(old_focused);
 
     con_set_fullscreen_mode(con, fullscreen_mode);
 }
@@ -1148,11 +1225,11 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
          * new workspace is hidden and it's necessary to immediately switch
          * back to the originally-focused workspace. */
         Con *old_focus = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head));
-        con_focus(con_descend_focused(con));
+        con_activate(con_descend_focused(con));
 
         /* Restore focus if the output's focused workspace has changed. */
         if (con_get_workspace(focused) != old_focus)
-            con_focus(old_focus);
+            con_activate(old_focus);
     }
 
     /* 7: when moving to another workspace, we leave the focus on the current
@@ -1172,7 +1249,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
     /* Set focus only if con was on current workspace before moving.
      * Otherwise we would give focus to some window on different workspace. */
     if (!ignore_focus && source_ws == current_ws)
-        con_focus(con_descend_focused(focus_next));
+        con_activate(con_descend_focused(focus_next));
 
     /* 8. If anything within the container is associated with a startup sequence,
      * delete it so child windows won't be created on the old workspace. */
@@ -1300,12 +1377,33 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
  * visible workspace on the given output.
  *
  */
-void con_move_to_output(Con *con, Output *output) {
+void con_move_to_output(Con *con, Output *output, bool fix_coordinates) {
     Con *ws = NULL;
     GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
     assert(ws != NULL);
     DLOG("Moving con %p to output %s\n", con, output_primary_name(output));
-    con_move_to_workspace(con, ws, false, false, false);
+    con_move_to_workspace(con, ws, fix_coordinates, false, false);
+}
+
+/*
+ * Moves the given container to the currently focused container on the
+ * visible workspace on the output specified by the given name.
+ * The current output for the container is used to resolve relative names
+ * such as left, right, up, down.
+ *
+ */
+bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates) {
+    Output *current_output = get_output_for_con(con);
+    assert(current_output != NULL);
+
+    Output *output = get_output_from_string(current_output, name);
+    if (output == NULL) {
+        ELOG("Could not find output \"%s\"\n", name);
+        return false;
+    }
+
+    con_move_to_output(con, output, fix_coordinates);
+    return true;
 }
 
 /*
@@ -1735,7 +1833,7 @@ void con_set_layout(Con *con, layout_t layout) {
             con->workspace_layout = ws_layout;
             DLOG("Setting layout to %d\n", layout);
             con->layout = layout;
-        } else if (layout == L_STACKED || layout == L_TABBED) {
+        } else if (layout == L_STACKED || layout == L_TABBED || layout == L_SPLITV || layout == L_SPLITH) {
             DLOG("Creating new split container\n");
             /* 1: create a new split container */
             Con *new = con_new(NULL, NULL);
@@ -1746,17 +1844,9 @@ void con_set_layout(Con *con, layout_t layout) {
             new->layout = layout;
             new->last_split_layout = con->last_split_layout;
 
-            /* Save the container that was focused before we move containers
-             * around, but only if the container is visible (otherwise focus
-             * will be restored properly automatically when switching). */
-            Con *old_focused = TAILQ_FIRST(&(con->focus_head));
-            if (old_focused == TAILQ_END(&(con->focus_head)))
-                old_focused = NULL;
-            if (old_focused != NULL &&
-                !workspace_is_visible(con_get_workspace(old_focused)))
-                old_focused = NULL;
-
             /* 3: move the existing cons of this workspace below the new con */
+            Con **focus_order = get_focus_order(con);
+
             DLOG("Moving cons\n");
             Con *child;
             while (!TAILQ_EMPTY(&(con->nodes_head))) {
@@ -1765,13 +1855,13 @@ void con_set_layout(Con *con, layout_t layout) {
                 con_attach(child, new, true);
             }
 
+            set_focus_order(new, focus_order);
+            free(focus_order);
+
             /* 4: attach the new split container to the workspace */
             DLOG("Attaching new split to ws\n");
             con_attach(new, con, false);
 
-            if (old_focused)
-                con_focus(old_focused);
-
             tree_flatten(croot);
         }
         con_force_split_parents_redraw(con);
@@ -1827,6 +1917,10 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
                  * change to the opposite split layout. */
                 if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) {
                     layout = parent->last_split_layout;
+                    /* In case last_split_layout was not initialized… */
+                    if (layout == L_DEFAULT) {
+                        layout = L_SPLITH;
+                    }
                 } else {
                     layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH;
                 }
@@ -2045,14 +2139,7 @@ bool con_fullscreen_permits_focusing(Con *con) {
 
     /* Allow it only if the container to be focused is contained within the
      * current fullscreen container. */
-    do {
-        if (con->parent == fs)
-            return true;
-        con = con->parent;
-    } while (con);
-
-    /* Focusing con would hide it behind a fullscreen window, disallow it. */
-    return false;
+    return con_has_parent(con, fs);
 }
 
 /*
@@ -2294,15 +2381,14 @@ bool con_swap(Con *first, Con *second) {
     Con *current_ws = con_get_workspace(old_focus);
     const bool focused_within_first = (first == old_focus || con_has_parent(old_focus, first));
     const bool focused_within_second = (second == old_focus || con_has_parent(old_focus, second));
+    fullscreen_mode_t first_fullscreen_mode = first->fullscreen_mode;
+    fullscreen_mode_t second_fullscreen_mode = second->fullscreen_mode;
 
-    if (!con_fullscreen_permits_focusing(first_ws)) {
-        DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", first_ws->name);
-        return false;
+    if (first_fullscreen_mode != CF_NONE) {
+        con_disable_fullscreen(first);
     }
-
-    if (!con_fullscreen_permits_focusing(second_ws)) {
-        DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", second_ws->name);
-        return false;
+    if (second_fullscreen_mode != CF_NONE) {
+        con_disable_fullscreen(second);
     }
 
     double first_percent = first->percent;
@@ -2341,7 +2427,7 @@ bool con_swap(Con *first, Con *second) {
      * We don't need to check this for the second container because we've only
      * moved the first one at this point.*/
     if (first_ws != second_ws && focused_within_first) {
-        con_focus(con_descend_focused(current_ws));
+        con_activate(con_descend_focused(current_ws));
     }
 
     /* Move second to where first has been originally. */
@@ -2384,15 +2470,15 @@ bool con_swap(Con *first, Con *second) {
      */
     if (focused_within_first) {
         if (first_ws == second_ws) {
-            con_focus(old_focus);
+            con_activate(old_focus);
         } else {
-            con_focus(con_descend_focused(second));
+            con_activate(con_descend_focused(second));
         }
     } else if (focused_within_second) {
         if (first_ws == second_ws) {
-            con_focus(old_focus);
+            con_activate(old_focus);
         } else {
-            con_focus(con_descend_focused(first));
+            con_activate(con_descend_focused(first));
         }
     }
 
@@ -2403,7 +2489,17 @@ bool con_swap(Con *first, Con *second) {
     second->percent = first_percent;
     fake->percent = 0.0;
 
+    SWAP(first_fullscreen_mode, second_fullscreen_mode, fullscreen_mode_t);
+
 swap_end:
+    /* The two windows exchange their original fullscreen status */
+    if (first_fullscreen_mode != CF_NONE) {
+        con_enable_fullscreen(first, first_fullscreen_mode);
+    }
+    if (second_fullscreen_mode != CF_NONE) {
+        con_enable_fullscreen(second, second_fullscreen_mode);
+    }
+
     /* We don't actually need this since percentages-wise we haven't changed
      * anything, but we'll better be safe than sorry and just make sure as we'd
      * otherwise crash i3. */
index 7e08b5208702ef64d06af2e047e302f3b0714aee..24c7b541e447e4f80ab2b676c4d3f0b140bdff6c 100644 (file)
@@ -227,6 +227,8 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
     if (config.workspace_urgency_timer == 0)
         config.workspace_urgency_timer = 0.5;
 
+    config.focus_wrapping = FOCUS_WRAPPING_ON;
+
     parse_configuration(override_configpath, true);
 
     if (reload) {
index 7ca6e102785a674daa3741e5f884fd68ebc958ab..ad6d65b5fe368c11b52925ac22cf58ee582c973c 100644 (file)
@@ -197,7 +197,7 @@ CFGFUN(workspace_layout, const char *layout) {
         config.default_layout = L_TABBED;
 }
 
-CFGFUN(new_window, const char *windowtype, const char *border, const long width) {
+CFGFUN(default_border, const char *windowtype, const char *border, const long width) {
     int border_style;
     int border_width;
 
@@ -215,7 +215,8 @@ CFGFUN(new_window, const char *windowtype, const char *border, const long width)
         border_width = width;
     }
 
-    if (strcmp(windowtype, "new_window") == 0) {
+    if ((strcmp(windowtype, "default_border") == 0) ||
+        (strcmp(windowtype, "new_window") == 0)) {
         DLOG("default tiled border style = %d and border width = %d (%d physical px)\n",
              border_style, border_width, logical_px(border_width));
         config.default_border = border_style;
@@ -264,8 +265,27 @@ CFGFUN(disable_randr15, const char *value) {
     config.disable_randr15 = eval_boolstr(value);
 }
 
+CFGFUN(focus_wrapping, const char *value) {
+    if (strcmp(value, "force") == 0) {
+        config.focus_wrapping = FOCUS_WRAPPING_FORCE;
+    } else if (eval_boolstr(value)) {
+        config.focus_wrapping = FOCUS_WRAPPING_ON;
+    } else {
+        config.focus_wrapping = FOCUS_WRAPPING_OFF;
+    }
+}
+
 CFGFUN(force_focus_wrapping, const char *value) {
-    config.force_focus_wrapping = eval_boolstr(value);
+    /* Legacy syntax. */
+    if (eval_boolstr(value)) {
+        config.focus_wrapping = FOCUS_WRAPPING_FORCE;
+    } else {
+        /* For "force_focus_wrapping off", don't enable or disable
+         * focus wrapping, just ensure it's not forced. */
+        if (config.focus_wrapping == FOCUS_WRAPPING_FORCE) {
+            config.focus_wrapping = FOCUS_WRAPPING_ON;
+        }
+    }
 }
 
 CFGFUN(workspace_back_and_forth, const char *value) {
@@ -377,15 +397,35 @@ CFGFUN(color, const char *colorclass, const char *border, const char *background
 #undef APPLY_COLORS
 }
 
-CFGFUN(assign, const char *workspace) {
+CFGFUN(assign_output, const char *output) {
     if (match_is_empty(current_match)) {
         ELOG("Match is empty, ignoring this assignment\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);
+    assignment->type = A_TO_OUTPUT;
+    assignment->dest.output = sstrdup(output);
+    TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+}
+
+CFGFUN(assign, const char *workspace, bool is_number) {
+    if (match_is_empty(current_match)) {
+        ELOG("Match is empty, ignoring this assignment\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;
+    }
+
     DLOG("New assignment, using above criteria, to workspace \"%s\".\n", workspace);
     Assignment *assignment = scalloc(1, sizeof(Assignment));
     match_copy(&(assignment->match), current_match);
-    assignment->type = A_TO_WORKSPACE;
+    assignment->type = is_number ? A_TO_WORKSPACE_NUMBER : A_TO_WORKSPACE;
     assignment->dest.workspace = sstrdup(workspace);
     TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
 }
@@ -463,7 +503,7 @@ CFGFUN(bar_modifier, const char *modifier) {
         current_bar->modifier = M_NONE;
 }
 
-static void bar_configure_binding(const char *button, const char *command) {
+static void bar_configure_binding(const char *button, const char *release, const char *command) {
     if (strncasecmp(button, "button", strlen("button")) != 0) {
         ELOG("Bindings for a bar can only be mouse bindings, not \"%s\", ignoring.\n", button);
         return;
@@ -474,16 +514,18 @@ static void bar_configure_binding(const char *button, const char *command) {
         ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button);
         return;
     }
+    const bool release_bool = release != NULL;
 
     struct Barbinding *current;
     TAILQ_FOREACH(current, &(current_bar->bar_bindings), bindings) {
-        if (current->input_code == input_code) {
+        if (current->input_code == input_code && current->release == release_bool) {
             ELOG("command for button %s was already specified, ignoring.\n", button);
             return;
         }
     }
 
     struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding));
+    new_binding->release = release_bool;
     new_binding->input_code = input_code;
     new_binding->command = sstrdup(command);
     TAILQ_INSERT_TAIL(&(current_bar->bar_bindings), new_binding, bindings);
@@ -491,16 +533,16 @@ static void bar_configure_binding(const char *button, const char *command) {
 
 CFGFUN(bar_wheel_up_cmd, const char *command) {
     ELOG("'wheel_up_cmd' is deprecated. Please us 'bindsym button4 %s' instead.\n", command);
-    bar_configure_binding("button4", command);
+    bar_configure_binding("button4", NULL, command);
 }
 
 CFGFUN(bar_wheel_down_cmd, const char *command) {
     ELOG("'wheel_down_cmd' is deprecated. Please us 'bindsym button5 %s' instead.\n", command);
-    bar_configure_binding("button5", command);
+    bar_configure_binding("button5", NULL, command);
 }
 
-CFGFUN(bar_bindsym, const char *button, const char *command) {
-    bar_configure_binding(button, command);
+CFGFUN(bar_bindsym, const char *button, const char *release, const char *command) {
+    bar_configure_binding(button, release, command);
 }
 
 CFGFUN(bar_position, const char *position) {
index 58a5552ceb27d77d7f267004cf457a636c902fe1..2d3f3bb919eb2d134baa61f9e510880275fd8210 100644 (file)
@@ -743,7 +743,7 @@ static char *migrate_config(char *input, off_t size) {
 
     /* read the script’s output */
     int conv_size = 65535;
-    char *converted = smalloc(conv_size);
+    char *converted = scalloc(conv_size, 1);
     int read_bytes = 0, ret;
     do {
         if (read_bytes == conv_size) {
@@ -764,6 +764,7 @@ static char *migrate_config(char *input, off_t size) {
     wait(&status);
     if (!WIFEXITED(status)) {
         fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
+        FREE(converted);
         return NULL;
     }
 
@@ -778,6 +779,7 @@ static char *migrate_config(char *input, off_t size) {
             fprintf(stderr, "# i3 config file (v4)\n");
             /* TODO: nag the user with a message to include a hint for i3 in their config file */
         }
+        FREE(converted);
         return NULL;
     }
 
@@ -900,7 +902,9 @@ bool parse_file(const char *f, bool use_nagbar) {
 
     FREE(current_config);
     current_config = scalloc(stbuf.st_size + 1, 1);
-    fread(current_config, 1, stbuf.st_size, fstr);
+    if ((ssize_t)fread(current_config, 1, stbuf.st_size, fstr) != stbuf.st_size) {
+        die("Could not fread: %s\n", strerror(errno));
+    }
     rewind(fstr);
 
     bool invalid_sets = false;
@@ -1061,7 +1065,7 @@ bool parse_file(const char *f, bool use_nagbar) {
     int version = detect_version(buf);
     if (version == 3) {
         /* We need to convert this v3 configuration */
-        char *converted = migrate_config(new, stbuf.st_size);
+        char *converted = migrate_config(new, strlen(new));
         if (converted != NULL) {
             ELOG("\n");
             ELOG("****************************************************************\n");
index 764ee753177f1875a5de4fdd65827a19fadad985..2e05cafa89cc75a7f65fdfe2c6c9467e049736c2 100644 (file)
@@ -55,10 +55,6 @@ static yajl_callbacks version_callbacks = {
  *
  */
 void display_running_version(void) {
-    char *socket_path = root_atom_contents("I3_SOCKET_PATH", conn, conn_screen);
-    if (socket_path == NULL)
-        exit(EXIT_SUCCESS);
-
     char *pid_from_atom = root_atom_contents("I3_PID", conn, conn_screen);
     if (pid_from_atom == NULL) {
         /* If I3_PID is not set, the running version is older than 4.2-200. */
@@ -71,18 +67,7 @@ void display_running_version(void) {
     printf("(Getting version from running i3, press ctrl-c to abort…)");
     fflush(stdout);
 
-    /* TODO: refactor this with the code for sending commands */
-    int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
-    if (sockfd == -1)
-        err(EXIT_FAILURE, "Could not create socket");
-
-    struct sockaddr_un addr;
-    memset(&addr, 0, sizeof(struct sockaddr_un));
-    addr.sun_family = AF_LOCAL;
-    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
-    if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
-        err(EXIT_FAILURE, "Could not connect to i3");
-
+    int sockfd = ipc_connect(NULL);
     if (ipc_send_message(sockfd, 0, I3_IPC_MESSAGE_TYPE_GET_VERSION,
                          (uint8_t *)"") == -1)
         err(EXIT_FAILURE, "IPC: write()");
@@ -184,5 +169,4 @@ void display_running_version(void) {
     yajl_free(handle);
     free(reply);
     free(pid_from_atom);
-    free(socket_path);
 }
index 5f46dcf9755f9fb8850db07492954620c3850954..e958153d6a647ab5242188d932f28e1b3ae19e98 100644 (file)
@@ -318,7 +318,7 @@ void floating_enable(Con *con, bool automatic) {
     render_con(con, false);
 
     if (set_focus)
-        con_focus(con);
+        con_activate(con);
 
     /* Check if we need to re-assign it to a different workspace because of its
      * coordinates and exit if that was done successfully. */
@@ -382,7 +382,7 @@ void floating_disable(Con *con, bool automatic) {
     con_fix_percent(con->parent);
 
     if (set_focus)
-        con_focus(con);
+        con_activate(con);
 
     floating_set_hint_atom(con, false);
     ipc_send_window_event("floating", con);
@@ -449,7 +449,8 @@ bool floating_maybe_reassign_ws(Con *con) {
     Con *ws = TAILQ_FIRST(&(content->focus_head));
     DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name);
     con_move_to_workspace(con, ws, false, true, false);
-    con_focus(con_descend_focused(con));
+    workspace_show(ws);
+    con_activate(con_descend_focused(con));
     return true;
 }
 
@@ -667,7 +668,7 @@ void floating_resize_window(Con *con, const bool proportional,
 
 /* Custom data structure used to track dragging-related events. */
 struct drag_x11_cb {
-    ev_check check;
+    ev_prepare prepare;
 
     /* Whether this modal event loop should be exited and with which result. */
     drag_result_t result;
@@ -686,7 +687,7 @@ struct drag_x11_cb {
     const void *extra;
 };
 
-static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
+static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
     struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
     xcb_motion_notify_event_t *last_motion_notify = NULL;
     xcb_generic_event_t *event;
@@ -746,8 +747,10 @@ static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
         if (last_motion_notify != (xcb_motion_notify_event_t *)event)
             free(event);
 
-        if (dragloop->result != DRAGGING)
+        if (dragloop->result != DRAGGING) {
+            free(last_motion_notify);
             return;
+        }
     }
 
     if (last_motion_notify == NULL)
@@ -765,6 +768,8 @@ static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
             dragloop->extra);
     }
     free(last_motion_notify);
+
+    xcb_flush(conn);
 }
 
 /*
@@ -831,18 +836,18 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
         .callback = callback,
         .extra = extra,
     };
-    ev_check *check = &loop.check;
+    ev_prepare *prepare = &loop.prepare;
     if (con)
         loop.old_rect = con->rect;
-    ev_check_init(check, xcb_drag_check_cb);
-    check->data = &loop;
+    ev_prepare_init(prepare, xcb_drag_prepare_cb);
+    prepare->data = &loop;
     main_set_x11_cb(false);
-    ev_check_start(main_loop, check);
+    ev_prepare_start(main_loop, prepare);
 
     while (loop.result == DRAGGING)
         ev_run(main_loop, EVRUN_ONCE);
 
-    ev_check_stop(main_loop, check);
+    ev_prepare_stop(main_loop, prepare);
     main_set_x11_cb(true);
 
     xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
index 3140e4057674ca7596719ece8df9f5fbba70b19a..e1671c3b4bec2df918fdf70d89d38a29ee77c0d1 100644 (file)
@@ -433,7 +433,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
         if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
             DLOG("Focusing con = %p\n", con);
             workspace_show(ws);
-            con_focus(con);
+            con_activate(con);
             tree_render();
         } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
             DLOG("Marking con = %p urgent\n", con);
@@ -774,7 +774,9 @@ static void handle_client_message(xcb_client_message_event_t *event) {
                 scratchpad_show(con);
             } else {
                 workspace_show(ws);
-                con_focus(con);
+                /* Re-set focus, even if unchanged from i3’s perspective. */
+                focused_id = XCB_NONE;
+                con_activate(con);
             }
         } else {
             /* Request is from an application. */
@@ -786,7 +788,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
                 DLOG("Focusing con = %p\n", con);
                 workspace_show(ws);
-                con_focus(con);
+                con_activate(con);
             } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
                 DLOG("Marking con = %p urgent\n", con);
                 con_set_urgency(con, true);
@@ -1243,7 +1245,7 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
     if (ws != con_get_workspace(focused))
         workspace_show(ws);
 
-    con_focus(con);
+    con_activate(con);
     /* We update focused_id because we don’t need to set focus again */
     focused_id = event->event;
     tree_render();
@@ -1262,6 +1264,9 @@ static void handle_configure_notify(xcb_configure_notify_event_t *event) {
     }
     DLOG("ConfigureNotify for root window 0x%08x\n", event->event);
 
+    if (force_xinerama) {
+        return;
+    }
     randr_query_outputs();
 }
 
index 759665fe91b76341e74128807ce830c39e0ef63e..a1a72b1ac8ba620136fd9f8d3cced898465d69f0 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -572,6 +572,8 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) {
         y(integer, current->input_code);
         ystr("command");
         ystr(current->command);
+        ystr("release");
+        y(bool, current->release == B_UPON_KEYRELEASE);
 
         y(map_close);
     }
@@ -1046,8 +1048,9 @@ static int add_subscription(void *extra, const unsigned char *s,
     memcpy(client->events[event], s, len);
 
     DLOG("client is now subscribed to:\n");
-    for (int i = 0; i < client->num_events; i++)
+    for (int i = 0; i < client->num_events; i++) {
         DLOG("event %s\n", client->events[i]);
+    }
     DLOG("(done)\n");
 
     return 1;
@@ -1099,6 +1102,25 @@ IPC_HANDLER(subscribe) {
     yajl_free(p);
     const char *reply = "{\"success\":true}";
     ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
+
+    if (client->first_tick_sent) {
+        return;
+    }
+
+    bool is_tick = false;
+    for (int i = 0; i < client->num_events; i++) {
+        if (strcmp(client->events[i], "tick") == 0) {
+            is_tick = true;
+            break;
+        }
+    }
+    if (!is_tick) {
+        return;
+    }
+
+    client->first_tick_sent = true;
+    const char *payload = "{\"first\":true,\"payload\":\"\"}";
+    ipc_send_message(client->fd, strlen(payload), I3_IPC_EVENT_TICK, (const uint8_t *)payload);
 }
 
 /*
@@ -1122,9 +1144,35 @@ IPC_HANDLER(get_config) {
     y(free);
 }
 
+/*
+ * Sends the tick event from the message payload to subscribers. Establishes a
+ * synchronization point in event-related tests.
+ */
+IPC_HANDLER(send_tick) {
+    yajl_gen gen = ygenalloc();
+
+    y(map_open);
+
+    ystr("payload");
+    yajl_gen_string(gen, (unsigned char *)message, message_size);
+
+    y(map_close);
+
+    const unsigned char *payload;
+    ylength length;
+    y(get_buf, &payload, &length);
+
+    ipc_send_event("tick", I3_IPC_EVENT_TICK, (const char *)payload);
+    y(free);
+
+    const char *reply = "{\"success\":true}";
+    ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_TICK, (const uint8_t *)reply);
+    DLOG("Sent tick event\n");
+}
+
 /* The index of each callback function corresponds to the numeric
  * value of the message type (see include/i3/ipc.h) */
-handler_t handlers[10] = {
+handler_t handlers[11] = {
     handle_run_command,
     handle_get_workspaces,
     handle_subscribe,
@@ -1135,6 +1183,7 @@ handler_t handlers[10] = {
     handle_get_version,
     handle_get_binding_modes,
     handle_get_config,
+    handle_send_tick,
 };
 
 /*
index 071b3ccd650a1833fbf4efdb4d7d91fe4649dff2..aa7ac03c452f20c66505215602be441cc61ae3bd 100644 (file)
@@ -654,6 +654,6 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
     yajl_free(hand);
 
     if (to_focus) {
-        con_focus(to_focus);
+        con_activate(to_focus);
     }
 }
index 0d1457fdc1e18ca443fe652292f7343c9948b91f..194ef05c337efc76898d780eee8148f5fc9c9ca0 100644 (file)
@@ -35,9 +35,9 @@ struct rlimit original_rlimit_core;
 /** The number of file descriptors passed via socket activation. */
 int listen_fds;
 
-/* We keep the xcb_check watcher around to be able to enable and disable it
+/* We keep the xcb_prepare watcher around to be able to enable and disable it
  * temporarily for drag_pointer(). */
-static struct ev_check *xcb_check;
+static struct ev_prepare *xcb_prepare;
 
 extern Con *focused;
 
@@ -92,29 +92,26 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment
 bool xcursor_supported = true;
 bool xkb_supported = true;
 
+bool force_xinerama = false;
+
 /*
- * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
+ * This callback is only a dummy, see xcb_prepare_cb.
  * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
  *
  */
 static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
-    /* empty, because xcb_prepare_cb and xcb_check_cb are used */
+    /* empty, because xcb_prepare_cb are used */
 }
 
 /*
- * Flush before blocking (and waiting for new events)
+ * Called just before the event loop sleeps. Ensures xcb’s incoming and outgoing
+ * queues are empty so that any activity will trigger another event loop
+ * iteration, and hence another xcb_prepare_cb invocation.
  *
  */
 static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
-    xcb_flush(conn);
-}
-
-/*
- * Instead of polling the X connection socket we leave this to
- * xcb_poll_for_event() which knows better than we can ever know.
- *
- */
-static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
+    /* Process all queued (and possibly new) events before the event loop
+       sleeps. */
     xcb_generic_event_t *event;
 
     while ((event = xcb_poll_for_event(conn)) != NULL) {
@@ -137,6 +134,9 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
 
         free(event);
     }
+
+    /* Flush all queued events to X11. */
+    xcb_flush(conn);
 }
 
 /*
@@ -148,12 +148,12 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
 void main_set_x11_cb(bool enable) {
     DLOG("Setting main X11 callback to enabled=%d\n", enable);
     if (enable) {
-        ev_check_start(main_loop, xcb_check);
+        ev_prepare_start(main_loop, xcb_prepare);
         /* Trigger the watcher explicitly to handle all remaining X11 events.
          * drag_pointer()’s event handler exits in the middle of the loop. */
-        ev_feed_event(main_loop, xcb_check, 0);
+        ev_feed_event(main_loop, xcb_prepare, 0);
     } else {
-        ev_check_stop(main_loop, xcb_check);
+        ev_prepare_stop(main_loop, xcb_prepare);
     }
 }
 
@@ -174,21 +174,64 @@ static void i3_exit(void) {
         fflush(stderr);
         shm_unlink(shmlogname);
     }
+    ipc_shutdown(SHUTDOWN_REASON_EXIT);
+    unlink(config.ipc_socket_path);
 }
 
 /*
- * (One-shot) Handler for all signals with default action "Term", see signal(7)
+ * (One-shot) Handler for all signals with default action "Core", see signal(7)
  *
  * Unlinks the SHM log and re-raises the signal.
  *
  */
-static void handle_signal(int sig, siginfo_t *info, void *data) {
+static void handle_core_signal(int sig, siginfo_t *info, void *data) {
     if (*shmlogname != '\0') {
         shm_unlink(shmlogname);
     }
     raise(sig);
 }
 
+/*
+ * (One-shot) Handler for all signals with default action "Term", see signal(7)
+ *
+ * Exits the program gracefully.
+ *
+ */
+static void handle_term_signal(struct ev_loop *loop, ev_signal *signal, int revents) {
+    /* We exit gracefully here in the sense that cleanup handlers
+     * installed via atexit are invoked. */
+    exit(128 + signal->signum);
+}
+
+/*
+ * Set up handlers for all signals with default action "Term", see signal(7)
+ *
+ */
+static void setup_term_handlers(void) {
+    static struct ev_signal signal_watchers[6];
+    size_t num_watchers = sizeof(signal_watchers) / sizeof(signal_watchers[0]);
+
+    /* We have to rely on libev functionality here and should not use
+     * sigaction handlers because we need to invoke the exit handlers
+     * and cannot do so from an asynchronous signal handling context as
+     * not all code triggered during exit is signal safe (and exiting
+     * the main loop from said handler is not easily possible). libev's
+     * signal handlers does not impose such a constraint on us. */
+    ev_signal_init(&signal_watchers[0], handle_term_signal, SIGHUP);
+    ev_signal_init(&signal_watchers[1], handle_term_signal, SIGINT);
+    ev_signal_init(&signal_watchers[2], handle_term_signal, SIGALRM);
+    ev_signal_init(&signal_watchers[3], handle_term_signal, SIGTERM);
+    ev_signal_init(&signal_watchers[4], handle_term_signal, SIGUSR1);
+    ev_signal_init(&signal_watchers[5], handle_term_signal, SIGUSR1);
+    for (size_t i = 0; i < num_watchers; i++) {
+        ev_signal_start(main_loop, &signal_watchers[i]);
+        /* The signal handlers should not block ev_run from returning
+         * and so none of the signal handlers should hold a reference to
+         * the main loop. */
+        ev_unref(main_loop);
+    }
+}
+
 int main(int argc, char *argv[]) {
     /* Keep a symbol pointing to the I3_VERSION string constant so that we have
      * it in gdb backtraces. */
@@ -197,7 +240,6 @@ int main(int argc, char *argv[]) {
     bool autostart = true;
     char *layout_path = NULL;
     bool delete_layout_path = false;
-    bool force_xinerama = false;
     bool disable_randr15 = false;
     char *fake_outputs = NULL;
     bool disable_signalhandler = false;
@@ -550,6 +592,10 @@ int main(int argc, char *argv[]) {
             config.ipc_socket_path = sstrdup(config.ipc_socket_path);
     }
 
+    if (config.force_xinerama) {
+        force_xinerama = true;
+    }
+
     xcb_void_cookie_t cookie;
     cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK});
     xcb_generic_error_t *error = xcb_request_check(conn, cookie);
@@ -668,7 +714,7 @@ int main(int argc, char *argv[]) {
         fake_outputs_init(fake_outputs);
         FREE(fake_outputs);
         config.fake_outputs = NULL;
-    } else if (force_xinerama || config.force_xinerama) {
+    } else if (force_xinerama) {
         /* Force Xinerama (for drivers which don't support RandR yet, esp. the
          * nVidia binary graphics driver), when specified either in the config
          * file or on command-line */
@@ -720,7 +766,7 @@ int main(int argc, char *argv[]) {
             output = get_first_output();
         }
 
-        con_focus(con_descend_focused(output_get_content(output->con)));
+        con_activate(con_descend_focused(output_get_content(output->con)));
         free(pointerreply);
     }
 
@@ -776,15 +822,11 @@ int main(int argc, char *argv[]) {
     ewmh_update_desktop_viewport();
 
     struct ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io));
-    xcb_check = scalloc(1, sizeof(struct ev_check));
-    struct ev_prepare *xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
+    xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
 
     ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
     ev_io_start(main_loop, xcb_watcher);
 
-    ev_check_init(xcb_check, xcb_check_cb);
-    ev_check_start(main_loop, xcb_check);
-
     ev_prepare_init(xcb_prepare, xcb_prepare_cb);
     ev_prepare_start(main_loop, xcb_prepare);
 
@@ -854,15 +896,15 @@ int main(int argc, char *argv[]) {
         err(EXIT_FAILURE, "pledge");
 #endif
 
-    struct sigaction action;
-
-    action.sa_sigaction = handle_signal;
-    action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
-    sigemptyset(&action.sa_mask);
-
     if (!disable_signalhandler)
         setup_signal_handler();
     else {
+        struct sigaction action;
+
+        action.sa_sigaction = handle_core_signal;
+        action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
+        sigemptyset(&action.sa_mask);
+
         /* Catch all signals with default action "Core", see signal(7) */
         if (sigaction(SIGQUIT, &action, NULL) == -1 ||
             sigaction(SIGILL, &action, NULL) == -1 ||
@@ -872,14 +914,7 @@ int main(int argc, char *argv[]) {
             ELOG("Could not setup signal handler.\n");
     }
 
-    /* Catch all signals with default action "Term", see signal(7) */
-    if (sigaction(SIGHUP, &action, NULL) == -1 ||
-        sigaction(SIGINT, &action, NULL) == -1 ||
-        sigaction(SIGALRM, &action, NULL) == -1 ||
-        sigaction(SIGUSR1, &action, NULL) == -1 ||
-        sigaction(SIGUSR2, &action, NULL) == -1)
-        ELOG("Could not setup signal handler.\n");
-
+    setup_term_handlers();
     /* Ignore SIGPIPE to survive errors when an IPC client disconnects
      * while we are sending them a message */
     signal(SIGPIPE, SIG_IGN);
@@ -922,7 +957,7 @@ int main(int argc, char *argv[]) {
         free(command);
     }
 
-    /* Make sure to destroy the event loop to invoke the cleeanup callbacks
+    /* Make sure to destroy the event loop to invoke the cleanup callbacks
      * when calling exit() */
     atexit(i3_exit);
 
index 004e8038b8de10718337342f62921ad420b59671..8b306052c7ea77243e457ccf97bccbd25ac0f23c 100644 (file)
@@ -259,9 +259,26 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
         Con *wm_desktop_ws = NULL;
 
         /* If not, check if it is assigned to a specific workspace */
-        if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE))) {
+        if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE)) ||
+            (assignment = assignment_for(cwindow, A_TO_WORKSPACE_NUMBER))) {
             DLOG("Assignment matches (%p)\n", match);
-            Con *assigned_ws = workspace_get(assignment->dest.workspace, NULL);
+
+            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);
+                }
+            }
+            /* A_TO_WORKSPACE type assignment or fallback from A_TO_WORKSPACE_NUMBER
+             * when the target workspace number does not exist yet. */
+            if (!assigned_ws) {
+                assigned_ws = workspace_get(assignment->dest.workspace, NULL);
+            }
+
             nc = con_descend_tiling_focused(assigned_ws);
             DLOG("focused on ws %s: %p / %s\n", assigned_ws->name, nc, nc->name);
             if (nc->type == CT_WORKSPACE)
@@ -305,6 +322,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
             } else
                 nc = tree_open_con(NULL, cwindow);
         }
+
+        if ((assignment = assignment_for(cwindow, A_TO_OUTPUT))) {
+            con_move_to_output_name(nc, assignment->dest.output, true);
+        }
     } else {
         /* M_BELOW inserts the new window as a child of the one which was
          * matched (e.g. dock areas) */
@@ -367,7 +388,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
              * needed e.g. for LibreOffice Impress multi-monitor
              * presentations to work out of the box. */
             if (output != NULL)
-                con_move_to_output(nc, output);
+                con_move_to_output(nc, output, false);
             con_toggle_fullscreen(nc, CF_OUTPUT);
         }
         fs = NULL;
@@ -625,7 +646,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
      * proper window event sequence. */
     if (set_focus && nc->mapped) {
         DLOG("Now setting focus.\n");
-        con_focus(nc);
+        con_activate(nc);
     }
 
     tree_render();
index 3ecc69e4c762c7c63e2e38ab6a9ceadaeb91edf0..97ca6d40b42423a07582d44f3ade0f9001d7ef7f 100644 (file)
@@ -118,7 +118,7 @@ static void move_to_output_directed(Con *con, direction_t direction) {
     attach_to_workspace(con, ws, direction);
 
     /* fix the focus stack */
-    con_focus(con);
+    con_activate(con);
 
     /* force re-painting the indicators */
     FREE(con->deco_render_params);
index e76903844821c532f14cca9b132beeb18ea3af93..c76dfd035d213360ef727663b3e98ff49fba6db4 100644 (file)
@@ -99,7 +99,8 @@ void output_push_sticky_windows(Con *to_focus) {
                     continue;
 
                 if (con_is_sticky(current)) {
-                    con_move_to_workspace(current, visible_ws, true, false, current != to_focus->parent);
+                    bool ignore_focus = (to_focus == NULL) || (current != to_focus->parent);
+                    con_move_to_workspace(current, visible_ws, true, false, ignore_focus);
                 }
             }
         }
index 1d3330145dfb7fe62c1d3db041fa9514614214cd..85add08fa41ea2cb64a60fc194a3fe65d02a1550 100644 (file)
@@ -496,7 +496,7 @@ void init_ws_for_output(Output *output, Con *content) {
     Con *ws = create_workspace_on_output(output, content);
 
     /* TODO: Set focus in main.c */
-    con_focus(ws);
+    con_activate(ws);
 }
 
 /*
@@ -924,7 +924,7 @@ void randr_query_outputs(void) {
             continue;
 
         DLOG("Focusing primary output %s\n", output_primary_name(output));
-        con_focus(con_descend_focused(output->con));
+        con_activate(con_descend_focused(output->con));
     }
 
     /* render_layout flushes */
@@ -987,7 +987,7 @@ void randr_disable_output(Output *output) {
 
         if (next) {
             DLOG("now focusing next = %p\n", next);
-            con_focus(next);
+            con_activate(next);
             workspace_show(con_get_workspace(next));
         }
 
index f07fcec6a169fb9e478d48d8161a9d3ac3bc2ea0..ee50bfbc694514abe8b43c51a5cffed65365fa69 100644 (file)
@@ -47,7 +47,7 @@ DRAGGING_CB(resize_callback) {
     xcb_flush(conn);
 }
 
-bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction) {
+bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides) {
     DLOG("Find two participants for resizing container=%p in direction=%i\n", other, direction);
     Con *first = *current;
     Con *second = NULL;
@@ -74,8 +74,14 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
         /* get the counterpart for this resizement */
         if (dir_backwards) {
             second = TAILQ_PREV(first, nodes_head, nodes);
+            if (second == NULL && both_sides == true) {
+                second = TAILQ_NEXT(first, nodes);
+            }
         } else {
             second = TAILQ_NEXT(first, nodes);
+            if (second == NULL && both_sides == true) {
+                second = TAILQ_PREV(first, nodes_head, nodes);
+            }
         }
 
         if (second == NULL) {
index bf16c864c04be4a28d757071cff9edc3db4f5afb..b99a50c165ebbd922379f996527c8a57e00e3dc5 100644 (file)
@@ -39,7 +39,6 @@ static TAILQ_HEAD(state_head, placeholder_state) state_head =
 static xcb_connection_t *restore_conn;
 
 static struct ev_io *xcb_watcher;
-static struct ev_check *xcb_check;
 static struct ev_prepare *xcb_prepare;
 
 static void restore_handle_event(int type, xcb_generic_event_t *event);
@@ -49,10 +48,6 @@ static void restore_xcb_got_event(EV_P_ struct ev_io *w, int revents) {
 }
 
 static void restore_xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
-    xcb_flush(restore_conn);
-}
-
-static void restore_xcb_check_cb(EV_P_ ev_check *w, int revents) {
     xcb_generic_event_t *event;
 
     if (xcb_connection_has_error(restore_conn)) {
@@ -77,6 +72,8 @@ static void restore_xcb_check_cb(EV_P_ ev_check *w, int revents) {
 
         free(event);
     }
+
+    xcb_flush(restore_conn);
 }
 
 /*
@@ -91,7 +88,6 @@ void restore_connect(void) {
         /* This is not the initial connect, but a reconnect, most likely
          * because our X11 connection was killed (e.g. by a user with xkill. */
         ev_io_stop(main_loop, xcb_watcher);
-        ev_check_stop(main_loop, xcb_check);
         ev_prepare_stop(main_loop, xcb_prepare);
 
         placeholder_state *state;
@@ -107,7 +103,6 @@ void restore_connect(void) {
          */
         xcb_disconnect(restore_conn);
         free(xcb_watcher);
-        free(xcb_check);
         free(xcb_prepare);
     }
 
@@ -124,15 +119,11 @@ void restore_connect(void) {
     }
 
     xcb_watcher = scalloc(1, sizeof(struct ev_io));
-    xcb_check = scalloc(1, sizeof(struct ev_check));
     xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
 
     ev_io_init(xcb_watcher, restore_xcb_got_event, xcb_get_file_descriptor(restore_conn), EV_READ);
     ev_io_start(main_loop, xcb_watcher);
 
-    ev_check_init(xcb_check, restore_xcb_check_cb);
-    ev_check_start(main_loop, xcb_check);
-
     ev_prepare_init(xcb_prepare, restore_xcb_prepare_cb);
     ev_prepare_start(main_loop, xcb_prepare);
 }
index 9018ad3ffc233c0037b000eb40837f58cf8a3f41..95154014c729a655a40b4ab9be49dd42f815eedf 100644 (file)
@@ -123,7 +123,7 @@ void scratchpad_show(Con *con) {
             /* use con_descend_tiling_focused to get the last focused
                  * window inside this scratch container in order to
                  * keep the focus the same within this container */
-            con_focus(con_descend_tiling_focused(walk_con));
+            con_activate(con_descend_tiling_focused(walk_con));
             return;
         }
     }
@@ -205,7 +205,7 @@ void scratchpad_show(Con *con) {
         workspace_show(active);
     }
 
-    con_focus(con_descend_focused(con));
+    con_activate(con_descend_focused(con));
 }
 
 /*
index d5f457dd24dc268921ff89acf8c2dd76499035cb..6c6a614e22d46479381e74447fd12f55dd378bb1 100644 (file)
@@ -330,6 +330,13 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
         DLOG("parent container killed\n");
     }
 
+    if (ws == con) {
+        DLOG("Closing a workspace container, updating EWMH atoms\n");
+        ewmh_update_number_of_desktops();
+        ewmh_update_desktop_names();
+        ewmh_update_wm_desktop();
+    }
+
     con_free(con);
 
     /* in the case of floating windows, we already focused another container
@@ -344,12 +351,12 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
             DLOG("focusing %p / %s\n", next, next->name);
             if (next->type == CT_DOCKAREA) {
                 /* Instead of focusing the dockarea, we need to restore focus to the workspace */
-                con_focus(con_descend_focused(output_get_content(next->parent)));
+                con_activate(con_descend_focused(output_get_content(next->parent)));
             } else {
                 if (!force_set_focus && con != focused)
                     DLOG("not changing focus, the container was not focused before\n");
                 else
-                    con_focus(next);
+                    con_activate(next);
             }
         } else {
             DLOG("not focusing because we're not killing anybody\n");
@@ -433,7 +440,7 @@ bool level_up(void) {
     /* Skip over floating containers and go directly to the grandparent
      * (which should always be a workspace) */
     if (focused->parent->type == CT_FLOATING_CON) {
-        con_focus(focused->parent->parent);
+        con_activate(focused->parent->parent);
         return true;
     }
 
@@ -444,7 +451,7 @@ bool level_up(void) {
         ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n");
         return false;
     }
-    con_focus(focused->parent);
+    con_activate(focused->parent);
     return true;
 }
 
@@ -469,7 +476,7 @@ bool level_down(void) {
             next = TAILQ_FIRST(&(next->focus_head));
     }
 
-    con_focus(next);
+    con_activate(next);
     return true;
 }
 
@@ -560,26 +567,14 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
         if (!workspace)
             return false;
 
-        workspace_show(workspace);
-
-        /* If a workspace has an active fullscreen container, one of its
-         * children should always be focused. The above workspace_show()
-         * should be adequate for that, so return. */
-        if (con_get_fullscreen_con(workspace, CF_OUTPUT))
-            return true;
-
-        Con *focus = con_descend_direction(workspace, direction);
-
-        /* special case: if there was no tiling con to focus and the workspace
-         * has a floating con in the focus stack, focus the top of the focus
-         * stack (which may be floating) */
-        if (focus == workspace)
+        Con *focus = con_descend_tiling_focused(workspace);
+        if (focus == workspace) {
             focus = con_descend_focused(workspace);
-
-        if (focus) {
-            con_focus(focus);
-            x_set_warp_to(&(focus->rect));
         }
+
+        workspace_show(workspace);
+        con_activate(focus);
+        x_set_warp_to(&(focus->rect));
         return true;
     }
 
@@ -616,7 +611,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
             TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows);
         }
 
-        con_focus(con_descend_focused(next));
+        con_activate(con_descend_focused(next));
         return true;
     }
 
@@ -641,7 +636,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
         next = TAILQ_PREV(current, nodes_head, nodes);
 
     if (!next) {
-        if (!config.force_focus_wrapping) {
+        if (config.focus_wrapping != FOCUS_WRAPPING_FORCE) {
             /* If there is no next/previous container, we check if we can focus one
              * when going higher (without wrapping, though). If so, we are done, if
              * not, we wrap */
@@ -665,7 +660,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
     /* 3: focus choice comes in here. at the moment we will go down
      * until we find a window */
     /* TODO: check for window, atm we only go down as far as possible */
-    con_focus(con_descend_focused(next));
+    con_activate(con_descend_focused(next));
     return true;
 }
 
@@ -675,7 +670,8 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
  *
  */
 void tree_next(char way, orientation_t orientation) {
-    _tree_next(focused, way, orientation, true);
+    _tree_next(focused, way, orientation,
+               config.focus_wrapping != FOCUS_WRAPPING_OFF);
 }
 
 /*
index ba0969c729674aabe3dd04a0ce9614803514f4f7..dc3444f7b6aa950be8b4696896ac55b1cc4033fc 100644 (file)
@@ -500,7 +500,7 @@ ssize_t slurp(const char *path, char **buf) {
     size_t n = fread(*buf, 1, stbuf.st_size, f);
     fclose(f);
     if ((ssize_t)n != stbuf.st_size) {
-        ELOG("File \"%s\" could not be read entirely: got %zd, want %zd\n", path, n, stbuf.st_size);
+        ELOG("File \"%s\" could not be read entirely: got %zd, want %" PRIi64 "\n", path, n, (int64_t)stbuf.st_size);
         free(*buf);
         *buf = NULL;
         return -1;
index 4b350b822730938ea4f0d6f73ef1b34b9d6e2909..8c46a94917b9f2f3dbc18848e8154d157a99fa06 100644 (file)
@@ -459,6 +459,11 @@ static void _workspace_show(Con *workspace) {
 
             y(free);
 
+            /* Avoid calling output_push_sticky_windows later with a freed container. */
+            if (old == old_focus) {
+                old_focus = NULL;
+            }
+
             ewmh_update_number_of_desktops();
             ewmh_update_desktop_names();
             ewmh_update_desktop_viewport();
@@ -810,9 +815,9 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
     /* 2: copy layout from workspace */
     split->layout = ws->layout;
 
-    Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
-
     /* 3: move the existing cons of this workspace below the new con */
+    Con **focus_order = get_focus_order(ws);
+
     DLOG("Moving cons\n");
     while (!TAILQ_EMPTY(&(ws->nodes_head))) {
         Con *child = TAILQ_FIRST(&(ws->nodes_head));
@@ -820,6 +825,9 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
         con_attach(child, split, true);
     }
 
+    set_focus_order(split, focus_order);
+    free(focus_order);
+
     /* 4: switch workspace layout */
     ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
     DLOG("split->layout = %d, ws->layout = %d\n", split->layout, ws->layout);
@@ -830,9 +838,6 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
 
     /* 6: fix the percentages */
     con_fix_percent(ws);
-
-    if (old_focused)
-        con_focus(old_focused);
 }
 
 /*
@@ -887,9 +892,10 @@ Con *workspace_encapsulate(Con *ws) {
     new->parent = ws;
     new->layout = ws->layout;
 
+    Con **focus_order = get_focus_order(ws);
+
     DLOG("Moving children of workspace %p / %s into container %p\n",
          ws, ws->name, new);
-
     Con *child;
     while (!TAILQ_EMPTY(&(ws->nodes_head))) {
         child = TAILQ_FIRST(&(ws->nodes_head));
@@ -897,6 +903,9 @@ Con *workspace_encapsulate(Con *ws) {
         con_attach(child, new, true);
     }
 
+    set_focus_order(new, focus_order);
+    free(focus_order);
+
     con_attach(new, ws, true);
 
     return new;
diff --git a/src/x.c b/src/x.c
index 09a604931926aa4915d8e388177f849bfdbe16c5..7829079bb0363f8514e5b1abe6513434c0db809a 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -1227,9 +1227,13 @@ void x_set_name(Con *con, const char *name) {
  *
  */
 void update_shmlog_atom() {
-    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
-                        A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
-                        strlen(shmlogname), shmlogname);
+    if (*shmlogname == '\0') {
+        xcb_delete_property(conn, root, A_I3_SHMLOG_PATH);
+    } else {
+        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
+                            A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
+                            strlen(shmlogname), shmlogname);
+    }
 }
 
 /*
index ddd6ccadf992596d1b02672028fe239c4c42fd3b..96b93bed085a07fd4d4d49832d00ab26daee10f3 100755 (executable)
@@ -38,6 +38,8 @@ binmode STDERR, ':utf8';
 # subshell or situations like that.
 AnyEvent::Util::close_all_fds_except(0, 1, 2);
 
+our @CLEANUP;
+
 # convenience wrapper to write to the log file
 my $log;
 sub Log { say $log "@_" }
@@ -55,6 +57,7 @@ my %options = (
     xtrace => 0,
     coverage => 0,
     restart => 0,
+    xvfb => 1,
 );
 my $keep_xserver_output = 0;
 
@@ -64,6 +67,7 @@ my $result = GetOptions(
     "valgrind" => \$options{valgrind},
     "strace" => \$options{strace},
     "xtrace" => \$options{xtrace},
+    "xvfb!" => \$options{xvfb},
     "display=s" => \@displays,
     "parallel=i" => \$parallel,
     "help|?" => \$help,
@@ -112,6 +116,44 @@ $ENV{PATH} = join(':',
 qx(Xephyr -help 2>&1);
 die "Xephyr was not found in your path. Please install Xephyr (xserver-xephyr on Debian)." if $?;
 
+qx(xvfb-run --help 2>&1);
+if ($? && $options{xvfb}) {
+    say "xvfb-run not found, not running tests under xvfb. Install the xvfb package to speed up tests";
+    $options{xvfb} = 0;
+}
+
+if ($options{xvfb}) {
+    for (my $n = 99; $n < 120; $n++) {
+       my $path = File::Temp::tmpnam($ENV{TMPDIR} // "/tmp", "i3-testsXXXXXX");
+       if (!defined(POSIX::mkfifo($path, 0600))) {
+           die "mkfifo: $!";
+       }
+       my $pid = fork // die "fork: $!";
+       if ($pid == 0) {
+           # Child
+
+           # Xvfb checks whether the parent ignores USR1 and sends USR1 to the
+           # parent when ready, so that the wait call will be interrupted.  We
+           # can’t implement this in Perl, as Perl’s waitpid transparently
+           # handles -EINTR.
+           exec('/bin/sh', '-c', qq|trap "exit" INT; trap : USR1; (trap '' USR1; exec Xvfb :$n -screen 0 640x480x8 -nolisten tcp) & PID=\$!; wait; if ! kill -0 \$PID 2>/dev/null; then echo 1:\$PID > $path; else echo 0:\$PID > $path; wait \$PID; fi|);
+           die "exec: $!";
+       }
+       chomp(my $kill = slurp($path));
+       unlink($path);
+       my ($code, $xvfbpid) = ($kill =~ m,^([0-1]):(.*)$,);
+       next unless $code eq '0';
+
+       $ENV{DISPLAY} = ":$n";
+       say "Running tests under Xvfb display $ENV{DISPLAY}";
+
+       push(@CLEANUP, sub {
+           kill(15, $xvfbpid);
+       });
+       last;
+    }
+}
+
 @displays = split(/,/, join(',', @displays));
 @displays = map { s/ //g; $_ } @displays;
 
@@ -379,7 +421,7 @@ sub take_job {
 
 sub cleanup {
     my $exitcode = $?;
-    $_->() for our @CLEANUP;
+    $_->() for @CLEANUP;
     exit $exitcode;
 }
 
@@ -443,6 +485,12 @@ C<latest/strace-for-$test.log>.
 Runs i3 under xtrace to trace X11 requests/replies. The output will be
 available in C<latest/xtrace-for-$test.log>.
 
+=item B<--xvfb>
+
+=item B<--no-xvfb>
+
+Enable or disable running tests under Xvfb. Enabled by default.
+
 =item B<--coverage-testing>
 
 Generates a test coverage report at C<latest/i3-coverage>. Exits i3 cleanly
index 6cccfa762d1a09b12a9704711d5c25fbcdadcc9b..5506d67e78b1bf748edd692a9a2f7e7f5450a4fe 100644 (file)
@@ -23,6 +23,7 @@
 #include <sys/resource.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 #include <libgen.h>
 
 static void uds_connection_cb(EV_P_ ev_io *w, int revents);
index 434ca238f6dfc36370a92b5c2f265e68405ee950..4c5bd6f54c74fc9358e6c7cbc4878de09ef3f2df 100644 (file)
@@ -87,7 +87,7 @@ sub start_xserver {
 
     # First get the last used display number, then increment it by one.
     # Effectively falls back to 1 if no X server is running.
-    my ($displaynum) = map { /(\d+)$/ } reverse sort glob($x_socketpath . '*');
+    my ($displaynum) = reverse sort { $a <=> $b } map{ /(\d+)$/ } glob($x_socketpath . '*');
     $displaynum++;
 
     say "Starting $parallel Xephyr instances, starting at :$displaynum...";
@@ -105,7 +105,7 @@ sub start_xserver {
     for (1 .. $parallel) {
         my $socket = fork_xserver($keep_xserver_output, $displaynum,
                 'Xephyr', ":$displaynum", '-screen', '1280x800',
-                '-nolisten', 'tcp');
+                '-nolisten', 'tcp', '-name', "i3test");
         push(@displays, ":$displaynum");
         push(@sockets_waiting, $socket);
         $displaynum++;
index a484c91a441fe318cbecfe5f3286e2c9e094ba76..e754c0c17f6bf3b8b5b92e72233ebbe05ab8198a 100644 (file)
@@ -12,6 +12,7 @@ use AnyEvent::I3;
 use List::Util qw(first);
 use Time::HiRes qw(sleep);
 use Cwd qw(abs_path);
+use POSIX ':sys_wait_h';
 use Scalar::Util qw(blessed);
 use SocketActivation;
 use i3test::Util qw(slurp);
@@ -37,6 +38,7 @@ our @EXPORT = qw(
     cmd
     sync_with_i3
     exit_gracefully
+    exit_forcefully
     workspace_exists
     focused_ws
     get_socket_path
@@ -47,6 +49,8 @@ our @EXPORT = qw(
     wait_for_unmap
     $x
     kill_all_windows
+    events_for
+    listen_for_binding
 );
 
 =head1 NAME
@@ -121,7 +125,7 @@ END {
 
     } else {
         kill(-9, $i3_pid)
-            or $tester->BAIL_OUT("could not kill i3");
+            or $tester->BAIL_OUT("could not kill i3: $!");
 
         waitpid $i3_pid, 0;
     }
@@ -131,6 +135,22 @@ sub import {
     my ($class, %args) = @_;
     my $pkg = caller;
 
+    $x ||= i3test::X11->new;
+    # set the pointer to a predictable position in case a previous test has
+    # disturbed it
+    $x->warp_pointer(
+       0, # src_window (None)
+       $x->get_root_window(), # dst_window (None)
+       0, # src_x
+       0, # src_y
+       0, # src_width
+       0, # src_height
+       0, # dst_x
+       0); # dst_y
+    # Synchronize with X11 to ensure the pointer has been warped before i3
+    # starts up.
+    $x->get_input_focus_reply($x->get_input_focus()->{sequence});
+
     $i3_autostart = delete($args{i3_autostart}) // 1;
     my $i3_config = delete($args{i3_config}) // '-default';
 
@@ -153,10 +173,6 @@ __
     strict->import;
     warnings->import;
 
-    $x ||= i3test::X11->new;
-    # set the pointer to a predictable position in case a previous test has
-    # disturbed it
-    $x->root->warp_pointer(0, 0);
     $cv->recv if $i3_autostart;
 
     @_ = ($class);
@@ -179,29 +195,11 @@ received, etc.
 sub wait_for_event {
     my ($timeout, $cb) = @_;
 
-    my $cv = AE::cv;
-
     $x->flush;
 
-    # unfortunately, there is no constant for this
-    my $ae_read = 0;
-
-    my $guard = AE::io $x->get_file_descriptor, $ae_read, sub {
-        while (defined(my $event = $x->poll_for_event)) {
-            if ($cb->($event)) {
-                $cv->send(1);
-                last;
-            }
-        }
-    };
-
-    # Trigger timeout after $timeout seconds (can be fractional)
-    my $t = AE::timer $timeout, 0, sub { warn "timeout ($timeout secs)"; $cv->send(0) };
-
-    my $result = $cv->recv;
-    undef $t;
-    undef $guard;
-    return $result;
+    while (defined(my $event = $x->wait_for_event)) {
+       return 1 if $cb->($event);
+    }
 }
 
 =head2 wait_for_map($window)
@@ -348,6 +346,12 @@ sub open_window {
 
     $window->map;
     wait_for_map($window);
+
+    # MapWindow is sent before i3 even starts rendering: the window is placed at
+    # temporary off-screen coordinates first, and x_push_changes() sends further
+    # X11 requests to set focus etc. Hence, we sync with i3 before continuing.
+    sync_with_i3();
+
     return $window;
 }
 
@@ -686,6 +690,7 @@ sub sync_with_i3 {
         $_sync_window = open_window(
             rect => [ -15, -15, 10, 10 ],
             override_redirect => 1,
+            dont_map => 1,
         );
     }
 
@@ -756,7 +761,7 @@ sub exit_gracefully {
 
     if (!$exited) {
         kill(9, $pid)
-            or $tester->BAIL_OUT("could not kill i3");
+            or $tester->BAIL_OUT("could not kill i3: $!");
     }
 
     if ($socketpath =~ m,^/tmp/i3-test-socket-,) {
@@ -767,6 +772,47 @@ sub exit_gracefully {
     undef $i3_pid;
 }
 
+=head2 exit_forcefully($pid, [ $signal ])
+
+Tries to exit i3 forcefully by sending a signal (defaults to SIGTERM).
+
+You only need to use this function if you want to test signal handling
+(in which case you must have launched i3 on your own with
+C<launch_with_config>).
+
+  use i3test i3_autostart => 0;
+  my $pid = launch_with_config($config);
+  # …
+  exit_forcefully($pid);
+
+=cut
+sub exit_forcefully {
+    my ($pid, $signal) = @_;
+    $signal ||= 'TERM';
+
+    # Send the given signal to the i3 instance and wait for up to 10s
+    # for it to terminate.
+    kill($signal, $pid)
+        or $tester->BAIL_OUT("could not kill i3: $!");
+    my $status;
+    my $timeout = 10;
+    do {
+        $status = waitpid $pid, WNOHANG;
+
+        if ($status <= 0) {
+            sleep(1);
+            $timeout--;
+        }
+    } while ($status <= 0 && $timeout > 0);
+
+    if ($status <= 0) {
+        kill('KILL', $pid)
+            or $tester->BAIL_OUT("could not kill i3: $!");
+        waitpid $pid, 0;
+    }
+    undef $i3_pid;
+}
+
 =head2 get_socket_path([ $cache ])
 
 Gets the socket path from the C<I3_SOCKET_PATH> atom stored on the X11 root
@@ -900,6 +946,86 @@ sub kill_all_windows {
     cmd '[title=".*"] kill';
 }
 
+=head2 events_for($subscribecb, [ $rettype ], [ $eventcbs ])
+
+Helper function which returns an array containing all events of type $rettype
+which were generated by i3 while $subscribecb was running.
+
+Set $eventcbs to subscribe to multiple event types and/or perform your own event
+aggregation.
+
+=cut
+sub events_for {
+    my ($subscribecb, $rettype, $eventcbs) = @_;
+
+    my @events;
+    $eventcbs //= {};
+    if (defined($rettype)) {
+       $eventcbs->{$rettype} = sub { push @events, shift };
+    }
+    my $subscribed = AnyEvent->condvar;
+    my $flushed = AnyEvent->condvar;
+    $eventcbs->{tick} = sub {
+       my ($event) = @_;
+       if ($event->{first}) {
+           $subscribed->send($event);
+       } else {
+           $flushed->send($event);
+       }
+    };
+    my $i3 = i3(get_socket_path(0));
+    $i3->connect->recv;
+    $i3->subscribe($eventcbs)->recv;
+    $subscribed->recv;
+    # Subscription established, run the callback.
+    $subscribecb->();
+    # Now generate a tick event, which we know we’ll receive (and at which point
+    # all other events have been received).
+    my $nonce = int(rand(255)) + 1;
+    $i3->send_tick($nonce);
+
+    my $tick = $flushed->recv;
+    $tester->is_eq($tick->{payload}, $nonce, 'tick nonce received');
+    return @events;
+}
+
+=head2 listen_for_binding($cb)
+
+Helper function to evaluate whether sending KeyPress/KeyRelease events via XTEST
+triggers an i3 key binding or not. Expects key bindings to be configured in the
+form “bindsym <binding> nop <binding>”, e.g.  “bindsym Mod4+Return nop
+Mod4+Return”.
+
+  is(listen_for_binding(
+      sub {
+          xtest_key_press(133); # Super_L
+          xtest_key_press(36); # Return
+          xtest_key_release(36); # Return
+          xtest_key_release(133); # Super_L
+          xtest_sync_with_i3;
+      },
+      ),
+     'Mod4+Return',
+     'triggered the "Mod4+Return" keybinding');
+
+=cut
+
+sub listen_for_binding {
+    my ($cb) = @_;
+    my $triggered = AnyEvent->condvar;
+    my @events = events_for(
+       $cb,
+       'binding');
+
+    $tester->is_eq(scalar @events, 1, 'Received precisely one event');
+    $tester->is_eq($events[0]->{change}, 'run', 'change is "run"');
+    # We look at the command (which is “nop <binding>”) because that is easier
+    # than re-assembling the string representation of $event->{binding}.
+    my $command = $events[0]->{binding}->{command};
+    $command =~ s/^nop //g;
+    return $command;
+}
+
 =head1 AUTHOR
 
 Michael Stapelberg <michael@i3wm.org>
index 0253bc2d5dfb912e6eab5f45fa1c7fba4a4ae625..552ae8b48367a6edaaa94095246f2f31d5ee8199 100644 (file)
@@ -5,6 +5,7 @@ use base 'Test::Builder::Module';
 
 our @EXPORT = qw(
     is_num_children
+    is_num_fullscreen
     cmp_float
     does_i3_live
 );
@@ -59,6 +60,25 @@ sub is_num_children {
     $tb->is_num($got_num_children, $num_children, $name);
 }
 
+=head2 is_num_fullscreen($workspace, $expected, $test_name)
+
+Gets the number of fullscreen containers on the given workspace and verifies that
+they match the expected amount.
+
+  is_num_fullscreen('1', 0, 'no fullscreen containers on workspace 1');
+
+=cut
+sub is_num_fullscreen {
+    my ($workspace, $num_fullscreen, $name) = @_;
+    my $workspace_content = i3test::get_ws($workspace);
+    my $tb = $CLASS->builder;
+
+    my $nodes = scalar grep { $_->{fullscreen_mode} != 0 } @{$workspace_content->{nodes}->[0]->{nodes}};
+    my $cons = scalar grep { $_->{fullscreen_mode} != 0 } @{$workspace_content->{nodes}};
+    my $floating = scalar grep { $_->{fullscreen_mode} != 0 } @{$workspace_content->{floating_nodes}->[0]->{nodes}};
+    $tb->is_num($nodes + $cons + $floating, $num_fullscreen, $name);
+}
+
 =head2 cmp_float($a, $b)
 
 Compares floating point numbers C<$a> and C<$b> and returns true if they differ
index 1ca964b154813d6897f7c43c3b40ab58c187cbb0..4c464c5e3943f6f5db064766dbef891af44a68cb 100644 (file)
@@ -14,13 +14,13 @@ use ExtUtils::PkgConfig;
 use Exporter ();
 our @EXPORT = qw(
     inlinec_connect
+    xtest_sync_with
+    xtest_sync_with_i3
     set_xkb_group
     xtest_key_press
     xtest_key_release
     xtest_button_press
     xtest_button_release
-    listen_for_binding
-    start_binding_capture
     binding_events
 );
 
@@ -38,7 +38,7 @@ i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb
 # ineffective.
 my %sn_config;
 BEGIN {
-    %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest');
+    %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest xcb-util');
 }
 
 use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
@@ -53,8 +53,12 @@ use Inline C => <<'END_OF_C_CODE';
 #include <xcb/xcb.h>
 #include <xcb/xkb.h>
 #include <xcb/xtest.h>
+#include <xcb/xcb_aux.h>
 
 static xcb_connection_t *conn = NULL;
+static xcb_window_t sync_window;
+static xcb_window_t root_window;
+static xcb_atom_t i3_sync_atom;
 
 bool inlinec_connect() {
     int screen;
@@ -89,9 +93,94 @@ bool inlinec_connect() {
     }
     free(usereply);
 
+    xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, xcb_intern_atom(conn, 0, strlen("I3_SYNC"), "I3_SYNC"), NULL);
+    i3_sync_atom = reply->atom;
+    free(reply);
+
+    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
+    root_window = root_screen->root;
+    sync_window = xcb_generate_id(conn);
+    xcb_create_window(conn,
+                      XCB_COPY_FROM_PARENT,           // depth
+                      sync_window,                    // window
+                      root_window,                    // parent
+                      -15,                            // x
+                      -15,                            // y
+                      1,                              // width
+                      1,                              // height
+                      0,                              // border_width
+                      XCB_WINDOW_CLASS_INPUT_OUTPUT,  // class
+                      XCB_COPY_FROM_PARENT,           // visual
+                      XCB_CW_OVERRIDE_REDIRECT,       // value_mask
+                      (uint32_t[]){
+                          1,  // override_redirect
+                      });     // value_list
+
     return true;
 }
 
+void xtest_sync_with(int window) {
+    xcb_client_message_event_t ev;
+    memset(&ev, '\0', sizeof(xcb_client_message_event_t));
+
+    const int nonce = rand() % 255;
+
+    ev.response_type = XCB_CLIENT_MESSAGE;
+    ev.window = sync_window;
+    ev.type = i3_sync_atom;
+    ev.format = 32;
+    ev.data.data32[0] = sync_window;
+    ev.data.data32[1] = nonce;
+
+    xcb_send_event(conn, false, (xcb_window_t)window, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev);
+    xcb_flush(conn);
+
+    xcb_generic_event_t *event = NULL;
+    while (1) {
+        free(event);
+        if ((event = xcb_wait_for_event(conn)) == NULL) {
+            break;
+        }
+        if (event->response_type == 0) {
+            fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
+            continue;
+        }
+
+        /* Strip off the highest bit (set if the event is generated) */
+        const int type = (event->response_type & 0x7F);
+        switch (type) {
+            case XCB_CLIENT_MESSAGE: {
+                xcb_client_message_event_t *ev = (xcb_client_message_event_t *)event;
+                {
+                    const uint32_t got = ev->data.data32[0];
+                    const uint32_t want = sync_window;
+                    if (got != want) {
+                        fprintf(stderr, "Ignoring ClientMessage: unknown window: got %d, want %d\n", got, want);
+                        continue;
+                    }
+                }
+                {
+                    const uint32_t got = ev->data.data32[1];
+                    const uint32_t want = nonce;
+                    if (got != want) {
+                        fprintf(stderr, "Ignoring ClientMessage: unknown nonce: got %d, want %d\n", got, want);
+                        continue;
+                    }
+                }
+                return;
+            }
+            default:
+                fprintf(stderr, "Unexpected X11 event of type %d received (XCB_CLIENT_MESSAGE = %d)\n", type, XCB_CLIENT_MESSAGE);
+                break;
+        }
+    }
+    free(event);
+}
+
+void xtest_sync_with_i3() {
+    xtest_sync_with((int)root_window);
+}
+
 // NOTE: while |group| should be a uint8_t, Inline::C will not define the
 // function unless we use an int.
 bool set_xkb_group(int group) {
@@ -170,86 +259,6 @@ sub import {
 
 =cut
 
-my $i3;
-our @binding_events;
-
-=head2 start_binding_capture()
-
-Captures all binding events sent by i3 in the C<@binding_events> symbol, so
-that you can verify the correct number of binding events was generated.
-
-  my $pid = launch_with_config($config);
-  start_binding_capture;
-  # …
-  sync_with_i3;
-  is(scalar @i3test::XTEST::binding_events, 2, 'Received exactly 2 binding events');
-
-=cut
-
-sub start_binding_capture {
-    # Store a copy of each binding event so that we can count the expected
-    # events in test cases.
-    $i3 = i3(get_socket_path());
-    $i3->connect()->recv;
-    $i3->subscribe({
-        binding => sub {
-            my ($event) = @_;
-            @binding_events = (@binding_events, $event);
-        },
-    })->recv;
-}
-
-=head2 listen_for_binding($cb)
-
-Helper function to evaluate whether sending KeyPress/KeyRelease events via
-XTEST triggers an i3 key binding or not (with a timeout of 0.5s). Expects key
-bindings to be configured in the form “bindsym <binding> nop <binding>”, e.g.
-“bindsym Mod4+Return nop Mod4+Return”.
-
-  is(listen_for_binding(
-      sub {
-          xtest_key_press(133); # Super_L
-          xtest_key_press(36); # Return
-          xtest_key_release(36); # Return
-          xtest_key_release(133); # Super_L
-      },
-      ),
-     'Mod4+Return',
-     'triggered the "Mod4+Return" keybinding');
-
-=cut
-
-sub listen_for_binding {
-    my ($cb) = @_;
-    my $triggered = AnyEvent->condvar;
-    my $i3 = i3(get_socket_path());
-    $i3->connect()->recv;
-    $i3->subscribe({
-        binding => sub {
-            my ($event) = @_;
-            return unless $event->{change} eq 'run';
-            # We look at the command (which is “nop <binding>”) because that is
-            # easier than re-assembling the string representation of
-            # $event->{binding}.
-            $triggered->send($event->{binding}->{command});
-        },
-    })->recv;
-
-    my $t;
-    $t = AnyEvent->timer(
-        after => 0.5,
-        cb => sub {
-            $triggered->send('timeout');
-        }
-    );
-
-    $cb->();
-
-    my $recv = $triggered->recv;
-    $recv =~ s/^nop //g;
-    return $recv;
-}
-
 =head2 set_xkb_group($group)
 
 Changes the current XKB group from the default of 1 to C<$group>, which must be
@@ -283,6 +292,15 @@ Sends a ButtonRelease event via XTEST, with the specified C<$button>.
 
 Returns false when there was an X11 error, true otherwise.
 
+=head2 xtest_sync_with($window)
+
+Ensures the specified window has processed all X11 events which were triggered
+by this module, provided the window response to the i3 sync protocol.
+
+=head2 xtest_sync_with_i3()
+
+Ensures i3 has processed all X11 events which were triggered by this module.
+
 =head1 AUTHOR
 
 Michael Stapelberg <michael@i3wm.org>
index f0d0b4c69a4d1eaa44791f213c7135eedc61ac79..d817bee03bc52f25472bf92a9897b82eff4f89b8 100644 (file)
@@ -21,13 +21,6 @@ my $i3 = i3(get_socket_path());
 
 my $tmp = fresh_workspace;
 
-sub fullscreen_windows {
-    my $ws = $tmp;
-    $ws = shift if @_;
-
-    scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)}
-}
-
 # get the output of this workspace
 my $tree = $i3->get_tree->recv;
 my @outputs = @{$tree->{nodes}};
@@ -143,11 +136,11 @@ ok(!eq_hash($new_rect, $original_rect), "Window got repositioned");
 $swindow->fullscreen(1);
 sync_with_i3;
 
-is(fullscreen_windows(), 1, 'amount of fullscreen windows');
+is_num_fullscreen($tmp, 1, 'amount of fullscreen windows');
 
 $window->fullscreen(0);
 sync_with_i3;
-is(fullscreen_windows(), 1, 'amount of fullscreen windows');
+is_num_fullscreen($tmp, 1, 'amount of fullscreen windows');
 
 ok($swindow->mapped, 'window mapped after other fullscreen ended');
 
@@ -160,15 +153,15 @@ ok($swindow->mapped, 'window mapped after other fullscreen ended');
 $swindow->fullscreen(0);
 sync_with_i3;
 
-is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling');
+is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after disabling');
 
 cmd 'fullscreen';
 
-is(fullscreen_windows(), 1, 'amount of fullscreen windows after fullscreen command');
+is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after fullscreen command');
 
 cmd 'fullscreen';
 
-is(fullscreen_windows(), 0, 'amount of fullscreen windows after fullscreen command');
+is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after fullscreen command');
 
 # clean up the workspace so that it will be cleaned when switching away
 cmd 'kill' for (@{get_ws_content($tmp)});
@@ -221,18 +214,18 @@ $swindow = open_window;
 
 cmd 'fullscreen';
 
-is(fullscreen_windows($tmp2), 1, 'one fullscreen window on second ws');
+is_num_fullscreen($tmp2, 1, 'one fullscreen window on second ws');
 
 cmd "move workspace $tmp";
 
-is(fullscreen_windows($tmp2), 0, 'no fullscreen windows on second ws');
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on first ws');
+is_num_fullscreen($tmp2, 0, 'no fullscreen windows on second ws');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on first ws');
 
 $swindow->fullscreen(0);
 sync_with_i3;
 
 # Verify that $swindow was the one that initially remained fullscreen.
-is(fullscreen_windows($tmp), 0, 'no fullscreen windows on first ws');
+is_num_fullscreen($tmp, 0, 'no fullscreen windows on first ws');
 
 ################################################################################
 # Verify that opening a window with _NET_WM_STATE_FULLSCREEN unfullscreens any
@@ -245,14 +238,14 @@ $window = open_window();
 
 cmd "fullscreen";
 
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on ws');
 is($x->input_focus, $window->id, 'fullscreen window focused');
 
 $swindow = open_window({
     fullscreen => 1
 });
 
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on ws');
 is($x->input_focus, $swindow->id, 'fullscreen window focused');
 
 ################################################################################
@@ -263,19 +256,19 @@ $tmp = fresh_workspace;
 
 $window = open_window;
 is($x->input_focus, $window->id, 'window focused');
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
 
 cmd 'fullscreen enable';
 is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
 
 cmd 'fullscreen enable';
 is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 1, 'still one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'still one fullscreen window on workspace');
 
 $window->fullscreen(0);
 sync_with_i3;
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
 
 ################################################################################
 # Verify that command ‘fullscreen enable global’ works and is idempotent.
@@ -285,19 +278,19 @@ $tmp = fresh_workspace;
 
 $window = open_window;
 is($x->input_focus, $window->id, 'window focused');
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
 
 cmd 'fullscreen enable global';
 is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
 
 cmd 'fullscreen enable global';
 is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 1, 'still one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'still one fullscreen window on workspace');
 
 $window->fullscreen(0);
 sync_with_i3;
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
 
 ################################################################################
 # Verify that command ‘fullscreen disable’ works and is idempotent.
@@ -307,19 +300,19 @@ $tmp = fresh_workspace;
 
 $window = open_window;
 is($x->input_focus, $window->id, 'window focused');
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
 
 $window->fullscreen(1);
 sync_with_i3;
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
 
 cmd 'fullscreen disable';
 is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
 
 cmd 'fullscreen disable';
 is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 0, 'still no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'still no fullscreen window on workspace');
 
 ################################################################################
 # Verify that command ‘fullscreen toggle’ works.
@@ -328,15 +321,15 @@ is(fullscreen_windows($tmp), 0, 'still no fullscreen window on workspace');
 $tmp = fresh_workspace;
 
 $window = open_window;
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
 
 cmd 'fullscreen toggle';
 is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
 
 cmd 'fullscreen toggle';
 is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
 
 ################################################################################
 # Verify that a window’s fullscreen is disabled when another one is enabled
@@ -349,15 +342,15 @@ $window = open_window;
 $other = open_window;
 
 is($x->input_focus, $other->id, 'other window focused');
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
 
 cmd 'fullscreen enable';
 is($x->input_focus, $other->id, 'other window focused');
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
 
 cmd '[id="' . $window->id . '"] fullscreen enable';
 is($x->input_focus, $window->id, 'window focused');
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
 
 ################################################################################
 # Verify that when a global fullscreen is enabled the window is focused and
index 9c1507e679e21217e92abab9a864b2f3ce95289b..1e2644ad96ed334437042825293b50df54660753 100644 (file)
@@ -304,7 +304,7 @@ for ($type = 1; $type <= 2; $type++) {
     cmd 'move right';
     cmd '[id="' . $w3->id . '"] focus';
     sync_with_i3;
-    my $ws = get_ws($tmp);
+    $ws = get_ws($tmp);
     ok(!$ws->{urgent}, 'urgent flag not set on workspace');
 
 ##############################################################################
index 34ce078130c34e0e99bd98558d6e7c6f1370ba1d..b0c4354ec10fe7385e41ed5a95d7dd06b3e01051 100644 (file)
 
 use i3test;
 
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-################################
-# Workspaces requests and events
-################################
-
 my $old_ws = get_ws(focused_ws());
 
-# Events
-
 # We are switching to an empty workpspace from an empty workspace, so we expect
 # to receive "init", "focus", and "empty".
-my $init = AnyEvent->condvar;
-my $focus = AnyEvent->condvar;
-my $empty = AnyEvent->condvar;
-$i3->subscribe({
-    workspace => sub {
-        my ($event) = @_;
-        if ($event->{change} eq 'init') {
-            $init->send($event);
-        } elsif ($event->{change} eq 'focus') {
-            $focus->send($event);
-        } elsif ($event->{change} eq 'empty') {
-            $empty->send($event);
-        }
-    }
-})->recv;
-
-cmd 'workspace 2';
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $init->send(0);
-        $focus->send(0);
-        $empty->send(0);
-    }
-);
-
-my $init_event = $init->recv;
-my $focus_event = $focus->recv;
-my $empty_event = $empty->recv;
+my @events = events_for(
+    sub { cmd 'workspace 2' },
+    'workspace');
 
 my $current_ws = get_ws(focused_ws());
 
-ok($init_event, 'workspace "init" event received');
-is($init_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the initted workspace con');
+is(scalar @events, 3, 'Received 3 events');
+is($events[0]->{change}, 'init', 'First event has change = init');
+is($events[0]->{current}->{id}, $current_ws->{id}, 'the "current" property contains the initted workspace con');
 
-ok($focus_event, 'workspace "focus" event received');
-is($focus_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con');
-is($focus_event->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last');
+is($events[1]->{change}, 'focus', 'Second event has change = focus');
+is($events[1]->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con');
+is($events[1]->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last');
 
-ok($empty_event, 'workspace "empty" event received');
-is($empty_event->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con');
+is($events[2]->{change}, 'empty', 'Third event has change = empty');
+is($events[2]->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con');
 
 done_testing;
index 7a5e38ca136b36548454c401cb8644f6561a4b40..9c396f40fadfc0a438e5a9c13f3978bb77d0bb8f 100644 (file)
@@ -157,9 +157,6 @@ isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
 cmd 'focus child';
 is($x->input_focus, $right2->id, 'bottom right window focused again');
 
-cmd '[id="' . $left->id . '"] focus';
-is($x->input_focus, $right2->id, 'prevented focus change to left window');
-
 cmd 'focus up';
 is($x->input_focus, $right1->id, 'allowed focus up');
 
@@ -178,9 +175,6 @@ is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
 cmd 'focus up';
 is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
 
-cmd '[id="' . $diff_ws->id . '"] focus';
-is($x->input_focus, $right2->id, 'prevented focus change to different ws');
-
 ################################################################################
 # Same tests when we're in non-global fullscreen mode. It should now be possible
 # to focus a container in a different workspace.
@@ -202,9 +196,6 @@ isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
 cmd 'focus child';
 is($x->input_focus, $right2->id, 'bottom right window focused again');
 
-cmd '[id="' . $left->id . '"] focus';
-is($x->input_focus, $right2->id, 'prevented focus change to left window');
-
 cmd 'focus up';
 is($x->input_focus, $right1->id, 'allowed focus up');
 
@@ -323,6 +314,105 @@ verify_move(2, 'prevented move to workspace by name');
 cmd "move to workspace prev";
 verify_move(2, 'prevented move to workspace by position');
 
-# TODO: Tests for "move to output" and "move workspace to output".
+################################################################################
+# Ensure it's possible to focus a window using the focus command despite
+# fullscreen window blocking it. Fullscreen window should lose its fullscreen
+# mode.
+################################################################################
+
+# first & second tiling, focus using id
+kill_all_windows;
+
+$tmp = fresh_workspace;
+my $first = open_window;
+my $second = open_window;
+cmd 'fullscreen';
+is($x->input_focus, $second->id, 'fullscreen window focused');
+is_num_fullscreen($tmp, 1, '1 fullscreen window');
+
+cmd '[id="'. $first->id .'"] focus';
+sync_with_i3;
+
+is($x->input_focus, $first->id, 'correctly focused using id');
+is_num_fullscreen($tmp, 0, 'no fullscreen windows');
+
+# first floating, second tiling, focus using 'focus floating'
+kill_all_windows;
+
+$tmp = fresh_workspace;
+$first = open_floating_window;
+$second = open_window;
+cmd 'fullscreen';
+is($x->input_focus, $second->id, 'fullscreen window focused');
+is_num_fullscreen($tmp, 1, '1 fullscreen window');
+
+cmd 'focus floating';
+sync_with_i3;
+
+is($x->input_focus, $first->id, 'correctly focused using focus floating');
+is_num_fullscreen($tmp, 0, 'no fullscreen windows');
+
+# first tiling, second floating, focus using 'focus tiling'
+kill_all_windows;
+
+$tmp = fresh_workspace;
+$first = open_window;
+$second = open_floating_window;
+cmd 'fullscreen';
+is($x->input_focus, $second->id, 'fullscreen window focused');
+is_num_fullscreen($tmp, 1, '1 fullscreen window');
+
+cmd 'focus tiling';
+sync_with_i3;
 
+is($x->input_focus, $first->id, 'correctly focused using focus tiling');
+is_num_fullscreen($tmp, 0, 'no fullscreen windows');
+
+################################################################################
+# When the fullscreen window is in an other workspace it should maintain its
+# fullscreen mode since it's not blocking the window to be focused.
+################################################################################
+
+kill_all_windows;
+
+$tmp = fresh_workspace;
+$first = open_window;
+
+$tmp2 = fresh_workspace;
+$second = open_window;
+cmd 'fullscreen';
+is($x->input_focus, $second->id, 'fullscreen window focused');
+is_num_fullscreen($tmp2, 1, '1 fullscreen window');
+
+cmd '[id="'. $first->id .'"] focus';
+sync_with_i3;
+
+is($x->input_focus, $first->id, 'correctly focused using focus id');
+is_num_fullscreen($tmp, 0, 'no fullscreen windows on first workspace');
+is_num_fullscreen($tmp2, 1, 'still one fullscreen window on second workspace');
+
+################################################################################
+# But a global window in another workspace is blocking the window to be focused.
+# Ensure that it loses its fullscreen mode.
+################################################################################
+
+kill_all_windows;
+
+$tmp = fresh_workspace;
+$first = open_window;
+
+$tmp2 = fresh_workspace;
+$second = open_window;
+cmd 'fullscreen global';
+is($x->input_focus, $second->id, 'global window focused');
+is_num_fullscreen($tmp2, 1, '1 fullscreen window');
+
+cmd '[id="'. $first->id .'"] focus';
+sync_with_i3;
+
+is($x->input_focus, $first->id, 'correctly focused using focus id');
+is_num_fullscreen($tmp2, 0, 'no fullscreen windows');
+
+
+# TODO: Tests for "move to output" and "move workspace to output".
 done_testing;
index 21cc51894101aa51181aab35d873b04e01817691..f7a08647ff5bd523c39dc86be73df1cbc91fddbb 100644 (file)
@@ -21,18 +21,44 @@ use i3test i3_autostart => 0;
 sub open_special {
     my %args = @_;
     $args{name} //= 'special window';
+    $args{wm_class} //= 'special';
 
     # We use dont_map because i3 will not map the window on the current
     # workspace. Thus, open_window would time out in wait_for_map (2 seconds).
     my $window = open_window(
         %args,
-        wm_class => 'special',
         dont_map => 1,
     );
     $window->map;
     return $window;
 }
 
+sub test_workspace_assignment {
+    my $target_ws = "@_";
+
+    # initialize the target workspace, then go to a fresh one
+    ok(!($target_ws ~~ @{get_workspace_names()}), "$target_ws does not exist yet");
+    cmd "workspace $target_ws";
+    cmp_ok(@{get_ws_content($target_ws)}, '==', 0, "no containers on $target_ws yet");
+    cmd 'open';
+    cmp_ok(@{get_ws_content($target_ws)}, '==', 1, "one container on $target_ws");
+    my $tmp = fresh_workspace;
+
+    ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+    ok($target_ws ~~ @{get_workspace_names()}, "$target_ws does not exist yet");
+
+    # We use sync_with_i3 instead of wait_for_map here because i3 will not actually
+    # map the window -- it will be assigned to a different workspace and will only
+    # be mapped once you switch to that workspace
+    my $window = open_special;
+    sync_with_i3;
+
+    ok(@{get_ws_content($tmp)} == 0, 'still no containers');
+    ok(@{get_ws_content($target_ws)} == 2, "two containers on $target_ws");
+
+    return $window
+}
+
 #####################################################################
 # start a window and see that it does not get assigned with an empty config
 #####################################################################
@@ -87,33 +113,67 @@ $window->destroy;
 exit_gracefully($pid);
 
 #####################################################################
-# start a window and see that it gets assigned to a workspace which has content
-# already, next to the existing node.
+# start a window and see that it gets assigned to a formerly unused
+# numbered workspace
 #####################################################################
 
-$pid = launch_with_config($config);
+my $config_numbered = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign [class="special"] → workspace number 2
+EOT
+
+$pid = launch_with_config($config_numbered);
 
-# initialize the target workspace, then go to a fresh one
-ok(!("targetws" ~~ @{get_workspace_names()}), 'targetws does not exist yet');
-cmd 'workspace targetws';
-cmp_ok(@{get_ws_content('targetws')}, '==', 0, 'no containers on targetws yet');
-cmd 'open';
-cmp_ok(@{get_ws_content('targetws')}, '==', 1, 'one container on targetws');
 $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-ok("targetws" ~~ @{get_workspace_names()}, 'targetws does not exist yet');
-
+$workspaces = get_workspace_names;
+ok(!("2" ~~ @{$workspaces}), 'workspace number 2 does not exist yet');
 
-# We use sync_with_i3 instead of wait_for_map here because i3 will not actually
-# map the window -- it will be assigned to a different workspace and will only
-# be mapped once you switch to that workspace
-$window = open_special(dont_map => 1);
-$window->map;
+$window = open_special;
 sync_with_i3;
 
 ok(@{get_ws_content($tmp)} == 0, 'still no containers');
-ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws');
+ok("2" ~~ @{get_workspace_names()}, 'workspace number 2 exists');
+
+$window->destroy;
+
+exit_gracefully($pid);
+
+#####################################################################
+# start a window and see that it gets assigned to a numbered
+# workspace which has content already, next to the existing node.
+#####################################################################
+
+$pid = launch_with_config($config_numbered);
+
+$window = test_workspace_assignment("2");
+$window->destroy;
+
+exit_gracefully($pid);
+
+#####################################################################
+# start a window and see that it gets assigned to a numbered workspace with
+# a name which has content already, next to the existing node.
+#####################################################################
+
+$pid = launch_with_config($config_numbered);
+
+cmd 'workspace 2';  # Make sure that we are not testing for "2" again.
+$window = test_workspace_assignment("2: targetws");
+$window->destroy;
+
+exit_gracefully($pid);
+
+#####################################################################
+# start a window and see that it gets assigned to a workspace which
+# has content already, next to the existing node.
+#####################################################################
+
+$pid = launch_with_config($config);
+
+test_workspace_assignment("targetws");
 
 exit_gracefully($pid);
 
@@ -143,8 +203,127 @@ my $content = get_ws($tmp);
 ok(@{$content->{nodes}} == 0, 'no tiling cons');
 ok(@{$content->{floating_nodes}} == 1, 'one floating con');
 
-$window->destroy;
+kill_all_windows;
+exit_gracefully($pid);
+
+#####################################################################
+# test assignments to named outputs
+#####################################################################
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
+
+workspace ws-0 output fake-0
+workspace ws-1 output fake-1
+workspace ws-2 output fake-2
+workspace ws-3 output fake-3
+
+assign [class="special-0"] → output fake-0
+assign [class="special-1"] → output fake-1
+assign [class="special-2"] → output fake-2
+assign [class="special-3"] → output fake-3
+assign [class="special-4"] → output invalid
+
+EOT
+
+$pid = launch_with_config($config);
+
+sub open_in_output {
+    my ($num, $expected_count) = @_;
+    my $ws = "ws-$num";
+    my $class = "special-$num";
+    my $output = "fake-$num";
+
+    is_num_children($ws, $expected_count - 1,
+                    "before: " . ($expected_count - 1) . " containers on output $output");
+    $window = open_special(wm_class => $class);
+    sync_with_i3;
+    is_num_children($ws, $expected_count,
+                    "after: $expected_count containers on output $output");
+}
+
+cmd "workspace ws-0";
+open_in_output(0, 1);
+my $focused = $x->input_focus;
+
+open_in_output(1, 1);
+is($x->input_focus, $focused, 'focus remains on output fake-0');
+
+open_in_output(2, 1);
+is($x->input_focus, $focused, 'focus remains on output fake-0');
+
+for my $i (1 .. 5){
+    open_in_output(3, $i);
+    is($x->input_focus, $focused, 'focus remains on output fake-0');
+}
+
+# Check invalid output
+$tmp = fresh_workspace;
+open_special(wm_class => "special-4");
+sync_with_i3;
+is_num_children($tmp, 1, 'window assigned to invalid output opened in current workspace');
+open_special(wm_class => "special-3");
+sync_with_i3;
+is_num_children($tmp, 1, 'but window assigned to valid output did not');
+
+kill_all_windows;
+exit_gracefully($pid);
+
+#####################################################################
+# Test assignments to outputs with relative names
+#####################################################################
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
+
+workspace left-top output fake-0
+workspace right-top output fake-1
+workspace right-bottom output fake-2
+workspace left-bottom output fake-3
+
+assign [class="current"] → output current
+assign [class="left"] → output left
+assign [class="right"] → output right
+assign [class="up"] → output up
+assign [class="down"] → output down
+EOT
+
+$pid = launch_with_config($config);
+
+cmd 'workspace left-top';
+
+is_num_children('left-top', 0, 'no childreon on left-top');
+for my $i (1 .. 5){
+    open_special(wm_class => 'current');
+}
+sync_with_i3;
+is_num_children('left-top', 5, 'windows opened in current workspace');
+
+is_num_children('right-top', 0, 'no children on right-top');
+open_special(wm_class => 'right');
+sync_with_i3;
+is_num_children('right-top', 1, 'one child on right-top');
+
+is_num_children('left-bottom', 0, 'no children on left-bottom');
+open_special(wm_class => 'down');
+sync_with_i3;
+is_num_children('left-bottom', 1, 'one child on left-bottom');
+
+cmd 'workspace right-bottom';
+
+open_special(wm_class => 'up');
+sync_with_i3;
+is_num_children('right-top', 2, 'two children on right-top');
+
+open_special(wm_class => 'left');
+sync_with_i3;
+is_num_children('left-bottom', 2, 'two children on left-bottom');
 
+kill_all_windows;
 exit_gracefully($pid);
 
 #####################################################################
@@ -181,7 +360,7 @@ $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 my @docked = get_dock_clients;
-is(@docked, 0, 'one dock client yet');
+is(@docked, 0, 'no dock client yet');
 
 $window = open_special(
     window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
index d983eb8565de3d77b5ba9ffba1544e145122aaaa..597d545eb8c903d806643503bb12d58e11120725 100644 (file)
@@ -375,7 +375,74 @@ ok(@content == 2, 'two containers opened');
 isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
 isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed');
 
+exit_gracefully($pid);
+
+#####################################################################
+# 16: Check that the command 'layout toggle split' works regardless
+# of what layout we're using.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+workspace_layout default
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+my @layouts = ('splith', 'splitv', 'tabbed', 'stacked');
+my $first_layout;
+
+foreach $first_layout (@layouts) {
+    cmd 'layout ' . $first_layout;
+    $first = open_window;
+    $second = open_window;
+    cmd 'layout toggle split';
+    @content = @{get_ws_content($tmp)};
+    if ($first_layout eq 'splith') {
+        is($content[0]->{layout}, 'splitv', 'layout toggles to splitv');
+    } else {
+        is($content[0]->{layout}, 'splith', 'layout toggles to splith');
+    }
+
+    cmd '[id="' . $first->id . '"] kill';
+    cmd '[id="' . $second->id . '"] kill';
+    sync_with_i3;
+}
 
 exit_gracefully($pid);
 
+#####################################################################
+# 17: Check about setting a new layout.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+workspace_layout default
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+my $second_layout;
+
+foreach $first_layout (@layouts) {
+    foreach $second_layout (@layouts) {
+        cmd 'layout ' . $first_layout;
+        $first = open_window;
+        $second = open_window;
+        cmd 'layout ' . $second_layout;
+        @content = @{get_ws_content($tmp)};
+        is($content[0]->{layout}, $second_layout, 'layout changes to ' . $second_layout);
+
+        cmd '[id="' . $first->id . '"] kill';
+        cmd '[id="' . $second->id . '"] kill';
+        sync_with_i3;
+    }
+}
+
 done_testing;
index 4b225214addca64097047ac2722408fb18395ebe..d135ed59ccf145f42beacce4ebf2f26357abd32e 100644 (file)
@@ -95,7 +95,19 @@ EOT
 
 is(launch_get_border($config), 'none', 'no border');
 
+#####################################################################
+# test that variables with longer name than value don't crash i3 with
+# v3 to v4 conversion.
+# See: #3076
+#####################################################################
+
+$config = <<'EOT';
+set $var a
+EOT
 
+my $pid = launch_with_config($config);
+does_i3_live;
+exit_gracefully($pid);
 
 done_testing;
 
index f94bd75b61c6a85c658c8812fec0765652707c61..147890e1f9a20742842ebf5d16e4f37ca24315cc 100644 (file)
@@ -429,7 +429,7 @@ does_i3_live;
 ################################################################################
 
 clear_scratchpad;
-my $ws = fresh_workspace;
+$ws = fresh_workspace;
 
 open_window;
 my $scratch = get_focused($ws);
index ea4c08de0ddfdcaf33d865366a516cc961958e12..6b082bfdcb1bab61bbe2a108fd8bc9e47ccb3272 100644 (file)
@@ -190,7 +190,7 @@ exit_gracefully($pid);
 # 7: check floating_maximum_size with cmd_size
 ################################################################################
 
-my $config = <<EOT;
+$config = <<EOT;
 # i3 config file (v4)
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 
@@ -201,12 +201,12 @@ EOT
 
 $pid = launch_with_config($config);
 
-my $window = open_floating_window(rect => [ 0, 0, 90, 80 ]);
+$window = open_floating_window(rect => [ 0, 0, 90, 80 ]);
 cmd 'border none';
 
 cmd 'resize set 101 91';
 sync_with_i3;
-my $rect = $window->rect;
+$rect = $window->rect;
 is($rect->{width}, 100, 'width did not exceed maximum width');
 is($rect->{height}, 90, 'height did not exceed maximum height');
 
index aee1e03a69d3288e7d2facaa0967923b6cfefb43..f9f883cbd7756a45bd48d85440a216afb8ebe222 100644 (file)
@@ -24,7 +24,7 @@ use i3test;
 sub send_net_active_window {
     my ($id, $source) = @_;
 
-    $source = ($source eq 'pager' ? 2 : 0);
+    $source = (((defined $source) && ($source eq 'pager')) ? 2 : 0);
 
     my $msg = pack "CCSLLLLLLL",
         X11::XCB::CLIENT_MESSAGE, # response_type
@@ -137,7 +137,7 @@ is($x->input_focus, $win3->id, 'window 3 still focused');
 # is received.
 ################################################################################
 
-my $scratch = open_window;
+$scratch = open_window;
 
 is($x->input_focus, $scratch->id, 'to-scratchpad window has focus');
 
index 959ff6c474281260ab8d0e2b59133bc04abc21f3..0e4f89600c47b39686609e6456e2bd652bd48a18 100644 (file)
@@ -28,24 +28,11 @@ mode "with spaces" {
 }
 EOT
 
-my $i3 = i3(get_socket_path(0));
-$i3->connect->recv;
+my @events = events_for(
+    sub { cmd 'mode "m1"' },
+    'mode');
 
-my $cv = AnyEvent->condvar;
-
-$i3->subscribe({
-    mode => sub {
-        my ($event) = @_;
-        $cv->send($event->{change} eq 'm1');
-    }
-})->recv;
-
-cmd 'mode "m1"';
-
-# Timeout after 0.5s
-my $t;
-$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); });
-
-ok($cv->recv, 'Mode event received');
+my @changes = map { $_->{change} } @events;
+is_deeply(\@changes, [ 'm1' ], 'Mode event received');
 
 done_testing;
index 01b2227306e94f17cb36954723d361574ba0ed10..6857b6211c2645ad456afeaf1521e09e063664d9 100644 (file)
@@ -116,6 +116,7 @@ is(parser_calls($config),
 
 $config = <<'EOT';
 assign [class="^Chrome"] 4
+assign [class="^Chrome"] workspace number 3
 assign [class="^Chrome"] named workspace
 assign [class="^Chrome"] "quoted named workspace"
 assign [class="^Chrome"] → "quoted named workspace"
@@ -123,13 +124,15 @@ EOT
 
 $expected = <<'EOT';
 cfg_criteria_add(class, ^Chrome)
-cfg_assign(4)
+cfg_assign(4, 0)
 cfg_criteria_add(class, ^Chrome)
-cfg_assign(named workspace)
+cfg_assign(3, 1)
 cfg_criteria_add(class, ^Chrome)
-cfg_assign(quoted named workspace)
+cfg_assign(named workspace, 0)
 cfg_criteria_add(class, ^Chrome)
-cfg_assign(quoted named workspace)
+cfg_assign(quoted named workspace, 0)
+cfg_criteria_add(class, ^Chrome)
+cfg_assign(quoted named workspace, 0)
 EOT
 
 is(parser_calls($config),
@@ -142,7 +145,7 @@ is(parser_calls($config),
 
 $config = <<'EOT';
 floating_minimum_size 80x55
-floating_minimum_size 80    x  55  
+floating_minimum_size 80    x  55
 floating_maximum_size 73 x 10
 EOT
 
@@ -242,8 +245,8 @@ is(parser_calls($config),
 ################################################################################
 
 $config = <<'EOT';
-workspace "3" output DP-1 
-workspace "3" output           VGA-1   
+workspace "3" output DP-1
+workspace "3" output           VGA-1
 EOT
 
 $expected = <<'EOT';
@@ -263,19 +266,33 @@ $config = <<'EOT';
 new_window 1pixel
 new_window normal
 new_window none
+default_border 1pixel
+default_border normal
+default_border none
 new_float 1pixel
 new_float normal
 new_float none
+default_floating_border 1pixel
+default_floating_border normal
+default_floating_border none
 EOT
 
 $expected = <<'EOT';
-cfg_new_window(new_window, 1pixel, -1)
-cfg_new_window(new_window, normal, 2)
-cfg_new_window(new_window, none, -1)
-cfg_new_window(new_float, 1pixel, -1)
-cfg_new_window(new_float, normal, 2)
-cfg_new_window(new_float, none, -1)
-EOT
+cfg_default_border(new_window, 1pixel, -1)
+cfg_default_border(new_window, normal, 2)
+cfg_default_border(new_window, none, -1)
+cfg_default_border(default_border, 1pixel, -1)
+cfg_default_border(default_border, normal, 2)
+cfg_default_border(default_border, none, -1)
+cfg_default_border(new_float, 1pixel, -1)
+cfg_default_border(new_float, normal, 2)
+cfg_default_border(new_float, none, -1)
+cfg_default_border(default_floating_border, 1pixel, -1)
+cfg_default_border(default_floating_border, normal, 2)
+cfg_default_border(default_floating_border, none, -1)
+EOT
+
+# TODO: are there no tests for "border pixel 1" etc?
 
 is(parser_calls($config),
    $expected,
@@ -459,7 +476,9 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
         floating_modifier
         default_orientation
         workspace_layout
+        default_border
         new_window
+        default_floating_border
         new_float
         hide_edge_borders
         for_window
@@ -467,6 +486,7 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
         no_focus
         focus_follows_mouse
         mouse_warping
+        focus_wrapping
         force_focus_wrapping
         force_xinerama
         force-xinerama
index ca7db1533493416550d1f984f626f8cc796fc406..bafd155f6afeecf055bd0efc5579ad667c8ee174 100644 (file)
 
 use i3test;
 
-SKIP: {
-
-    skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
-
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-################################
-# Window event
-################################
-
-# Events
-
 my $new = AnyEvent->condvar;
 my $focus = AnyEvent->condvar;
-$i3->subscribe({
-    window => sub {
-        my ($event) = @_;
-        if ($event->{change} eq 'new') {
-            $new->send($event);
-        } elsif ($event->{change} eq 'focus') {
-            $focus->send($event);
-        }
-    }
-})->recv;
-
-open_window;
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $new->send(0);
-        $focus->send(0);
-    }
-);
 
-is($new->recv->{container}->{focused}, 0, 'Window "new" event received');
-is($focus->recv->{container}->{focused}, 1, 'Window "focus" event received');
+my @events = events_for(
+    sub { open_window },
+    'window');
 
-}
+is(scalar @events, 2, 'Received 2 events');
+is($events[0]->{container}->{focused}, 0, 'Window "new" event received');
+is($events[1]->{container}->{focused}, 1, 'Window "focus" event received');
 
 done_testing;
index c098f23fbe6e7036a27fe5185216859cf4ea729c..95245099332f991f736f36868a74d591c62f5ac4 100644 (file)
@@ -21,15 +21,6 @@ use i3test;
 
 my $tmp = fresh_workspace;
 
-sub fullscreen_windows {
-    my $ws = $tmp;
-    $ws = shift if @_;
-
-    my $nodes = scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)->[0]->{nodes}};
-    my $cons = scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)};
-    return $nodes + $cons;
-}
-
 ##########################################################################################
 # map two windows in one container, fullscreen one of them and then move it to scratchpad
 ##########################################################################################
@@ -41,7 +32,7 @@ my $second_win = open_window;
 cmd 'fullscreen';
 
 # see if the window really is in fullscreen mode
-is(fullscreen_windows(), 1, 'amount of fullscreen windows after enabling fullscreen');
+is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after enabling fullscreen');
 
 # move window to scratchpad
 cmd 'move scratchpad';
@@ -57,7 +48,7 @@ cmd 'scratchpad show';
 cmd 'floating toggle';
 
 # see if no window is in fullscreen mode
-is(fullscreen_windows(), 0, 'amount of fullscreen windows after showing previously fullscreened scratchpad window');
+is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after showing previously fullscreened scratchpad window');
 
 ########################################################################################
 # move a window to scratchpad, focus parent container, make it fullscreen, focus a child
@@ -79,7 +70,7 @@ cmd 'fullscreen';
 cmd 'focus child';
 
 # see if the window really is in fullscreen mode
-is(fullscreen_windows(), 1, 'amount of fullscreen windows after enabling fullscreen on parent');
+is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after enabling fullscreen on parent');
 
 ##########################################################################
 # show a scratchpad window; no window should be in fullscreen mode anymore
@@ -89,6 +80,6 @@ is(fullscreen_windows(), 1, 'amount of fullscreen windows after enabling fullscr
 cmd 'scratchpad show';
 
 # see if no window is in fullscreen mode
-is(fullscreen_windows(), 0, 'amount of fullscreen windows after showing a scratchpad window while a parent container was in fullscreen mode');
+is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after showing a scratchpad window while a parent container was in fullscreen mode');
 
 done_testing;
index 1351d568d77a05c7e7a8d24b95b205ac1d620275..c2b2ebaa3c1294c5c0b2652b004799062a7250ab 100644 (file)
@@ -54,7 +54,7 @@ like($stderr, qr#^$#, 'stderr empty');
 # 3: change size of the shared memory log buffer and verify old content is gone
 ################################################################################
 
-cmd 'shmlog ' . (23 * 1024 * 1024);
+cmd 'shmlog ' . (1 * 1024 * 1024);
 
 run [ 'i3-dump-log' ],
     '>', \$stdout,
index ae7781877cffac9e3e7a26c5d2d0a7a123bd76be..b1c8ba1836148b377a706da4848143d040a2b951 100644 (file)
 
 use i3test;
 
-SKIP: {
-
-    skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
-
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
 ################################
 # Window focus event
 ################################
@@ -33,62 +26,29 @@ my $win0 = open_window;
 my $win1 = open_window;
 my $win2 = open_window;
 
-my $focus = AnyEvent->condvar;
-
-$i3->subscribe({
-    window => sub {
-        my ($event) = @_;
-        $focus->send($event);
-    }
-})->recv;
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $focus->send(0);
-    }
-);
-
 # ensure the rightmost window contains input focus
-$i3->command('[id="' . $win2->id . '"] focus')->recv;
+cmd '[id="' . $win2->id . '"] focus';
 is($x->input_focus, $win2->id, "Window 2 focused");
 
-cmd 'focus left';
-my $event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($focus->recv->{container}->{name}, 'Window 1', 'Window 1 focused');
-
-$focus = AnyEvent->condvar;
-cmd 'focus left';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 0', 'Window 0 focused');
-
-$focus = AnyEvent->condvar;
-cmd 'focus right';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 1', 'Window 1 focused');
+sub focus_subtest {
+    my ($cmd, $name) = @_;
 
-$focus = AnyEvent->condvar;
-cmd 'focus right';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 2', 'Window 2 focused');
+    my $focus = AnyEvent->condvar;
 
-$focus = AnyEvent->condvar;
-cmd 'focus right';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 0', 'Window 0 focused');
-
-$focus = AnyEvent->condvar;
-cmd 'focus left';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 2', 'Window 2 focused');
+    my @events = events_for(
+       sub { cmd $cmd },
+       'window');
 
+    is(scalar @events, 1, 'Received 1 event');
+    is($events[0]->{change}, 'focus', 'Focus event received');
+    is($events[0]->{container}->{name}, $name, "$name focused");
 }
 
+subtest 'focus left (1)', \&focus_subtest, 'focus left', $win1->name;
+subtest 'focus left (2)', \&focus_subtest, 'focus left', $win0->name;
+subtest 'focus right (1)', \&focus_subtest, 'focus right', $win1->name;
+subtest 'focus right (2)', \&focus_subtest, 'focus right', $win2->name;
+subtest 'focus right (3)', \&focus_subtest, 'focus right', $win0->name;
+subtest 'focus left', \&focus_subtest, 'focus left', $win2->name;
+
 done_testing;
index c751350a43f9660150323eaed2077f0dc040cabe..b5d14e237864622f64dea8fa8aec4643d2d26db5 100644 (file)
 
 use i3test;
 
-SKIP: {
-
-    skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
-
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-################################
-# Window title event
-################################
-
 my $window = open_window(name => 'Window 0');
 
-my $title = AnyEvent->condvar;
-
-$i3->subscribe({
-    window => sub {
-        my ($event) = @_;
-        $title->send($event);
-    }
-})->recv;
-
-$window->name('New Window Title');
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $title->send(0);
-    }
-);
-
-my $event = $title->recv;
-is($event->{change}, 'title', 'Window title change event received');
-is($event->{container}->{name}, 'New Window Title', 'Window title changed');
+my @events = events_for(
+    sub {
+       $window->name('New Window Title');
+       sync_with_i3;
+    },
+    'window');
 
-}
+is(scalar @events, 1, 'Received 1 event');
+is($events[0]->{change}, 'title', 'Window title change event received');
+is($events[0]->{container}->{name}, 'New Window Title', 'Window title changed');
 
 done_testing;
index aeabe9539f44cbb5a3f9040a1e7a49d1fd34372a..bc150546906e94e625e0c3a6fb8d920aa2480399 100644 (file)
 # Bug still in: 4.7.2-135-g7deb23c
 use i3test;
 
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
+open_window;
 
-my $cv;
-my $t;
+sub fullscreen_subtest {
+    my ($want) = @_;
+    my @events = events_for(
+       sub { cmd 'fullscreen' },
+       'window');
 
-sub reset_test {
-    $cv = AE::cv;
-    $t = AE::timer(0.5, 0, sub { $cv->send(0); });
+    is(scalar @events, 1, 'Received 1 event');
+    is($events[0]->{container}->{fullscreen_mode}, $want, "fullscreen_mode now $want");
 }
 
-reset_test;
-
-$i3->subscribe({
-        window => sub {
-            my ($e) = @_;
-            if ($e->{change} eq 'fullscreen_mode') {
-                $cv->send($e->{container});
-            }
-        },
-    })->recv;
-
-my $window = open_window;
-
-cmd 'fullscreen';
-my $con = $cv->recv;
-
-ok($con, 'got fullscreen window event (on)');
-is($con->{fullscreen_mode}, 1, 'window is fullscreen');
-
-reset_test;
-cmd 'fullscreen';
-$con = $cv->recv;
-
-ok($con, 'got fullscreen window event (off)');
-is($con->{fullscreen_mode}, 0, 'window is not fullscreen');
+subtest 'fullscreen on', \&fullscreen_subtest, 1;
+subtest 'fullscreen off', \&fullscreen_subtest, 0;
 
 done_testing;
index fe8e03c02b01f9cb37c335fd2097a0e29ac8547c..b1f517ef5e4eb67aed9cf44044b02c04a31686db 100644 (file)
 #
 use i3test;
 
-SKIP: {
-
-    skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
-
 ################################################################################
-# check that the workspace empty event is send upon workspace switch when the
+# check that the workspace empty event is sent upon workspace switch when the
 # old workspace is empty
 ################################################################################
 subtest 'Workspace empty event upon switch', sub {
@@ -35,26 +31,17 @@ subtest 'Workspace empty event upon switch', sub {
     cmd '[id="' . $w1->id . '"] kill';
 
     my $cond = AnyEvent->condvar;
-    my $client = i3(get_socket_path(0));
-    $client->connect()->recv;
-    $client->subscribe({
-        workspace => sub {
-            my ($event) = @_;
-            $cond->send($event);
-        }
-    })->recv;
-
-    cmd "workspace $ws2";
-
-    sync_with_i3;
+    my @events = events_for(
+       sub { cmd "workspace $ws2" },
+       'workspace');
 
-    my $event = $cond->recv;
-    is($event->{change}, 'empty', '"Empty" event received upon workspace switch');
-    is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
+    is(scalar @events, 2, 'Received 2 event');
+    is($events[1]->{change}, 'empty', '"Empty" event received upon workspace switch');
+    is($events[1]->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
 };
 
 ################################################################################
-# check that no workspace empty event is send upon workspace switch if the
+# check that no workspace empty event is sent upon workspace switch if the
 # workspace is not empty
 ################################################################################
 subtest 'No workspace empty event', sub {
@@ -63,36 +50,16 @@ subtest 'No workspace empty event', sub {
     my $ws1 = fresh_workspace;
     my $w1 = open_window();
 
-    my @events;
-    my $cond = AnyEvent->condvar;
-    my $client = i3(get_socket_path(0));
-    $client->connect()->recv;
-    $client->subscribe({
-        workspace => sub {
-            my ($event) = @_;
-            push @events, $event;
-        }
-    })->recv;
-
-    # Wait for the workspace event on a new connection. Events will be delivered
-    # to older connections earlier, so by the time it arrives here, it should be
-    # in @events already.
-    my $ws_event_block_conn = i3(get_socket_path(0));
-    $ws_event_block_conn->connect()->recv;
-    $ws_event_block_conn->subscribe({ workspace => sub { $cond->send(1) }});
-
-    cmd "workspace $ws2";
+    my @events = events_for(
+       sub { cmd "workspace $ws2" },
+       'workspace');
 
-    sync_with_i3;
-
-    my @expected_events = grep { $_->{change} eq 'focus' } @events;
-    my @empty_events = grep { $_->{change} eq 'empty' } @events;
-    is(@expected_events, 1, '"Focus" event received');
-    is(@empty_events, 0, 'No "empty" events received');
+    is(scalar @events, 1, 'Received 1 event');
+    is($events[0]->{change}, 'focus', 'Event change is "focus"');
 };
 
 ################################################################################
-# check that workspace empty event is send when the last window has been closed
+# check that workspace empty event is sent when the last window has been closed
 # on invisible workspace
 ################################################################################
 subtest 'Workspace empty event upon window close', sub {
@@ -101,25 +68,16 @@ subtest 'Workspace empty event upon window close', sub {
     my $ws2 = fresh_workspace;
     my $w2 = open_window();
 
-    my $cond = AnyEvent->condvar;
-    my $client = i3(get_socket_path(0));
-    $client->connect()->recv;
-    $client->subscribe({
-        workspace => sub {
-            my ($event) = @_;
-            $cond->send($event);
-        }
-    })->recv;
-
-    cmd '[id="' . $w1->id . '"] kill';
-
-    sync_with_i3;
+    my @events = events_for(
+       sub {
+           $w1->unmap;
+           sync_with_i3;
+       },
+       'workspace');
 
-    my $event = $cond->recv;
-    is($event->{change}, 'empty', '"Empty" event received upon window close');
-    is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
+    is(scalar @events, 1, 'Received 1 event');
+    is($events[0]->{change}, 'empty', '"Empty" event received upon window close');
+    is($events[0]->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
 };
 
-}
-
 done_testing;
index e38a187609797a1853fca76f5a590aeca083fc38..96c94a497bd7b1b1a00f0b440ff7acc8a229a192 100644 (file)
 # Bug still in: 4.8-7-gf4a8253
 use i3test;
 
-my $i3 = i3(get_socket_path());
-$i3->connect->recv;
+sub floating_subtest {
+    my ($win, $cmd, $want) = @_;
 
-my $cv = AnyEvent->condvar;
+    my @events = events_for(
+       sub { cmd $cmd },
+       'window');
 
-$i3->subscribe({
-        window => sub {
-            my ($event) = @_;
-            $cv->send($event) if $event->{change} eq 'floating';
-        }
-    })->recv;
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $cv->send(0);
-    }
-);
+    my @floating = grep { $_->{change} eq 'floating' } @events;
+    is(scalar @floating, 1, 'Received 1 floating event');
+    is($floating[0]->{container}->{window}, $win->{id}, "window id matches");
+    is($floating[0]->{container}->{floating}, $want, "floating is $want");
+}
 
 my $win = open_window();
 
-cmd '[id="' . $win->{id} . '"] floating enable';
-my $e = $cv->recv;
-
-isnt($e, 0, 'floating a container should send an ipc window event');
-is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window');
-is($e->{container}->{floating}, 'user_on', 'the container should be floating');
-
-$cv = AnyEvent->condvar;
-cmd '[id="' . $win->{id} . '"] floating disable';
-$e = $cv->recv;
-
-isnt($e, 0, 'disabling floating on a container should send an ipc window event');
-is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window');
-is($e->{container}->{floating}, 'user_off', 'the container should not be floating');
+subtest 'floating enable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating enable', 'user_on';
+subtest 'floating disable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating disable', 'user_off';
 
 done_testing;
index af3f4d2fa10bd25f6a13e2bdf028f5f96d2226eb..bec95a2376e29fcb2cd8952c718482262c8c614e 100644 (file)
@@ -35,51 +35,38 @@ SKIP: {
 
     skip 'xdotool is required to test the binding event. `[apt-get install|pacman -S] xdotool`', 1 if $?;
 
-    skip "AnyEvent::I3 too old (need >= 0.16)", 1 if $AnyEvent::I3::VERSION < 0.16;
-
     my $pid = launch_with_config($config);
 
-    my $i3 = i3(get_socket_path());
-    $i3->connect->recv;
-
-    my $cv = AE::cv;
-    my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
-
-    $i3->subscribe({
-            binding => sub {
-                $cv->send(shift);
-            }
-        })->recv;
-
-    qx(xdotool key $binding_symbol);
-
-    my $e = $cv->recv;
-
-    does_i3_live;
+    my $cv = AnyEvent->condvar;
 
-    diag "Event:\n", Dumper($e);
+    my @events = events_for(
+       sub {
+           # TODO: this is still flaky: we need to synchronize every X11
+           # connection with i3. Move to XTEST and synchronize that connection.
+           qx(xdotool key $binding_symbol);
+       },
+       'binding');
 
-    ok($e,
-        'the binding event should emit when user input triggers an i3 binding event');
+    is(scalar @events, 1, 'Received 1 event');
 
-    is($e->{change}, 'run',
+    is($events[0]->{change}, 'run',
         'the `change` field should indicate this binding has run');
 
-    ok($e->{binding},
+    ok($events[0]->{binding},
         'the `binding` field should be a hash that contains information about the binding');
 
-    is($e->{binding}->{input_type}, 'keyboard',
+    is($events[0]->{binding}->{input_type}, 'keyboard',
         'the input_type field should be the input type of the binding (keyboard or mouse)');
 
     note 'the `mods` field should contain the symbols for the modifiers of the binding';
     foreach (@mods) {
-        ok(grep(/$_/i, @{$e->{binding}->{mods}}), "`mods` contains the modifier $_");
+        ok(grep(/$_/i, @{$events[0]->{binding}->{mods}}), "`mods` contains the modifier $_");
     }
 
-    is($e->{binding}->{command}, $command,
+    is($events[0]->{binding}->{command}, $command,
         'the `command` field should contain the command the binding ran');
 
-    is($e->{binding}->{input_code}, 0,
+    is($events[0]->{binding}->{input_code}, 0,
         'the input_code should be the specified code if the key was bound with bindcode, and otherwise zero');
 
     exit_gracefully($pid);
index cd3989c4257a89fcf88491ba3cf183ef4ffc11ed..57060753978ac6f5c99085109e43b2c00a96df43 100644 (file)
@@ -111,7 +111,7 @@ EOT
 
 $pid = launch_with_config($config);
 
-my $ws = fresh_workspace;
+$ws = fresh_workspace;
 $first = open_window;
 $second = open_window;
 
@@ -165,7 +165,7 @@ EOT
 
 $pid = launch_with_config($config);
 
-my $ws = fresh_workspace;
+$ws = fresh_workspace;
 $first = open_window;
 $second = open_window;
 
index 8d8d412036105fd17a18ebb9aaf72bae8e176733..2c8edf397084f2226a62ce56c4da6a8ffb17e3da 100644 (file)
 # Test behavior of "resize <width> <height>" command.
 # Ticket: #1727
 # Bug still in: 4.10.2-1-gc0dbc5d
-use i3test;
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1333x999+0+0
+workspace ws output fake-0
+EOT
 
 ################################################################################
 # Check that setting floating windows size works
@@ -42,4 +48,54 @@ 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');
 
+################################################################################
+# Same but with ppt instead of px
+################################################################################
+
+kill_all_windows;
+$tmp = 'ws';
+cmd "workspace $tmp";
+open_floating_window;
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+is(@content, 1, 'one floating node on this ws');
+
+$oldrect = $content[0]->{rect};
+
+cmd 'resize set 33 ppt 20 ppt';
+my $expected_width = int(0.33 * 1333);
+my $expected_height = int(0.2 * 999);
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
+cmp_ok($content[0]->{rect}->{width}, '!=', $oldrect->{width}, 'width changed');
+cmp_ok($content[0]->{rect}->{height}, '!=', $oldrect->{width}, 'height changed');
+cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px");
+cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px");
+
+################################################################################
+# Mix ppt and px in a single resize set command
+################################################################################
+
+cmd 'resize set 44 ppt 111 px';
+$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");
+
+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");
+
 done_testing;
index f9166fadbcbfeddff82ead75870a17773d0ff81c..bc08aa2f2118997b2117e564bc633372126db65d 100644 (file)
@@ -36,14 +36,13 @@ SKIP: {
     skip "setxkbmap not found", 1 if
         system(q|setxkbmap -print >/dev/null|) != 0;
 
-start_binding_capture;
-
 system(q|setxkbmap us,ru -option grp:alt_shift_toggle|);
 
 is(listen_for_binding(
     sub {
         xtest_key_press(107);
         xtest_key_release(107);
+        xtest_sync_with_i3;
     },
     ),
    'Print',
@@ -55,6 +54,7 @@ is(listen_for_binding(
         xtest_key_press(36); # Return
         xtest_key_release(36); # Return
         xtest_key_release(133); # Super_L
+        xtest_sync_with_i3;
     },
     ),
    'Mod4+Return',
@@ -67,6 +67,7 @@ is(listen_for_binding(
     sub {
         xtest_key_press(107);
         xtest_key_release(107);
+        xtest_sync_with_i3;
     },
     ),
    'Print',
@@ -78,14 +79,12 @@ is(listen_for_binding(
         xtest_key_press(36); # Return
         xtest_key_release(36); # Return
         xtest_key_release(133); # Super_L
+        xtest_sync_with_i3;
     },
     ),
    'Mod4+Return',
    'triggered the "Mod4+Return" keybinding');
 
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events');
-
 # Disable the grp:alt_shift_toggle option, as we use Alt+Shift in other testcases.
 system(q|setxkbmap us -option|);
 
index 72bfc86281e2831355ebca4609713d3b8a4690ac..8bca0d869996216c7aa4293e82703259841408bc 100644 (file)
@@ -37,12 +37,11 @@ SKIP: {
     skip "libxcb-xkb too old (need >= 1.11)", 1 unless
         ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11');
 
-start_binding_capture;
-
 is(listen_for_binding(
     sub {
         xtest_key_press(107); # Print
         xtest_key_release(107); # Print
+        xtest_sync_with_i3;
     },
     ),
     'Print',
@@ -54,6 +53,7 @@ is(listen_for_binding(
         xtest_key_press(107); # Print
         xtest_key_release(107); # Print
         xtest_key_release(37); # Control_L
+        xtest_sync_with_i3;
     },
     ),
     'Control+Print',
@@ -65,6 +65,7 @@ is(listen_for_binding(
         xtest_key_press(56); # b
         xtest_key_release(56); # b
         xtest_key_release(64); # Alt_L
+        xtest_sync_with_i3;
     },
     ),
     'Mod1+b',
@@ -78,14 +79,12 @@ is(listen_for_binding(
         xtest_key_release(56); # b
         xtest_key_release(50); # Shift_L
         xtest_key_release(64); # Alt_L
+        xtest_sync_with_i3;
     },
     ),
     'Mod1+Shift+b release',
     'triggered the "Mod1+Shift+b" release keybinding');
 
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events');
-
 }
 
 done_testing;
index ba95897c2974ffaf8e392ce40f79b0d6af2a34cd..1416b6b9e19bedf3d0e0b46feefa6c837d92413b 100644 (file)
@@ -28,23 +28,11 @@ EOT
 
 cmd 'mode othermode';
 
-my $i3 = i3(get_socket_path(0));
-$i3->connect->recv;
+my @events = events_for(
+    sub { cmd 'reload' },
+    'mode');
 
-my $cv = AnyEvent->condvar;
-$i3->subscribe({
-    mode => sub {
-        my ($event) = @_;
-        $cv->send($event->{change} eq 'default');
-    }
-})->recv;
-
-cmd 'reload';
-
-# Timeout after 0.5s
-my $t;
-$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); });
-
-ok($cv->recv, 'Mode event received');
+is(scalar @events, 1, 'Received 1 event');
+is($events[0]->{change}, 'default', 'change is "default"');
 
 done_testing;
index 06d8d83d15cad1bca951e84c76f5a409658acfaf..a101944e793e83233e3addc456afa9927101038e 100644 (file)
 # Ticket: #2501
 use i3test;
 
-my ($i3, $timer, $event, $mark);
+sub mark_subtest {
+    my ($cmd) = @_;
 
-$i3 = i3(get_socket_path());
-$i3->connect()->recv;
+    my @events = events_for(
+       sub { cmd $cmd },
+       'window');
 
-$i3->subscribe({
-    window => sub {
-        my ($event) = @_;
-        return unless defined $mark;
-        return unless $event->{change} eq 'mark';
-
-        $mark->send($event);
-    }
-})->recv;
-
-$timer = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $mark->send(0);
-    }
-);
+    my @mark = grep { $_->{change} eq 'mark' } @events;
+    is(scalar @mark, 1, 'Received 1 window::mark event');
+}
 
 ###############################################################################
 # Marking a container triggers a 'mark' event.
@@ -46,11 +35,7 @@ $timer = AnyEvent->timer(
 fresh_workspace;
 open_window;
 
-$mark = AnyEvent->condvar;
-cmd 'mark x';
-
-$event = $mark->recv;
-ok($event, 'window::mark event has been received');
+subtest 'mark', \&mark_subtest, 'mark x';
 
 ###############################################################################
 # Unmarking a container triggers a 'mark' event.
@@ -59,11 +44,7 @@ fresh_workspace;
 open_window;
 cmd 'mark x';
 
-$mark = AnyEvent->condvar;
-cmd 'unmark x';
-
-$event = $mark->recv;
-ok($event, 'window::mark event has been received');
+subtest 'unmark', \&mark_subtest, 'unmark x';
 
 ###############################################################################
 
index b21ff1b2ae003781b9763c818ed4ea1b7d0c5d5e..9ac749b63143950b372984f7462b40ac06c41e7d 100644 (file)
@@ -42,8 +42,8 @@ get_socket_path(0);
 my $i3 = i3(get_socket_path());
 $i3->connect->recv;
 
-my $cv = AE::cv;
-my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
+my $cv = AnyEvent->condvar;
+my $timer = AnyEvent->timer(after => 0.5, interval => 0, cb => sub { $cv->send(0); });
 
 my $last_config = $i3->get_config()->recv;
 chomp($last_config->{config});
index bf9d4f8bba6cfca833b930dcbbbe3e7b05fbb780..ccaf4440ea8a0d9dbe8b64543984e28d53c13751 100644 (file)
 # Bug still in: 4.8-7-gf4a8253
 use i3test;
 
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-my $cv;
-my $t;
-
-sub reset_test {
-    $cv = AE::cv;
-    $t = AE::timer(0.5, 0, sub { $cv->send(0); });
-}
-
-reset_test;
-
-$i3->subscribe({
-        window => sub {
-            my ($e) = @_;
-            if ($e->{change} eq 'close') {
-                $cv->send($e->{container});
-            }
-        },
-    })->recv;
-
 my $window = open_window;
 
-cmd 'kill';
-my $con = $cv->recv;
+my @events = events_for(
+    sub {
+       $window->unmap;
+       sync_with_i3;
+    },
+    'window');
 
-ok($con, 'closing a window should send the window::close event');
-is($con->{window}, $window->{id}, 'the event should contain information about the window');
+my @close = grep { $_->{change} eq 'close' } @events;
+is(scalar @close, 1, 'Received 1 window::close event');
+is($close[0]->{container}->{window}, $window->{id}, 'the event should contain information about the window');
 
 done_testing;
index a3c825348a203246b736441600ecf3b48395da48..f3606b4e8106b1c32d34a463cca45bb2d29d5d24 100644 (file)
 # Bug still in: 4.8-7-gf4a8253
 use i3test;
 
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-my $cv;
-my $t;
-
-sub reset_test {
-    $cv = AE::cv;
-    $t = AE::timer(0.5, 0, sub { $cv->send(0); });
-}
-
-reset_test;
-
-$i3->subscribe({
-        window => sub {
-            my ($e) = @_;
-            if ($e->{change} eq 'move') {
-                $cv->send($e->{container});
-            }
-        },
-    })->recv;
-
 my $dummy_window = open_window;
 my $window = open_window;
 
-cmd 'move right';
-my $con = $cv->recv;
-
-ok($con, 'moving a window should emit the window::move event');
-is($con->{window}, $window->{id}, 'the event should contain info about the window');
+sub move_subtest {
+    my ($cmd) = @_;
+    my $cv = AnyEvent->condvar;
+    my @events = events_for(
+       sub { cmd $cmd },
+       'window');
 
-reset_test;
-
-cmd 'move to workspace ws_new';
-$con = $cv->recv;
+    my @move = grep { $_->{change} eq 'move' } @events;
+    is(scalar @move, 1, 'Received 1 window::move event');
+    is($move[0]->{container}->{window}, $window->{id}, 'window id matches');
+}
 
-ok($con, 'moving a window to a different workspace should emit the window::move event');
-is($con->{window}, $window->{id}, 'the event should contain info about the window');
+subtest 'move right', \&move_subtest, 'move right';
+subtest 'move to workspace', \&move_subtest, 'move to workspace ws_new';
 
 done_testing;
index 2af29dac9de05494c71cef31d606ec535240ea6a..4eea2cdcac1ef27eed5a40a0cc25ec5bca6fbd7f 100644 (file)
 #
 use i3test;
 
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-
-force_display_urgency_hint 0ms
-EOT
-
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-my $cv;
-$i3->subscribe({
-    window => sub {
-        my ($event) = @_;
-        $cv->send($event) if $event->{change} eq 'urgent';
-    }
-})->recv;
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $cv->send(0);
-    }
-);
-
-$cv = AnyEvent->condvar;
 fresh_workspace;
 my $win = open_window;
 my $dummy_win = open_window;
 
-$win->add_hint('urgency');
-my $event = $cv->recv;
-
-isnt($event, 0, 'an urgent con should emit the window::urgent event');
-is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window');
-is($event->{container}->{urgent}, 1, 'the container should be urgent');
-
-$cv = AnyEvent->condvar;
-$win->delete_hint('urgency');
-$event = $cv->recv;
-
-isnt($event, 0, 'an urgent con should emit the window::urgent event');
-is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window');
-is($event->{container}->{urgent}, 0, 'the container should not be urgent');
+sub urgency_subtest {
+    my ($subscribecb, $win, $want) = @_;
+
+    my @events = events_for(
+       $subscribecb,
+       'window');
+
+    my @urgent = grep { $_->{change} eq 'urgent' } @events;
+    is(scalar @urgent, 1, 'Received 1 window::urgent event');
+    is($urgent[0]->{container}->{window}, $win->{id}, "window id matches");
+    is($urgent[0]->{container}->{urgent}, $want, "urgent is $want");
+}
+
+subtest "urgency set", \&urgency_subtest,
+    sub {
+       $win->add_hint('urgency');
+       sync_with_i3;
+    },
+    $win,
+    1;
+
+subtest "urgency unset", \&urgency_subtest,
+    sub {
+       $win->delete_hint('urgency');
+       sync_with_i3;
+    },
+    $win,
+    0;
 
 done_testing;
index 6c1b5d6ace7b5f561df1c66672ee8c5e55f0ac3f..0da373b74e3168129d95b6971dcf60200d5011b7 100644 (file)
@@ -30,7 +30,7 @@ fresh_workspace;
 
 xtest_button_press(4, 50, 50);
 xtest_button_release(4, 50, 50);
-sync_with_i3;
+xtest_sync_with_i3;
 
 is(focused_ws(), 'special', 'the binding was triggered');
 
index 0cf347aa88b923c210f0c551308ec05e67c3edbb..606474e24e2abb27a77648f06962d545d5ae6b54 100644 (file)
 # Bug still in: 4.12-46-g2123888
 use i3test;
 
-SKIP: {
-    skip "AnyEvent::I3 too old (need >= 0.17)", 1 if $AnyEvent::I3::VERSION < 0.17;
+# We cannot use events_for in this test as we cannot send events after
+# issuing the restart/shutdown command.
 
 my $i3 = i3(get_socket_path());
 $i3->connect->recv;
 
-my $cv = AE::cv;
-my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
+my $cv = AnyEvent->condvar;
+my $timer = AnyEvent->timer(after => 0.5, interval => 0, cb => sub { $cv->send(0); });
 
 $i3->subscribe({
         shutdown => sub {
@@ -50,8 +50,8 @@ is($e->{change}, 'restart', 'the `change` field should tell the reason for the s
 $i3 = i3(get_socket_path());
 $i3->connect->recv;
 
-$cv = AE::cv;
-$timer = AE::timer 0.5, 0, sub { $cv->send(0); };
+$cv = AnyEvent->condvar;
+$timer = AnyEvent->timer(after => 0.5, interval => 0, cb => sub { $cv->send(0); });
 
 $i3->subscribe({
         shutdown => sub {
@@ -66,6 +66,5 @@ $e = $cv->recv;
 diag "Event:\n", Dumper($e);
 ok($e, 'the shutdown event should emit when the ipc is exited by command');
 is($e->{change}, 'exit', 'the `change` field should tell the reason for the shutdown');
-}
 
 done_testing;
index 5137c35fc8e55d0e3ed1fb1f45e8c282b1d4176f..94a5747d0ffa9e5f4033ce71b0fab2a8c551b60b 100644 (file)
@@ -51,12 +51,11 @@ EOT
 
 my $pid = launch_with_config($config);
 
-start_binding_capture;
-
 is(listen_for_binding(
     sub {
         xtest_key_press(87); # KP_End
         xtest_key_release(87); # KP_End
+        xtest_sync_with_i3;
     },
     ),
    'KP_End',
@@ -70,6 +69,7 @@ is(listen_for_binding(
         xtest_key_release(87); # KP_1
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'KP_1',
@@ -81,6 +81,7 @@ is(listen_for_binding(
         xtest_key_press(38); # a
         xtest_key_release(38); # a
         xtest_key_release(133); # Super_L
+        xtest_sync_with_i3;
     },
     ),
    'a',
@@ -96,6 +97,7 @@ is(listen_for_binding(
         xtest_key_release(133); # Super_L
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'a',
@@ -105,6 +107,7 @@ is(listen_for_binding(
     sub {
         xtest_key_press(9); # Escape
         xtest_key_release(9); # Escape
+        xtest_sync_with_i3;
     },
     ),
    'Escape',
@@ -118,6 +121,7 @@ is(listen_for_binding(
         xtest_key_release(9); # Escape
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'Escape',
@@ -129,6 +133,7 @@ is(listen_for_binding(
         xtest_key_press(9); # Escape
         xtest_key_release(9); # Escape
         xtest_key_release(50); # Shift_L
+        xtest_sync_with_i3;
     },
     ),
    'Shift+Escape',
@@ -144,6 +149,7 @@ is(listen_for_binding(
         xtest_key_release(50); # Shift_L
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'Shift+Escape',
@@ -157,6 +163,7 @@ is(listen_for_binding(
         xtest_key_release(24); # q
         xtest_key_release(64); # Alt_L
         xtest_key_release(50); # Shift_L
+        xtest_sync_with_i3;
     },
     ),
    'Mod1+Shift+q',
@@ -174,6 +181,7 @@ is(listen_for_binding(
         xtest_key_release(50); # Shift_L
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'Mod1+Shift+q',
@@ -183,6 +191,7 @@ is(listen_for_binding(
     sub {
         xtest_key_press(39); # s
         xtest_key_release(39); # s
+        xtest_sync_with_i3;
     },
     ),
    's',
@@ -196,14 +205,12 @@ is(listen_for_binding(
         xtest_key_release(39); # s
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    's',
    'triggered the "s" keybinding with Num_Lock');
 
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 12, 'Received exactly 12 binding events');
-
 exit_gracefully($pid);
 
 ################################################################################
@@ -222,12 +229,11 @@ EOT
 
 $pid = launch_with_config($config);
 
-start_binding_capture;
-
 is(listen_for_binding(
     sub {
         xtest_key_press(133); # Super_L
         xtest_key_release(133); # Super_L
+        xtest_sync_with_i3;
     },
     ),
    'Super_L',
@@ -241,6 +247,7 @@ is(listen_for_binding(
         xtest_key_release(133); # Super_L
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'Super_L',
@@ -252,6 +259,7 @@ is(listen_for_binding(
         xtest_key_press(36); # Return
         xtest_key_release(36); # Return
         xtest_key_release(133); # Super_L
+        xtest_sync_with_i3;
     },
     ),
    'Return',
@@ -267,14 +275,12 @@ is(listen_for_binding(
         xtest_key_release(133); # Super_L
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'Return',
    'triggered the "Return" keybinding with Num_Lock');
 
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 16, 'Received exactly 16 binding events');
-
 exit_gracefully($pid);
 
 ################################################################################
@@ -291,12 +297,11 @@ EOT
 
 $pid = launch_with_config($config);
 
-start_binding_capture;
-
 is(listen_for_binding(
     sub {
         xtest_key_press(87); # KP_End
         xtest_key_release(87); # KP_End
+        xtest_sync_with_i3;
     },
     ),
    'KP_End',
@@ -306,12 +311,13 @@ is(listen_for_binding(
     sub {
         xtest_key_press(88); # KP_Down
         xtest_key_release(88); # KP_Down
+        xtest_sync_with_i3;
     },
     ),
    'KP_Down',
    'triggered the "KP_Down" keybinding');
 
-is(listen_for_binding(
+my @unexpected = events_for(
     sub {
         xtest_key_press(77); # enable Num_Lock
         xtest_key_release(77); # enable Num_Lock
@@ -319,12 +325,12 @@ is(listen_for_binding(
         xtest_key_release(87); # KP_1
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
-    ),
-   'timeout',
-   'Did not trigger the KP_End keybinding with KP_1');
+    'binding');
+is(scalar @unexpected, 0, 'Did not trigger the KP_End keybinding with KP_1');
 
-is(listen_for_binding(
+my @unexpected2 = events_for(
     sub {
         xtest_key_press(77); # enable Num_Lock
         xtest_key_release(77); # enable Num_Lock
@@ -332,15 +338,13 @@ is(listen_for_binding(
         xtest_key_release(88); # KP_2
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
-    ),
-   'timeout',
-   'Did not trigger the KP_Down keybinding with KP_2');
+    'binding');
 
-# TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2.
+is(scalar @unexpected2, 0, 'Did not trigger the KP_Down keybinding with KP_2');
 
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 18, 'Received exactly 18 binding events');
+# TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2.
 
 exit_gracefully($pid);
 
@@ -359,8 +363,6 @@ $pid = launch_with_config($config);
 
 my $win = open_window;
 
-start_binding_capture;
-
 is(listen_for_binding(
     sub {
         xtest_key_press(77); # enable Num_Lock
@@ -369,6 +371,7 @@ is(listen_for_binding(
         xtest_button_release(4, 50, 50);
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'button4',
@@ -376,8 +379,9 @@ is(listen_for_binding(
 
 is(listen_for_binding(
     sub {
-       xtest_button_press(4, 50, 50);
-       xtest_button_release(4, 50, 50);
+        xtest_button_press(4, 50, 50);
+        xtest_button_release(4, 50, 50);
+        xtest_sync_with_i3;
     },
     ),
    'button4',
index f04d0f3b5fc324335082c77f912795f910ca02f3..3b61fdab36e8ba35a914f34fdb5a71138f011abc 100644 (file)
@@ -25,8 +25,9 @@ for_window[class="mark_B"] mark B
 EOT
 
 my ($ws, $ws1, $ws2, $ws3);
-my ($nodes, $expected_focus, $A, $B, $F);
+my ($node, $nodes, $expected_focus, $A, $B, $F);
 my ($result);
+my @fullscreen_permutations = ([], ["A"], ["B"], ["A", "B"]);
 my @urgent;
 
 ###############################################################################
@@ -162,26 +163,115 @@ kill_all_windows;
 # | Y | B |    Focus Stacks:
 # +---+---+        H2: B, Y
 ###############################################################################
+for my $fullscreen (@fullscreen_permutations){
+    $ws1 = fresh_workspace;
+    $A = open_window(wm_class => 'mark_A');
+    $expected_focus = get_focused($ws1);
+    open_window;
+    cmd 'focus left';
+
+    $ws2 = fresh_workspace;
+    open_window;
+    $B = open_window(wm_class => 'mark_B');
+
+    my $A_fullscreen = "A" ~~ @$fullscreen || 0;
+    my $B_fullscreen = "B" ~~ @$fullscreen || 0;
+    $A->fullscreen($A_fullscreen);
+    $B->fullscreen($B_fullscreen);
+    sync_with_i3;
+
+    cmd '[con_mark=B] swap container with mark A';
+
+    $nodes = get_ws_content($ws1);
+    $node = $nodes->[0];
+    is($node->{window}, $B->{id}, 'B is on ws1:left');
+    is_num_fullscreen($ws1, $A_fullscreen, 'amount of fullscreen windows in ws1');
+    is($node->{fullscreen_mode}, $A_fullscreen, 'B got A\'s fullscreen mode');
+
+    $nodes = get_ws_content($ws2);
+    $node = $nodes->[1];
+    is($node->{window}, $A->{id}, 'A is on ws2:right');
+    is(get_focused($ws2), $expected_focus, 'A is focused');
+    is_num_fullscreen($ws2, $B_fullscreen, 'amount of fullscreen windows in ws2');
+    is($node->{fullscreen_mode}, $B_fullscreen, 'A got B\'s fullscreen mode');
+
+    kill_all_windows;
+}
+
+###############################################################################
+# Swap a non-fullscreen window with a fullscreen one in different workspaces.
+# Layout: O1[ W1[ H1 ] W2[ B ] ]
+#
+# +---+---+    Layout: H1[ A F ]
+# | A | F |    Focus Stacks:
+# +---+---+        H1: F, A
+#
+# +---+---+
+# |   B   |
+# +---+---+
+###############################################################################
 $ws1 = fresh_workspace;
+
 $A = open_window(wm_class => 'mark_A');
+$F = open_window();
+$F->fullscreen(1);
 $expected_focus = get_focused($ws1);
-open_window;
-cmd 'focus left';
 
 $ws2 = fresh_workspace;
-open_window;
 $B = open_window(wm_class => 'mark_B');
+$B->fullscreen(1);
+sync_with_i3;
 
 cmd '[con_mark=B] swap container with mark A';
 
 $nodes = get_ws_content($ws1);
 is($nodes->[0]->{window}, $B->{id}, 'B is on ws1:left');
+is_num_fullscreen($ws1, 1, 'F still fullscreen in ws1');
+is(get_focused($ws1), $expected_focus, 'F is still focused');
 
 $nodes = get_ws_content($ws2);
-is($nodes->[1]->{window}, $A->{id}, 'A is on ws1:right');
-is(get_focused($ws2), $expected_focus, 'A is focused');
+is($nodes->[0]->{window}, $A->{id}, 'A is on ws1');
 
-kill_all_windows;
+###############################################################################
+# Try a more exotic layout with fullscreen containers.
+# A and F are fullscreened as a stack of two vertical containers before the
+# swap is performed.
+# A is swapped with fullscreened window B which is in another workspace.
+#
+# +---+---+    Layout: H1[ X V1[ A F ] ]
+# |   | A |    Focus Stacks:
+# | X +---+        H1: V1, X
+# |   | F |        V1: F, A
+# +---+---+
+###############################################################################
+$ws1 = fresh_workspace;
+
+open_window;
+$A = open_window(wm_class => 'mark_A');
+cmd "split v";
+open_window;
+cmd "focus parent";
+cmd "fullscreen enable";
+$expected_focus = get_focused($ws1);
+
+$ws2 = fresh_workspace;
+$B = open_window(wm_class => 'mark_B');
+$B->fullscreen(1);
+sync_with_i3;
+
+cmd '[con_mark=B] swap container with mark A';
+
+sync_with_i3;
+does_i3_live;
+
+$nodes = get_ws_content($ws1);
+is($nodes->[1]->{nodes}->[0]->{window}, $B->{id}, 'B is on top right in ws1');
+is(get_focused($ws1), $expected_focus, 'The container of the stacked windows remains focused in ws1');
+is_num_fullscreen($ws1, 1, 'Same amount of fullscreen windows in ws1');
+
+$nodes = get_ws_content($ws2);
+is($nodes->[0]->{window}, $A->{id}, 'A is on ws2');
+is_num_fullscreen($ws2, 1, 'A is in fullscreen mode');
 
 ###############################################################################
 # Swap two non-focused containers within the same workspace.
@@ -232,27 +322,41 @@ kill_all_windows;
 # | F |
 # +---+
 ###############################################################################
-$ws1 = fresh_workspace;
-$A = open_window(wm_class => 'mark_A');
+for my $fullscreen (@fullscreen_permutations){
+    $ws1 = fresh_workspace;
+    $A = open_window(wm_class => 'mark_A');
 
-$ws2 = fresh_workspace;
-$B = open_window(wm_class => 'mark_B');
+    $ws2 = fresh_workspace;
+    $B = open_window(wm_class => 'mark_B');
 
-$ws3 = fresh_workspace;
-open_window;
-$expected_focus = get_focused($ws3);
+    $ws3 = fresh_workspace;
+    open_window;
+    $expected_focus = get_focused($ws3);
 
-cmd '[con_mark=B] swap container with mark A';
+    my $A_fullscreen = "A" ~~ @$fullscreen || 0;
+    my $B_fullscreen = "B" ~~ @$fullscreen || 0;
+    $A->fullscreen($A_fullscreen);
+    $B->fullscreen($B_fullscreen);
+    sync_with_i3;
 
-$nodes = get_ws_content($ws1);
-is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace');
+    cmd '[con_mark=B] swap container with mark A';
 
-$nodes = get_ws_content($ws2);
-is($nodes->[0]->{window}, $A->{id}, 'A is on the second workspace');
+    $nodes = get_ws_content($ws1);
+    $node = $nodes->[0];
+    is($node->{window}, $B->{id}, 'B is on the first workspace');
+    is_num_fullscreen($ws1, $A_fullscreen, 'amount of fullscreen windows in ws1');
+    is($node->{fullscreen_mode}, $A_fullscreen, 'B got A\'s fullscreen mode');
 
-is(get_focused($ws3), $expected_focus, 'F is still focused');
+    $nodes = get_ws_content($ws2);
+    $node = $nodes->[0];
+    is($node->{window}, $A->{id}, 'A is on the second workspace');
+    is_num_fullscreen($ws2, $B_fullscreen, 'amount of fullscreen windows in ws2');
+    is($node->{fullscreen_mode}, $B_fullscreen, 'A got B\'s fullscreen mode');
 
-kill_all_windows;
+    is(get_focused($ws3), $expected_focus, 'F is still focused');
+
+    kill_all_windows;
+}
 
 ###############################################################################
 # Swap two non-focused containers with one being on a different workspace.
diff --git a/testcases/t/293-focus-follows-mouse.t b/testcases/t/293-focus-follows-mouse.t
new file mode 100644 (file)
index 0000000..0cd6e5c
--- /dev/null
@@ -0,0 +1,88 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests the focus_follows_mouse setting.
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1000x1000+0+0
+EOT
+
+my ($first, $second);
+
+sub synced_warp_pointer {
+    my ($x_px, $y_px) = @_;
+    sync_with_i3;
+    $x->root->warp_pointer($x_px, $y_px);
+    sync_with_i3;
+}
+
+###################################################################
+# Test a simple case with 2 windows.
+###################################################################
+
+synced_warp_pointer(600, 600);
+$first = open_window;
+$second = open_window;
+is($x->input_focus, $second->id, 'second window focused');
+
+synced_warp_pointer(0, 0);
+is($x->input_focus, $first->id, 'first window focused');
+
+###################################################################
+# Test that focus isn't changed with tabbed windows.
+###################################################################
+
+fresh_workspace;
+synced_warp_pointer(600, 600);
+$first = open_window;
+cmd 'layout tabbed';
+$second = open_window;
+is($x->input_focus, $second->id, 'second (tabbed) window focused');
+
+synced_warp_pointer(0, 0);
+is($x->input_focus, $second->id, 'second window still focused');
+
+###################################################################
+# Test that floating windows are focused but not raised to the top.
+# See issue #2990.
+###################################################################
+
+my $ws;
+my $tmp = fresh_workspace;
+my ($first_floating, $second_floating);
+
+synced_warp_pointer(0, 0);
+$first_floating = open_floating_window;
+$first_floating->rect(X11::XCB::Rect->new(x => 1, y => 1, width => 100, height => 100));
+$second_floating = open_floating_window;
+$second_floating->rect(X11::XCB::Rect->new(x => 50, y => 50, width => 100, height => 100));
+sync_with_i3;
+$first = open_window;
+
+is($x->input_focus, $first->id, 'first (tiling) window focused');
+$ws = get_ws($tmp);
+is($ws->{floating_nodes}->[1]->{nodes}->[0]->{window}, $second_floating->id, 'second floating on top');
+is($ws->{floating_nodes}->[0]->{nodes}->[0]->{window}, $first_floating->id, 'first floating behind');
+
+synced_warp_pointer(40, 40);
+is($x->input_focus, $first_floating->id, 'first floating window focused');
+$ws = get_ws($tmp);
+is($ws->{floating_nodes}->[1]->{nodes}->[0]->{window}, $second_floating->id, 'second floating still on top');
+is($ws->{floating_nodes}->[0]->{nodes}->[0]->{window}, $first_floating->id, 'first floating still behind');
+
+done_testing;
diff --git a/testcases/t/293-sticky-output-crash.t b/testcases/t/293-sticky-output-crash.t
new file mode 100644 (file)
index 0000000..93ebaee
--- /dev/null
@@ -0,0 +1,41 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verifies that i3 does not crash when opening a floating sticky on one output
+# and then switching empty workspaces on the other output.
+# Ticket: #3075
+# Bug still in: 4.14-191-g9d2d602d
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
+
+# A window on the left output.
+fresh_workspace(output => 0);
+open_window;
+cmd 'sticky enable, floating enable';
+
+# Switch to the right output and open a new workspace.
+my $ws = fresh_workspace(output => 1);
+does_i3_live;
+
+# Verify results.
+is(@{get_ws($ws)->{floating_nodes}}, 0, 'workspace in right output is empty');
+$ws = fresh_workspace(output => 0);
+is(@{get_ws($ws)->{floating_nodes}}, 1, 'new workspace in left output has the sticky container');
+
+done_testing;
diff --git a/testcases/t/294-focus-order.t b/testcases/t/294-focus-order.t
new file mode 100644 (file)
index 0000000..41c0bf0
--- /dev/null
@@ -0,0 +1,109 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verify that the corrent focus stack order is preserved after various
+# operations.
+use i3test;
+
+sub kill_and_confirm_focus {
+    my $focus = shift;
+    my $msg = shift;
+    cmd "kill";
+    sync_with_i3;
+    is($x->input_focus, $focus, $msg);
+}
+
+my @windows;
+
+sub focus_windows {
+    for (my $i = $#windows; $i >= 0; $i--) {
+        cmd '[id=' . $windows[$i]->id . '] focus';
+    }
+}
+
+sub confirm_focus {
+    my $msg = shift;
+    sync_with_i3;
+    is($x->input_focus, $windows[0]->id, $msg . ': window 0 focused');
+    foreach my $i (1 .. $#windows) {
+        kill_and_confirm_focus($windows[$i]->id, "$msg: window $i focused");
+    }
+    cmd 'kill';
+    @windows = ();
+}
+
+#####################################################################
+# Open 5 windows, focus them in a custom order and then change to
+# tabbed layout. The focus order should be preserved.
+#####################################################################
+
+fresh_workspace;
+
+$windows[3] = open_window;
+$windows[1] = open_window;
+$windows[0] = open_window;
+$windows[2] = open_window;
+$windows[4] = open_window;
+focus_windows;
+
+cmd 'layout tabbed';
+confirm_focus('tabbed');
+
+#####################################################################
+# Same as above but with stacked.
+#####################################################################
+
+fresh_workspace;
+
+$windows[3] = open_window;
+$windows[1] = open_window;
+$windows[0] = open_window;
+$windows[2] = open_window;
+$windows[4] = open_window;
+focus_windows;
+
+cmd 'layout stacked';
+confirm_focus('stacked');
+
+#####################################################################
+# Open 4 windows horizontally, move the last one down. The focus
+# order should be preserved.
+#####################################################################
+
+fresh_workspace;
+$windows[3] = open_window;
+$windows[2] = open_window;
+$windows[1] = open_window;
+$windows[0] = open_window;
+
+cmd 'move down';
+confirm_focus('split-h + move');
+
+#####################################################################
+# Same as above but with a vertical split.
+#####################################################################
+
+fresh_workspace;
+$windows[3] = open_window;
+cmd 'split v';
+$windows[2] = open_window;
+$windows[1] = open_window;
+$windows[0] = open_window;
+
+cmd 'move left';
+confirm_focus('split-v + move');
+
+done_testing;
diff --git a/testcases/t/294-update-ewmh-atoms.t b/testcases/t/294-update-ewmh-atoms.t
new file mode 100644 (file)
index 0000000..047cc11
--- /dev/null
@@ -0,0 +1,111 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verifies _NET_DESKTOP_NAMES, _NET_CURRENT_DESKTOP and _NET_CURRENT_DESKTOP
+# are updated properly when closing an inactive workspace container.
+# See github issue #3126
+
+use i3test;
+
+sub get_desktop_names {
+    sync_with_i3;
+
+    my $cookie = $x->get_property(
+        0,
+        $x->get_root_window(),
+        $x->atom(name => '_NET_DESKTOP_NAMES')->id,
+        $x->atom(name => 'UTF8_STRING')->id,
+        0,
+        4096,
+    );
+
+    my $reply = $x->get_property_reply($cookie->{sequence});
+
+    return 0 if $reply->{value_len} == 0;
+
+    # the property is a null-delimited list of utf8 strings ;;
+    return split /\0/, $reply->{value};
+}
+
+sub get_num_of_desktops {
+    sync_with_i3;
+
+    my $cookie = $x->get_property(
+        0,
+        $x->get_root_window(),
+        $x->atom(name => '_NET_NUMBER_OF_DESKTOPS')->id,
+        $x->atom(name => 'CARDINAL')->id,
+        0,
+        4,
+    );
+
+    my $reply = $x->get_property_reply($cookie->{sequence});
+
+    return undef if $reply->{value_len} != 1;
+    return undef if $reply->{format} != 32;
+    return undef if $reply->{type} != $x->atom(name => 'CARDINAL')->id,;
+
+    return unpack 'L', $reply->{value};
+}
+
+sub get_current_desktop {
+    sync_with_i3;
+
+    my $cookie = $x->get_property(
+        0,
+        $x->get_root_window(),
+        $x->atom(name => '_NET_CURRENT_DESKTOP')->id,
+        $x->atom(name => 'CARDINAL')->id,
+        0,
+        4,
+    );
+
+    my $reply = $x->get_property_reply($cookie->{sequence});
+
+    return undef if $reply->{value_len} != 1;
+    return undef if $reply->{format} != 32;
+    return undef if $reply->{type} != $x->atom(name => 'CARDINAL')->id,;
+
+    return unpack 'L', $reply->{value};
+}
+
+cmd 'workspace 0';
+my $first = open_window;
+
+cmd 'workspace 1';
+my $second = open_window;
+
+cmd 'workspace 2';
+my $third = open_window;
+
+# Sanity check
+is(get_current_desktop, 2);
+is(get_num_of_desktops, 3);
+my @actual_names = get_desktop_names;
+my @expected_names = ('0', '1', '2');
+is_deeply(\@actual_names, \@expected_names);
+
+# Kill first window to close a workspace.
+cmd '[id="' . $second->id . '"] kill';
+
+is(get_current_desktop, 2, '_NET_CURRENT_DESKTOP should be updated');
+is(get_num_of_desktops, 2, '_NET_NUMBER_OF_DESKTOPS should be updated');
+my @actual_names = get_desktop_names;
+my @expected_names = ('0', '2');
+is_deeply(\@actual_names, \@expected_names, '_NET_DESKTOP_NAMES should be updated');
+
+
+done_testing;
index 8e48bafe366c0209da1e22d4aa00347d69468eff..4ec33f6632e0de0bde9b3221a17cef30a1cc09a3 100644 (file)
@@ -179,6 +179,36 @@ cmd '[con_mark=marked] move workspace to output current';
 ($x0, $x1) = workspaces_per_screen();
 ok($ws1 ~~ @$x0, 'ws1 on fake-0');
 
+################################################################################
+# Verify that '[class=".*"] move workspace to output' doesn't fail when
+# containers in the scratchpad are matched.
+# See issue: #3064.
+################################################################################
+my $__i3_scratch = get_ws('__i3_scratch');
+is(scalar @{$__i3_scratch->{floating_nodes}}, 0, 'scratchpad is empty');
+
+$ws0 = fresh_workspace(output => 0);
+open_window(wm_class => 'a');
+
+$ws1 = fresh_workspace(output => 1);
+open_window(wm_class => 'b');
+my $scratchpad_window = open_window(wm_class => 'c');
+cmd 'move to scratchpad';
+
+($x0, $x1) = workspaces_per_screen();
+ok($ws0 ~~ @$x0, 'ws0 on fake-0');
+ok($ws1 ~~ @$x1, 'ws1 on fake-1');
+
+my $reply = cmd '[class=".*"] move workspace to output fake-1';
+ok($reply->[0]->{success}, 'move successful');
+
+($x0, $x1) = workspaces_per_screen();
+ok($ws0 ~~ @$x1, 'ws0 on fake-1');
+ok($ws1 ~~ @$x1, 'ws1 on fake-1');
+
+$__i3_scratch = get_ws('__i3_scratch');
+is(scalar @{$__i3_scratch->{floating_nodes}}, 1, 'window still in scratchpad');
+
 ################################################################################
 
 done_testing;
index 21169adbc3b25ca155f6c9f5ad4e180702e79181..065437f0b73ae24652aeb2a2d5ad4f4c122df2ca 100644 (file)
@@ -92,7 +92,7 @@ reset_focus $s3_ws;
 
 cmd "workspace $s2_ws";
 cmd 'focus right';
-is($x->input_focus, $sixth->id, 'sixth window focused');
+is($x->input_focus, $seventh->id, 'seventh window focused');
 reset_focus $s3_ws;
 
 cmd "workspace $s2_ws";
@@ -110,6 +110,8 @@ is($x->input_focus, $third->id, 'third window focused');
 cmd 'focus parent';
 cmd 'focus parent';
 cmd 'split v';
+# Focus second or else $first gets to the top of the focus stack.
+cmd '[id=' . $second->id . '] focus';
 reset_focus $s0_ws;
 
 cmd "workspace $s3_ws";
@@ -117,6 +119,7 @@ is($x->input_focus, $eighth->id, 'eighth window focused');
 cmd 'focus parent';
 cmd 'focus parent';
 cmd 'split v';
+cmd '[id=' . $sixth->id . '] focus';
 reset_focus $s3_ws;
 
 cmd "workspace $s1_ws";
@@ -137,4 +140,96 @@ cmd "workspace $s2_ws";
 cmd 'focus up';
 is($x->input_focus, $second->id, 'second window focused');
 
+###################################################################
+# Test that focus (left|down|right|up), when focusing across
+# outputs, doesn't focus the next window in the given direction but
+# the most focused window of the container in the given direction.
+# In the following layout:
+# [ WS1*[ ] WS2[ H[ A B* ] ] ]
+# (where the asterisk denotes the focused container within its
+# parent) moving right from WS1 should focus B which is focused
+# inside WS2, not A which is the next window on the right of WS1.
+# See issue #1160.
+###################################################################
+
+kill_all_windows;
+
+sync_with_i3;
+$x->root->warp_pointer(1025, 0);  # Second screen.
+sync_with_i3;
+$s1_ws = fresh_workspace;
+$first = open_window;
+$second = open_window;
+
+sync_with_i3;
+$x->root->warp_pointer(0, 0);  # First screen.
+sync_with_i3;
+$s0_ws = fresh_workspace;
+open_window;
+$third = open_window;
+
+cmd 'focus right';
+is($x->input_focus, $second->id, 'second window (rightmost) focused');
+cmd 'focus left';
+is($x->input_focus, $first->id, 'first window focused');
+cmd 'focus left';
+is($x->input_focus, $third->id, 'third window focused');
+
+
+###################################################################
+# Similar but with a tabbed layout.
+###################################################################
+
+cmd 'layout tabbed';
+$fourth = open_window;
+cmd 'focus left';
+is($x->input_focus, $third->id, 'third window (tabbed) focused');
+cmd "workspace $s1_ws";
+cmd 'focus left';
+is($x->input_focus, $third->id, 'third window (tabbed) focused');
+
+
+###################################################################
+# Similar but with a stacked layout on the bottom screen.
+###################################################################
+
+sync_with_i3;
+$x->root->warp_pointer(0, 769);  # Third screen.
+sync_with_i3;
+$s2_ws = fresh_workspace;
+cmd 'layout stacked';
+$fifth = open_window;
+$sixth = open_window;
+
+cmd "workspace $s0_ws";
+cmd 'focus down';
+is($x->input_focus, $sixth->id, 'sixth window (stacked) focused');
+
+###################################################################
+# Similar but with a more complex layout.
+###################################################################
+
+sync_with_i3;
+$x->root->warp_pointer(1025, 769);  # Fourth screen.
+sync_with_i3;
+$s3_ws = fresh_workspace;
+open_window;
+open_window;
+cmd 'split v';
+open_window;
+open_window;
+cmd 'split h';
+my $nested = open_window;
+open_window;
+cmd 'focus left';
+is($x->input_focus, $nested->id, 'nested window focused');
+
+cmd "workspace $s1_ws";
+cmd 'focus down';
+is($x->input_focus, $nested->id, 'nested window focused from workspace above');
+
+cmd "workspace $s2_ws";
+cmd 'focus right';
+is($x->input_focus, $nested->id, 'nested window focused from workspace on the left');
+
 done_testing;
index e3753bec681ffffa3ac15ac2814d0c1b76e38d3c..ac918fe379d74c160fed7c672156f47edf5af34f 100644 (file)
@@ -13,7 +13,7 @@
 #
 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
 #   (unless you are already familiar with Perl)
-# 
+#
 # Ticket: #990
 # Bug still in: 4.5.1-23-g82b5978
 
@@ -23,46 +23,17 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 fake-outputs 1024x768+0+0,1024x768+1024+0
 EOT
 
-my $i3 = i3(get_socket_path());
-
-$i3->connect()->recv;
-
-################################
-# Workspaces requests and events
-################################
-
 my $old_ws = get_ws(focused_ws);
 
-# Events
-
-# We are switching to an empty workpspace on the output to the right from an empty workspace on the output on the left, so we expect
-# to receive "init", "focus", and "empty".
 my $focus = AnyEvent->condvar;
-$i3->subscribe({
-    workspace => sub {
-        my ($event) = @_;
-        if ($event->{change} eq 'focus') {
-            $focus->send($event);
-        }
-    }
-})->recv;
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $focus->send(0);
-    }
-);
-
-cmd 'focus output right';
-
-my $event = $focus->recv;
+my @events = events_for(
+    sub { cmd 'focus output right' },
+    'workspace');
 
 my $current_ws = get_ws(focused_ws);
 
-ok($event, 'Workspace "focus" event received');
-is($event->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace');
-is($event->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace');
+is(scalar @events, 1, 'Received 1 event');
+is($events[0]->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace');
+is($events[0]->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace');
 
 done_testing;
index 217d898ca2f2dbdb202368920dcbf8ee03a847f6..2f7f2b27f897b5168a4fa839071f9d7614bf2430 100644 (file)
@@ -27,51 +27,29 @@ workspace ws-left output fake-0
 workspace ws-right output fake-1
 EOT
 
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-# subscribe to the 'focus' ipc event
-my $focus = AnyEvent->condvar;
-$i3->subscribe({
-    workspace => sub {
-        my ($event) = @_;
-        if ($event->{change} eq 'focus') {
-            $focus->send($event);
-        }
-    }
-})->recv;
-
-# give up after 0.5 seconds
-my $timer = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $focus->send(0);
-    }
-);
-
 # open two windows on the left output
 cmd 'workspace ws-left';
 open_window;
 open_window;
 
-# move a window over to the right output
-cmd 'move right';
-my $event = $focus->recv;
+sub focus_subtest {
+    my ($cmd, $want) = @_;
 
-ok($event, 'moving from workspace with two windows triggered focus ipc event');
-is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
-is(@{$event->{current}->{nodes}}, 1, 'focus event gave the right number of windows on the workspace');
+    my @events = events_for(
+       sub { cmd $cmd },
+       'workspace');
 
-# reset and try again
-$focus = AnyEvent->condvar;
-cmd 'workspace ws-left';
-$focus->recv;
+    my @focus = grep { $_->{change} eq 'focus' } @events;
+    is(scalar @focus, 1, 'Received 1 workspace::focus event');
+    is($focus[0]->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
+    is(@{$focus[0]->{current}->{nodes}}, $want, 'focus event gave the right number of windows on the workspace');
+}
+
+# move a window over to the right output
+subtest 'move right (1)', \&focus_subtest, 'move right', 1;
 
-$focus = AnyEvent->condvar;
-cmd 'move right';
-$event = $focus->recv;
-ok($event, 'moving from workspace with one window triggered focus ipc event');
-is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
-is(@{$event->{current}->{nodes}}, 2, 'focus event gave the right number of windows on the workspace');
+# move another window
+cmd 'workspace ws-left';
+subtest 'move right (2)', \&focus_subtest, 'move right', 2;
 
 done_testing;
index ff34328c6c35ee2ecbc727b07dd6aecfbc08d5f0..8755278508e0391563ee8f0ae96ab8db7ff12898 100644 (file)
@@ -30,21 +30,19 @@ bar {
     bindsym button3 focus left
     bindsym button4 focus right
     bindsym button5 focus left
+    bindsym --release button6 focus right
+    bindsym button7 focus left
+    bindsym button7 --release focus right
 }
 EOT
 use i3test::XTEST;
 
-my ($cv, $timer);
-sub reset_test {
-    $cv = AE::cv;
-    $timer = AE::timer(1, 0, sub { $cv->send(0); });
-}
-
 my $i3 = i3(get_socket_path());
 $i3->connect()->recv;
 my $ws = fresh_workspace;
 
-reset_test;
+my $cv = AnyEvent->condvar;
+my $timer = AnyEvent->timer(after => 1, interval => 0, cb => sub { $cv->send(0) });
 $i3->subscribe({
         window => sub {
             my ($event) = @_;
@@ -60,15 +58,13 @@ $i3->subscribe({
         },
     })->recv;
 
-my $con;
-
 sub i3bar_present {
     my ($nodes) = @_;
 
     for my $node (@{$nodes}) {
        my $props = $node->{window_properties};
        if (defined($props) && $props->{class} eq 'i3bar') {
-           return 1;
+           return $node->{window};
        }
     }
 
@@ -80,53 +76,112 @@ sub i3bar_present {
     return i3bar_present(\@children);
 }
 
-if (i3bar_present($i3->get_tree->recv->{nodes})) {
+my $i3bar_window = i3bar_present($i3->get_tree->recv->{nodes});
+if ($i3bar_window) {
     ok(1, 'i3bar present');
 } else {
-    $con = $cv->recv;
+    my $con = $cv->recv;
     ok($con, 'i3bar appeared');
+    $i3bar_window = $con->{window};
 }
 
+diag('i3bar window = ' . $i3bar_window);
+
 my $left = open_window;
 my $right = open_window;
 sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $right->{id}, 'focus is initially on the right container');
-reset_test;
 
-xtest_button_press(1, 3, 3);
-xtest_button_release(1, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $left->{id}, 'button 1 moves focus left');
-reset_test;
-
-xtest_button_press(2, 3, 3);
-xtest_button_release(2, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $right->{id}, 'button 2 moves focus right');
-reset_test;
-
-xtest_button_press(3, 3, 3);
-xtest_button_release(3, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $left->{id}, 'button 3 moves focus left');
-reset_test;
-
-xtest_button_press(4, 3, 3);
-xtest_button_release(4, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $right->{id}, 'button 4 moves focus right');
-reset_test;
+sub focus_subtest {
+    my ($subscribecb, $want, $msg) = @_;
+    my @events = events_for(
+       $subscribecb,
+       'window');
+    my @focus = map { $_->{container}->{window} } grep { $_->{change} eq 'focus' } @events;
+    is_deeply(\@focus, $want, $msg);
+}
 
-xtest_button_press(5, 3, 3);
-xtest_button_release(5, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $left->{id}, 'button 5 moves focus left');
-reset_test;
+subtest 'button 1 moves focus left', \&focus_subtest,
+    sub {
+       xtest_button_press(1, 3, 3);
+       xtest_button_release(1, 3, 3);
+       xtest_sync_with($i3bar_window);
+    },
+    [ $left->{id} ],
+    'button 1 moves focus left';
+
+subtest 'button 2 moves focus right', \&focus_subtest,
+    sub {
+       xtest_button_press(2, 3, 3);
+       xtest_button_release(2, 3, 3);
+       xtest_sync_with($i3bar_window);
+    },
+    [ $right->{id} ],
+    'button 2 moves focus right';
+
+subtest 'button 3 moves focus left', \&focus_subtest,
+    sub {
+       xtest_button_press(3, 3, 3);
+       xtest_button_release(3, 3, 3);
+       xtest_sync_with($i3bar_window);
+    },
+    [ $left->{id} ],
+    'button 3 moves focus left';
+
+subtest 'button 4 moves focus right', \&focus_subtest,
+    sub {
+       xtest_button_press(4, 3, 3);
+       xtest_button_release(4, 3, 3);
+       xtest_sync_with($i3bar_window);
+    },
+    [ $right->{id} ],
+    'button 4 moves focus right';
+
+subtest 'button 5 moves focus left', \&focus_subtest,
+    sub {
+       xtest_button_press(5, 3, 3);
+       xtest_button_release(5, 3, 3);
+       xtest_sync_with($i3bar_window);
+    },
+    [ $left->{id} ],
+    'button 5 moves focus left';
+
+# Test --release flag with bar bindsym.
+# See issue: #3068.
+
+my $old_focus = get_focused($ws);
+subtest 'button 6 does not move focus while pressed', \&focus_subtest,
+    sub {
+        xtest_button_press(6, 3, 3);
+        xtest_sync_with($i3bar_window);
+    },
+    [],
+    'button 6 does not move focus while pressed';
+is(get_focused($ws), $old_focus, 'focus unchanged');
+
+subtest 'button 6 release moves focus right', \&focus_subtest,
+    sub {
+        xtest_button_release(6, 3, 3);
+        xtest_sync_with($i3bar_window);
+    },
+    [ $right->{id} ],
+    'button 6 release moves focus right';
+
+# Test same bindsym button with and without --release.
+
+subtest 'button 7 press moves focus left', \&focus_subtest,
+    sub {
+        xtest_button_press(7, 3, 3);
+        xtest_sync_with($i3bar_window);
+    },
+    [ $left->{id} ],
+    'button 7 press moves focus left';
+
+subtest 'button 7 release moves focus right', \&focus_subtest,
+    sub {
+        xtest_button_release(7, 3, 3);
+        xtest_sync_with($i3bar_window);
+    },
+    [ $right->{id} ],
+    'button 7 release moves focus right';
 
 done_testing;
diff --git a/testcases/t/539-disable_focus_wrapping.t b/testcases/t/539-disable_focus_wrapping.t
new file mode 100644 (file)
index 0000000..8d2e847
--- /dev/null
@@ -0,0 +1,51 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests that focus does not wrap when focus_wrapping is disabled in
+# the configuration.
+# Ticket: #2352
+# Bug still in: 4.14-72-g6411130c
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_wrapping no
+EOT
+
+sub test_orientation {
+    my ($orientation, $prev, $next) = @_;
+    my $tmp = fresh_workspace;
+
+    cmd "split $orientation";
+
+    my $win1 = open_window;
+    my $win2 = open_window;
+
+    is($x->input_focus, $win2->id, "Second window focused initially");
+    cmd "focus $prev";
+    is($x->input_focus, $win1->id, "First window focused");
+    cmd "focus $prev";
+    is($x->input_focus, $win1->id, "First window still focused");
+    cmd "focus $next";
+    is($x->input_focus, $win2->id, "Second window focused");
+    cmd "focus $next";
+    is($x->input_focus, $win2->id, "Second window still focused");
+}
+
+test_orientation('v', 'up', 'down');
+test_orientation('h', 'left', 'right');
+
+done_testing;
diff --git a/testcases/t/540-sigterm-cleanup.t b/testcases/t/540-sigterm-cleanup.t
new file mode 100644 (file)
index 0000000..5e5b9bf
--- /dev/null
@@ -0,0 +1,35 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests that the socket file is cleaned up properly after gracefully
+# shutting down i3 via SIGTERM.
+# Ticket: #3049
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config, dont_add_socket_path => 1);
+my $socket = get_socket_path();
+ok(-S $socket, "socket $socket exists");
+
+exit_forcefully($pid, 'TERM');
+
+ok(!-e $socket, "socket $socket no longer exists");
+
+done_testing;
diff --git a/testcases/t/541-resize-set-tiling.t b/testcases/t/541-resize-set-tiling.t
new file mode 100644 (file)
index 0000000..82267ba
--- /dev/null
@@ -0,0 +1,147 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests resizing tiling containers
+use i3test;
+
+############################################################
+# resize horizontally
+############################################################
+
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+
+my $left = open_window;
+my $right = open_window;
+
+diag("left = " . $left->id . ", right = " . $right->id);
+
+is($x->input_focus, $right->id, 'Right window focused');
+
+cmd 'resize set 75 ppt 0 ppt';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.25, 'left window got only 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'right window got 75%');
+
+############################################################
+# resize vertically
+############################################################
+
+my $tmp = fresh_workspace;
+
+cmd 'split v';
+
+my $top = open_window;
+my $bottom = open_window;
+
+diag("top = " . $top->id . ", bottom = " . $bottom->id);
+
+is($x->input_focus, $bottom->id, 'Bottom window focused');
+
+cmd 'resize set 0 ppt 75 ppt';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+
+
+############################################################
+# resize horizontally and vertically
+############################################################
+
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+my $left = open_window;
+my $top_right = open_window;
+cmd 'split v';
+my $bottom_right = open_window;
+
+diag("left = " . $left->id . ", top-right = " . $top_right->id . ", bottom-right = " . $bottom_right->id);
+
+is($x->input_focus, $bottom_right->id, 'Bottom-right window focused');
+
+cmd 'resize set 75 ppt 75 ppt';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
+cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.25, 'top-right window got 25%');
+cmp_float($nodes->[1]->{nodes}->[1]->{percent}, 0.75, 'bottom-right window got 75%');
+
+
+############################################################
+# resize from inside a tabbed container
+############################################################
+
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+
+my $left = open_window;
+my $right1 = open_window;
+
+cmd 'split h';
+cmd 'layout tabbed';
+
+my $right2 = open_window;
+
+diag("left = " . $left->id . ", right1 = " . $right1->id . ", right2 = " . $right2->id);
+
+is($x->input_focus, $right2->id, '2nd right window focused');
+
+cmd 'resize set 75 ppt 0 ppt';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
+
+
+############################################################
+# resize from inside a stacked container
+############################################################
+
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+
+my $left = open_window;
+my $right1 = open_window;
+
+cmd 'split h';
+cmd 'layout stacked';
+
+my $right2 = open_window;
+
+diag("left = " . $left->id . ", right1 = " . $right1->id . ", right2 = " . $right2->id);
+
+is($x->input_focus, $right2->id, '2nd right window focused');
+
+cmd 'resize set 75 ppt 0 ppt';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
+
+
+done_testing;
diff --git a/travis/clang-analyze.sh b/travis/clang-analyze.sh
deleted file mode 100755 (executable)
index 5ef390d..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-
-set -e
-set -x
-
-mkdir -p deb/DIST-clang/build
-tar xf build/*.tar.bz2 -C deb/DIST-clang --strip-components=1
-(cd deb/DIST-clang/build && scan-build -o ../../CLANG ../configure && scan-build -o ../../CLANG --html-title="Analysis of i3 v$(git describe --tags)" make -j8)
-mv deb/CLANG/*/* deb/CLANG
index f86fbf22eff19d778cae8ac767e43870a548a871..2d4548208b73a13b82370a71ec871173349306b7 100755 (executable)
@@ -6,7 +6,6 @@ set -x
 GITVERSION=$(git describe --tags)
 
 mkdir build.i3wm.org
-cp -r deb/CLANG build.i3wm.org/clang-analyze
 cp -r deb/COPY-DOCS build.i3wm.org/docs
 cd build.i3wm.org
 echo build.i3wm.org > CNAME
index 44df81d2ffbdf257fdb0c428999290c56c711db5..eac2ea8a37715f340f27e17969419ff55bceb4b0 100755 (executable)
@@ -26,7 +26,7 @@ fi
 
 # Try running the tests in parallel so that the common case (tests pass) is
 # quick, but fall back to running them in sequence to make debugging easier.
-if ! xvfb-run make check
+if ! make check
 then
-       xvfb-run ./testcases/complete-run.pl --parallel=1 || (cat latest/complete-run.log; false)
+       ./testcases/complete-run.pl --parallel=1 || (cat latest/complete-run.log; false)
 fi
index 5704d8e4636587fd0ee21dcbf7e477da6b27a293..7eafb9fb12dcf6f3750a5012a2af30e8541c0ea9 100644 (file)
@@ -19,7 +19,7 @@ RUN apt-get update && \
     dpkg-dev devscripts git equivs \
     clang clang-format-3.8 \
     lintian \
-    libmodule-install-perl libanyevent-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libjson-xs-perl && \
+    libmodule-install-perl libanyevent-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libjson-xs-perl x11-xserver-utils && \
     rm -rf /var/lib/apt/lists/*
 
 # Install i3 build dependencies.