]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'next' into master
authorMichael Stapelberg <michael@stapelberg.de>
Sun, 4 Nov 2018 13:47:46 +0000 (14:47 +0100)
committerMichael Stapelberg <michael@stapelberg.de>
Sun, 4 Nov 2018 13:47:46 +0000 (14:47 +0100)
169 files changed:
.github/ISSUE_TEMPLATE.md
.github/ISSUE_TEMPLATE/bug_report.md [new file with mode: 0644]
.github/ISSUE_TEMPLATE/feature_request.md [new file with mode: 0644]
.travis.yml
AnyEvent-I3/lib/AnyEvent/I3.pm
DEPENDS
I3_VERSION
Makefile.am
README.md
RELEASE-NOTES-4.15 [deleted file]
RELEASE-NOTES-4.16 [new file with mode: 0644]
configure.ac
contrib/dump-asy.pl
debian/changelog
debian/control
docs/debugging
docs/i3bar-protocol
docs/ipc
docs/layout-saving
docs/userguide
etc/config
etc/config.keycodes
generate-command-parser.pl
i3-config-wizard/main.c
i3-input/main.c
i3-msg/main.c
i3-nagbar/main.c
i3-save-tree
i3-sensible-terminal
i3bar/include/child.h
i3bar/include/configuration.h
i3bar/include/xcb.h
i3bar/src/child.c
i3bar/src/config.c
i3bar/src/ipc.c
i3bar/src/main.c
i3bar/src/workspaces.c
i3bar/src/xcb.c
include/all.h
include/atoms_NET_SUPPORTED.xmacro
include/atoms_rest.xmacro
include/commands.h
include/commands_parser.h
include/con.h
include/config_directives.h
include/config_parser.h
include/configuration.h
include/data.h
include/ewmh.h
include/floating.h
include/i3/ipc.h
include/ipc.h
include/libi3.h
include/match.h
include/move.h
include/output.h
include/randr.h
include/render.h
include/resize.h
include/scratchpad.h
include/shmlog.h
include/startup.h
include/sync.h [new file with mode: 0644]
include/tree.h
include/util.h
include/workspace.h
include/x.h
include/xcb.h
libi3/dpi.c
libi3/draw_util.c
libi3/font.c
libi3/format_placeholders.c
libi3/g_utf8_make_valid.c [new file with mode: 0644]
libi3/get_colorpixel.c
libi3/get_config_path.c
libi3/is_debug_build.c
libi3/mkdirp.c
libi3/resolve_tilde.c
libi3/safewrappers.c
libi3/string.c
libi3/ucs2_conversion.c
man/i3-config-wizard.man
man/i3-input.man
man/i3-msg.man
man/i3-nagbar.man
man/i3-sensible-editor.man
man/i3-sensible-pager.man
man/i3-sensible-terminal.man
man/i3.man
parser-specs/commands.spec
parser-specs/config.spec
release.sh
src/assignments.c
src/bindings.c
src/click.c
src/commands.c
src/commands_parser.c
src/con.c
src/config.c
src/config_directives.c
src/config_parser.c
src/ewmh.c
src/floating.c
src/handlers.c
src/ipc.c
src/load_layout.c
src/main.c
src/manage.c
src/match.c
src/move.c
src/output.c
src/randr.c
src/render.c
src/resize.c
src/scratchpad.c
src/sighandler.c
src/startup.c
src/sync.c [new file with mode: 0644]
src/tree.c
src/util.c
src/workspace.c
src/x.c
testcases/inject_randr1.5.c
testcases/lib/i3test.pm.in
testcases/t/113-urgent.t
testcases/t/132-move-workspace.t
testcases/t/134-invalid-command.t
testcases/t/135-floating-focus.t
testcases/t/141-resize.t
testcases/t/156-fullscreen-focus.t
testcases/t/158-wm_take_focus.t
testcases/t/169-border-toggle.t
testcases/t/185-scratchpad.t
testcases/t/187-commands-parser.t
testcases/t/189-floating-constraints.t
testcases/t/201-config-parser.t
testcases/t/232-cmd-move-criteria.t
testcases/t/240-focus-on-window-activation.t
testcases/t/242-no-focus.t
testcases/t/243-move-to-mark.t
testcases/t/245-move-position-mouse.t
testcases/t/251-command-criteria-focused.t
testcases/t/252-floating-size.t
testcases/t/253-multiple-net-wm-state-atoms.t
testcases/t/258-keypress-release.t
testcases/t/276-ipc-window-move.t
testcases/t/285-sticky.t
testcases/t/291-swap.t
testcases/t/293-focus-follows-mouse.t
testcases/t/294-focus-order.t
testcases/t/295-net-wm-state-focused.t [new file with mode: 0644]
testcases/t/296-regress-focus-behind-fullscreen-floating.t [new file with mode: 0644]
testcases/t/297-assign-workspace-to-output.t [new file with mode: 0644]
testcases/t/297-scroll-tabbed.t [new file with mode: 0644]
testcases/t/298-ipc-misbehaving-connection.t [new file with mode: 0644]
testcases/t/299-regress-scratchpad-focus.t [new file with mode: 0644]
testcases/t/300-restart-non-utf8.t [new file with mode: 0644]
testcases/t/516-move.t
testcases/t/518-interpret-workspace-numbers.t
testcases/t/522-rename-assigned-workspace.t
testcases/t/525-i3bar-mouse-bindings.t
testcases/t/529-net-wm-desktop.t
testcases/t/530-bug-2229.t
testcases/t/541-resize-set-tiling.t
travis/check-formatting.sh
travis/travis-base-386.Dockerfile
travis/travis-base-ubuntu-386.Dockerfile
travis/travis-base-ubuntu.Dockerfile
travis/travis-base.Dockerfile

index a9cfbd4769e7500491702a3fc6c57ba477ca1aa4..f2c55972ec872695bbb869f220a2865b2d6935b7 100644 (file)
@@ -1,18 +1,68 @@
-Output of `i3 --moreversion 2>&- || i3 --version`:
+<!--
+PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
+-->
 
-_REPLACE: i3 version output_
+## I'm submitting a…
+<!-- Please check one of the following options with "x" -->
+<pre>
+[ ] Bug
+[ ] Feature Request
+[ ] Documentation Request
+[ ] Other (Please describe in detail)
+</pre>
 
-URL to a logfile as per https://i3wm.org/docs/debugging.html:
+## Current Behavior
+<!--
+Describe the current behavior,
+e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
+-->
 
-_REPLACE: URL to logfile_
+## Expected Behavior
+<!--
+Describe the desired behavior you expect after mitigation of the issue,
+e.g., »The window left next to the current window should be focused.«
+-->
 
-**What I did:**
+## Reproduction Instructions
+<!--
+For bug reports, please provide detailed instructions on how the bug can be reproduced.
+For feature requests you can remove this section.
 
-_REPLACE: e.g. "I’m pressing Alt+j (focus left)"_
+E.g., »Open three windows in a V[A H[B C]] layout on a new workspace«
+-->
 
-**What I saw:**
+## Environment
+<!--
+Please include your exact i3 version.
+Note that we only support the latest major release and the current development version. If you are using an older version of i3, please first update to the current release version and reproduce the issue there.
+-->
+Output of `i3 --moreversion 2>&-`:
+<pre>
+i3 version: 
+</pre>
 
-_REPLACE: e.g. "i3 changed focus to the window ABOVE the current window"_
+<!--
+For bug reports, please include your (complete) i3 config with which the issue occurs. You can either paste the file directly or provide a link to a service such as pastebin.
 
-**What I expected instead:**
-_REPLACE: e.g. "Focus should be on the window to the left"_
+If you would like to help debugging the issue, please try to reduce the config such that it is as close to the default config as possible while still reproducing the issue. This can help us bisect the root cause.
+-->
+<pre>
+</pre>
+
+<!--
+Providing a logfile can help us trace the root cause of an issue much quicker. You can learn how to generate the logfile here:
+https://i3wm.org/docs/debugging.html
+
+Providing the logfile is optional.
+-->
+<pre>
+Logfile URL:
+</pre>
+
+<!--
+Please also answer the questions below to help us process your issue faster. If you have any other information to share, please add it here as well.
+-->
+<pre>
+- Linux Distribution & Version:
+- Are you using a compositor (e.g., xcompmgr or compton):
+</pre>
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644 (file)
index 0000000..910b0fc
--- /dev/null
@@ -0,0 +1,71 @@
+---
+name: Bug report
+about: Create a report to help us improve
+---
+
+<!--
+PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
+-->
+
+## I'm submitting a…
+<!-- Please check one of the following options with "x" -->
+<pre>
+[x] Bug
+[ ] Feature Request
+[ ] Documentation Request
+[ ] Other (Please describe in detail)
+</pre>
+
+## Current Behavior
+<!--
+Describe the current behavior,
+e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
+-->
+
+## Expected Behavior
+<!--
+Describe the desired behavior you expect after mitigation of the issue,
+e.g., »The window left next to the current window should be focused.«
+-->
+
+## Reproduction Instructions
+<!--
+Please provide detailed instructions on how the bug can be reproduced.
+E.g., »Open three windows in a V[A H[B C]] layout on a new workspace«
+-->
+
+## Environment
+<!--
+Please include your exact i3 version.
+Note that we only support the latest major release and the current development version. If you are using an older version of i3, please first update to the current release version and reproduce the issue there.
+-->
+Output of `i3 --moreversion 2>&-`:
+<pre>
+i3 version: 
+</pre>
+
+<!--
+Please include your (complete) i3 config with which the issue occurs. You can either paste the file directly or provide a link to a service such as pastebin.
+
+If you would like to help debugging the issue, please try to reduce the config such that it is as close to the default config as possible while still reproducing the issue. This can help us bisect the root cause.
+-->
+<pre>
+</pre>
+
+<!--
+Providing a logfile can help us trace the root cause of an issue much quicker. You can learn how to generate the logfile here:
+https://i3wm.org/docs/debugging.html
+
+Providing the logfile is optional.
+-->
+<pre>
+Logfile URL:
+</pre>
+
+<!--
+Please also answer the questions below to help us process your issue faster. If you have any other information to share, please add it here as well.
+-->
+<pre>
+- Linux Distribution & Version:
+- Are you using a compositor (e.g., xcompmgr or compton):
+</pre>
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644 (file)
index 0000000..e1c169a
--- /dev/null
@@ -0,0 +1,47 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+---
+
+<!--
+PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
+-->
+
+## I'm submitting a…
+<!-- Please check one of the following options with "x" -->
+<pre>
+[ ] Bug
+[x] Feature Request
+[ ] Documentation Request
+[ ] Other (Please describe in detail)
+</pre>
+
+## Current Behavior
+<!--
+Describe the current behavior,
+e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
+-->
+
+## Desired Behavior
+<!--
+Describe the desired behavior you expect after mitigation of the issue,
+e.g., »The window left next to the current window should be focused.«
+-->
+
+## Environment
+<!--
+Please include your exact i3 version.
+Note that we only support the latest major release and the current development version. If you are using an older version of i3, please first update to the current release version and reproduce the issue there.
+-->
+Output of `i3 --moreversion 2>&-`:
+<pre>
+i3 version: 
+</pre>
+
+<!--
+Please also answer the questions below to help us process your issue faster. If you have any other information to share, please add it here as well.
+-->
+<pre>
+- Linux Distribution & Version:
+- Are you using a compositor (e.g., xcompmgr or compton):
+</pre>
index 87c996fb53bc2c5c29684aa67d00e08ccc3655a1..8098035c84f2f640a135356ac11c3983485f3e1f 100644 (file)
@@ -35,7 +35,7 @@ install:
 script:
   - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-safe-wrappers.sh
   - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-formatting.sh
-  - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'autoreconf -fi && mkdir -p build && cd build && (../configure || (cat config.log; false)) && make -j CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror"'
+  - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'autoreconf -fi && mkdir -p build && cd build && (../configure || (cat config.log; false)) && make -j CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror"'
   - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-spelling.pl
   - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} ./travis/run-tests.sh
   - ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/debian-build.sh deb/debian-amd64/DIST
index 198c41c9a5eef1164a128150ec26496783134e69..ae9e5bea28af297a1613ec967b2fdea1b752676e 100644 (file)
@@ -100,11 +100,12 @@ use constant TYPE_GET_VERSION => 7;
 use constant TYPE_GET_BINDING_MODES => 8;
 use constant TYPE_GET_CONFIG => 9;
 use constant TYPE_SEND_TICK => 10;
+use constant TYPE_SYNC => 11;
 
 our %EXPORT_TAGS = ( 'all' => [
     qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
        TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION
-       TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK)
+       TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK TYPE_SYNC)
 ] );
 
 our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
@@ -534,6 +535,19 @@ sub send_tick {
     $self->message(TYPE_SEND_TICK, $payload);
 }
 
+=head2 sync
+
+Sends an i3 sync event. Requires i3 >= 4.16
+
+=cut
+sub sync {
+    my ($self, $payload) = @_;
+
+    $self->_ensure_connection;
+
+    $self->message(TYPE_SYNC, $payload);
+}
+
 =head2 command($content)
 
 Makes i3 execute the given command
diff --git a/DEPENDS b/DEPENDS
index 1e26afa2bd4cadd5c211ba1f04c11740c88ad389..9a92f99e1193f6912aa33e428b3c3a32ff28c8a7 100644 (file)
--- a/DEPENDS
+++ b/DEPENDS
@@ -4,29 +4,29 @@
    "min" means minimum required version
    "lkgv" means last known good version
 
-┌──────────────┬────────┬────────┬───────────────────────────────────────────────────────────┐
-│ dependency   │ min.   │ lkgv   │ URL                                                       │
-├──────────────┼────────┼────────┼───────────────────────────────────────────────────────────┤
-│ pkg-config   │ 0.25   │ 0.29   │ https://pkgconfig.freedesktop.org/                        │
-│ libxcb       │ 1.1.93 │ 1.12   │ https://xcb.freedesktop.org/dist/                         │
-│ xcb-util     │ 0.3.3  │ 0.4.1  │ https://xcb.freedesktop.org/dist/                         │
-│ xkbcommon    │ 0.4.0  │ 0.6.1  │ https://xkbcommon.org/                                    │
-│ xkbcommon-x11│ 0.4.0  │ 0.6.1  │ https://xkbcommon.org/                                    │
-│ util-cursor³⁴│ 0.0.99 │ 0.1.3  │ https://xcb.freedesktop.org/dist/                         │
-│ util-wm⁴     │ 0.3.8  │ 0.3.8  │ https://xcb.freedesktop.org/dist/                         │
-│ util-keysyms⁴│ 0.3.8  │ 0.4.0  │ https://xcb.freedesktop.org/dist/                         │
-│ util-xrm⁴    │ 1.0.0  │ 1.0.0  │ https://github.com/Airblader/xcb-util-xrm                 │
-│ libev        │ 4.0    │ 4.19   │ http://libev.schmorp.de/                                  │
-│ yajl         │ 2.0.1  │ 2.1.0  │ https://lloyd.github.com/yajl/                            │
-│ asciidoc     │ 8.3.0  │ 8.6.9  │ http://www.methods.co.nz/asciidoc/                        │
-│ xmlto        │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/                        │
-│ Pod::Simple² │ 3.22   │ 3.22   │ http://search.cpan.org/~dwheeler/Pod-Simple-3.23/         │
-│ docbook-xml  │ 4.5    │ 4.5    │ http://www.methods.co.nz/asciidoc/                        │
-│ PCRE         │ 8.12   │ 8.38   │ https://www.pcre.org/                                     │
-│ libsn¹       │ 0.10   │ 0.12   │ https://freedesktop.org/wiki/Software/startup-notification │
-│ pango        │ 1.30.0 | 1.40.1 │ http://www.pango.org/                                     │
-│ cairo        │ 1.14.4 │ 1.14.6 │ https://cairographics.org/                                │
-└──────────────┴────────┴────────┴───────────────────────────────────────────────────────────┘
\94\8câ\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¬â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¬â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¬â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\90
+│ dependency   │ min.   │ lkgv   │ URL                                                         
\94\9câ\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¼â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¼â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¼â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¤
+│ pkg-config   │ 0.25   │ 0.29   │ https://pkgconfig.freedesktop.org/                          
+│ libxcb       │ 1.1.93 │ 1.12   │ https://xcb.freedesktop.org/dist/                           
+│ xcb-util     │ 0.3.3  │ 0.4.1  │ https://xcb.freedesktop.org/dist/                           
+│ xkbcommon    │ 0.4.0  │ 0.6.1  │ https://xkbcommon.org/                                      
+│ xkbcommon-x11│ 0.4.0  │ 0.6.1  │ https://xkbcommon.org/                                      
+│ util-cursor³⁴│ 0.0.99 │ 0.1.3  │ https://xcb.freedesktop.org/dist/                           
+│ util-wm⁴     │ 0.3.8  │ 0.3.8  │ https://xcb.freedesktop.org/dist/                           
+│ util-keysyms⁴│ 0.3.8  │ 0.4.0  │ https://xcb.freedesktop.org/dist/                           
+│ util-xrm⁴    │ 1.0.0  │ 1.0.0  │ https://github.com/Airblader/xcb-util-xrm                 │
+│ libev        │ 4.0    │ 4.19   │ http://libev.schmorp.de/                                    
+│ yajl         │ 2.0.1  │ 2.1.0  │ https://lloyd.github.com/yajl/                              
+│ asciidoc     │ 8.3.0  │ 8.6.9  │ http://www.methods.co.nz/asciidoc/                          
+│ xmlto        │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/                          
+│ Pod::Simple² │ 3.22   │ 3.22   │ http://search.cpan.org/dist/Pod-Simple/                     │
+│ docbook-xml  │ 4.5    │ 4.5    │ http://www.methods.co.nz/asciidoc/                          
+│ PCRE         │ 8.12   │ 8.38   │ https://www.pcre.org/                                       
+│ libsn¹       │ 0.10   │ 0.12   │ https://freedesktop.org/wiki/Software/startup-notification/ │
+│ pango        │ 1.30.0 │ 1.40.1 │ http://www.pango.org/                                       │
+│ cairo        │ 1.14.4 │ 1.14.6 │ https://cairographics.org/                                  
\94\94â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94´â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94´â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94´â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\98
  ¹ libsn = libstartup-notification
  ² Pod::Simple is a Perl module required for converting the testsuite
    documentation to HTML. See https://michael.stapelberg.de/cpan/#Pod::Simple
index 0d5ece5828451b7849b41ede6dd28ef00a076958..a39209b260224f5d4a7c384abc7496202877afe1 100644 (file)
@@ -1 +1 @@
-4.15-non-git
+4.16-non-git
index 184b07343f8cc0b143c1eeaa8feba3d969b1b74c..537fc6a00e04e64d4ba413f4b8ec3b377c395b2b 100644 (file)
@@ -118,7 +118,7 @@ EXTRA_DIST = \
        I3_VERSION \
        LICENSE \
        PACKAGE-MAINTAINER \
-       RELEASE-NOTES-4.15 \
+       RELEASE-NOTES-4.16 \
        generate-command-parser.pl \
        parser-specs/commands.spec \
        parser-specs/config.spec \
@@ -301,6 +301,7 @@ libi3_a_SOURCES = \
        libi3/fake_configure_notify.c \
        libi3/font.c \
        libi3/format_placeholders.c \
+       libi3/g_utf8_make_valid.c \
        libi3/get_colorpixel.c \
        libi3/get_config_path.c \
        libi3/get_exe_path.c \
@@ -357,10 +358,12 @@ i3_msg_i3_msg_SOURCES = \
 
 i3_nagbar_i3_nagbar_CFLAGS = \
        $(AM_CFLAGS) \
+       $(LIBSN_CFLAGS) \
        $(libi3_CFLAGS)
 
 i3_nagbar_i3_nagbar_LDADD = \
        $(libi3_LIBS) \
+       $(LIBSN_LIBS) \
        $(XCB_UTIL_CURSOR_LIBS)
 
 i3_nagbar_i3_nagbar_SOURCES = \
@@ -414,10 +417,12 @@ i3bar_i3bar_SOURCES = \
 i3_config_wizard_i3_config_wizard_CFLAGS = \
        $(AM_CFLAGS) \
        $(libi3_CFLAGS) \
+       $(LIBSN_CFLAGS) \
        $(XKBCOMMON_CFLAGS)
 
 i3_config_wizard_i3_config_wizard_LDADD = \
        $(libi3_LIBS) \
+       $(LIBSN_LIBS) \
        $(XCB_UTIL_KEYSYMS_LIBS) \
        $(XKBCOMMON_LIBS)
 
@@ -521,6 +526,7 @@ i3_SOURCES = \
        include/shmlog.h \
        include/sighandler.h \
        include/startup.h \
+       include/sync.h \
        include/tree.h \
        include/util.h \
        include/window.h \
@@ -562,6 +568,7 @@ i3_SOURCES = \
        src/sd-daemon.c \
        src/sighandler.c \
        src/startup.c \
+       src/sync.c \
        src/tree.c \
        src/util.c \
        src/version.c \
index 21b2e24b86bfe501b60a9f741a56c23b673656d7..6ac6754142503da482a3f791965741eba5a5276b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
 =====================================================
 
 [![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)
+[![Issue Stats](https://img.shields.io/github/issues/i3/i3.svg)](https://github.com/i3/i3/issues)
+[![Pull Request Stats](https://img.shields.io/github/issues-pr/i3/i3.svg)](https://github.com/i3/i3/pulls)
 
 i3 is a tiling window manager for X11.
 
diff --git a/RELEASE-NOTES-4.15 b/RELEASE-NOTES-4.15
deleted file mode 100644 (file)
index 0e1f81e..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-
- ┌────────────────────────────┐
- │ 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
diff --git a/RELEASE-NOTES-4.16 b/RELEASE-NOTES-4.16
new file mode 100644 (file)
index 0000000..e5505cb
--- /dev/null
@@ -0,0 +1,145 @@
+
+ ┌────────────────────────────┐
+ │ Release notes for i3 v4.16 │
+ └────────────────────────────┘
+
+This is i3 v4.16. This version is considered stable. All users of i3 are
+strongly encouraged to upgrade.
+
+This release contains a number of assorted fixes and improvements across pretty
+much all individual components of i3.
+
+ ┌────────────────────────────┐
+ │ Changes in i3 v4.16        │
+ └────────────────────────────┘
+
+  • build: add conditionals for building docs/mans
+  • docs/i3bar-protocol: mention skipping blocks with empty full_text
+  • docs/ipc: add window_properties to tree node
+  • docs/layout-saving: clarify JSON non-compliance
+  • docs/userguide: clarify X resource value format
+  • docs/userguide: fix move_to_outputs link
+  • docs/userguide: link workspace_auto_back_and_forth from workspace
+    command
+  • docs/userguide: mention known issues for assign
+  • docs/userguide: use anchor for list_of_commands
+  • docs/userguide: add the default keybinding for focus parent
+  • man/*: fix title markers (for asciidoctor)
+  • man/i3-msg.man: add get_config and send_tick
+  • ipc: kill misbehaving subscribed clients instead of hanging
+  • ipc: introduce the sync IPC command for synchronization with i3bar
+  • ipc: scratchpad show now returns correct success
+  • ipc: send_tick now sets the already-documented “first” field
+  • i3bar-protocol: add modifiers to events sent by i3bar
+  • dump-asy: use Pod::Usage for --help and perldoc
+  • dump-asy: introduce -gv flag to disable opening ghostview
+  • dump-asy: introduce -save flag to store the rendered tree in a file
+  • dump-asy: add marks
+  • dump-asy: include floating containers
+  • i3bar: add --verbose flag
+  • i3bar: make modifier accept combinations (like floating_modifier)
+  • i3-config-wizard: add --modifier flag to allow for headless config
+  • i3-config-wizard: support startup notifications
+  • i3-msg: only print input + error position if they are set
+  • i3-msg: check replies also in quiet mode (-q)
+  • i3-msg: add support for the SUBSCRIBE message type
+  • i3-nagbar: support startup notifications
+  • i3-nagbar: add option for button that runs commands without a terminal
+  • i3-save-tree: exclude unsupported transient_for property
+  • i3-sensible-terminal: add alacritty
+  • i3-sensible-terminal: add hyper
+  • introduce strip_workspace_name alongside strip_workspace_numbers
+  • introduce title_align config directive
+  • “border toggle” now accepts an optional pixel argument
+  • “resize set” now interprets 0 as “no change”
+  • “resize set” now accepts the “width” and “height” keywords
+  • “resize” with pixel values now works for tiling containers
+  • the optional “absolute” method is now silently ignored in “move position”
+    commands, where it did not cause a visible difference anyway
+  • the _NET_WM_STATE_FOCUSED atom is now supported, resulting in e.g.
+    GTK applications displaying the correct window decoration
+  • moving fullscreen containers now moves them across outputs
+  • floating windows can now be used with a geometry of e.g. +1+1, i.e.
+    their top-left corner can be outside any output as long as the window
+    is contained partially by one
+  • prefer floating fullscreen containers when switching focus
+  • moving containers to an active workspace no longer changes focus
+  • the rename workspace command no longer confuses directions (e.g. “left”)
+    with output names
+  • prefer $XDG_CONFIG_HOME/i3/config over ~/.i3/config
+  • allow multiple assignments of workspaces to output
+  • respect maximum size in WM_NORMAL_HINTS
+  • reject requests for WM_STATE_ICONIC, which avoids e.g. wine
+    applications being stuck in paused state
+  • a number of code refactorings and cleanups, some of which tool-assisted
+
+ ┌────────────────────────────┐
+ │ Bugfixes                   │
+ └────────────────────────────┘
+
+  • build: fix static linking
+  • i3bar: fix various memory leaks
+  • i3bar: fix crash when no status_command is provided
+  • i3bar: fix chopping the first character on the very left when using the
+    full width of the output
+  • i3bar: fix relative_x and width properties of click events
+  • i3bar: fix the tray disappearing in some cases when using "tray_output"
+  • fix various memory leaks and memory correctness issues
+  • refocus focused window on FOCUS_IN events for the root window. This
+    fixes incorrect behavior with steam and some tk apps
+  • fix focus bugs when moving unfocused containers
+  • fix incorrect urgent window state edge case
+  • moving an unfocused container from inside a split container to another
+    workspace doesn’t focus siblings
+  • toggling and killing floating windows now maintains focus order
+  • don’t incorrectly focus siblings when scrolling on window decorations
+  • fix crash when moving a container to a marked workspace
+  • fix swap when first is behind a fullscreen window
+  • fix crash when renaming an existing workspace to a name assigned to the
+    focused output
+  • reframe swallowed windows if depth doesn’t match
+  • use detectable autorepeat so that --release bindings are run only when
+    the key is actually released (and not when it is repeated)
+  • fix border artifacts when moving windows
+  • correctly handle bindings for the same mod key with and without --release
+  • reset B_UPON_KEYRELEASE_IGNORE_MODS bindings when switching modes
+  • fix height offset calculation in pango text drawing
+  • fix detection of libiconv on OpenBSD
+  • free workspace assignments on reload
+  • fix mouse position at startup with multiple outputs
+  • no longer allow dragging global fullscreen floating containers
+  • fix rendering artifacts with global fullscreen containers
+  • fix disabling floating for scratchpad windows
+  • fix a crash when renaming an unfocused empty workspace matching an
+    assignment
+  • ensure containers have a size of at least 1px after resize
+  • permit invalid UTF-8 in layout JSON files (e.g. for window titles)
+  • correct invalid UTF-8 characters in window and container titles
+  • fix a crash when moving to a child of a floating container
+  • fix a crash when matching __focused__ with no window open
+  • fix no_focus when only using floating windows
+  • fix max_aspect calculation
+  • moving an unfocused container from another output now maintains
+    the correct focus order
+  • don’t change focus order when swapping containers
+  • correctly update _NET_CURRENT_DESKTOP when moving containers between outputs
+    using the directional move command
+  • don’t produce move events after attempting to directionally move a container
+    towards a direction it can’t go
+  • fix sticky focus when switching to workspace on different output
+
+
+ ┌────────────────────────────┐
+ │ Thanks!                    │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+  Adrian Cybulski, Aestek, Alan Barr, Andriy Yablonskyy, Cassandra Fox,
+  Christian Duerr, Dan Elkouby, downzer0, Elouan Martinet, Felix Buehler,
+  Gravemind, Harry Lawrence, Hritik Vijay, hwangcc23, Ingo Bürk, Joona, Klorax,
+  lasers, Łukasz Adamczak, Martin, Michael Stapelberg, Oliver Graff,
+  Orestis Floros, Soumya, Takashi Iwai, Thomas Fischer, Todd Walton, Tony
+  Crisci, Uli Schlachter, Vivien Didelot
+
+-- Michael Stapelberg, 2018-11-04
index 8dce4f9f7846ffcefb0fabbfce894ce602ed216f..7ae014222d9b370673f940ef9d2cff8b86390ee2 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.15], [https://github.com/i3/i3/issues])
+AC_INIT([i3], [4.16], [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])
@@ -81,9 +81,10 @@ AC_SEARCH_LIBS([floor], [m], , [AC_MSG_FAILURE([cannot find the required floor()
 # libev does not ship with a pkg-config file :(.
 AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_run() function despite trying to link with -lev])])
 
-AC_SEARCH_LIBS([shm_open], [rt])
+AC_SEARCH_LIBS([shm_open], [rt], [], [], [-pthread])
 
-AC_SEARCH_LIBS([iconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])])
+AC_SEARCH_LIBS([iconv_open], [iconv], ,
+AC_SEARCH_LIBS([libiconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])]))
 
 AX_PTHREAD
 
@@ -109,12 +110,27 @@ AC_PROG_MAKE_SET
 AC_PROG_RANLIB
 AC_PROG_LN_S
 
-AC_PATH_PROG([PATH_ASCIIDOC], [asciidoc])
-AC_PATH_PROG([PATH_XMLTO], [xmlto])
-AC_PATH_PROG([PATH_POD2MAN], [pod2man])
-
-AM_CONDITIONAL([BUILD_MANS], [test x$PATH_ASCIIDOC != x && test x$PATH_XMLTO != x && test x$PATH_POD2MAN != x])
-AM_CONDITIONAL([BUILD_DOCS], [test x$PATH_ASCIIDOC != x])
+AC_ARG_ENABLE(docs,
+  AS_HELP_STRING(
+    [--disable-docs],
+    [disable building documentation]),
+  [ax_docs=$enableval],
+  [ax_docs=yes])
+AC_ARG_ENABLE(mans,
+  AS_HELP_STRING(
+    [--disable-mans],
+    [disable building manual pages]),
+  [ax_mans=$enableval],
+  [ax_mans=yes])
+AS_IF([test x$ax_docs = xyes || test x$ax_mans = xyes], [
+  AC_PATH_PROG([PATH_ASCIIDOC], [asciidoc])
+])
+AS_IF([test x$ax_mans = xyes], [
+  AC_PATH_PROG([PATH_XMLTO], [xmlto])
+  AC_PATH_PROG([PATH_POD2MAN], [pod2man])
+])
+AM_CONDITIONAL([BUILD_MANS], [test x$ax_mans = xyes && test x$PATH_ASCIIDOC != x && test x$PATH_XMLTO != x && test x$PATH_POD2MAN != x])
+AM_CONDITIONAL([BUILD_DOCS], [test x$ax_docs = xyes && test x$PATH_ASCIIDOC != x])
 
 AM_PROG_AR
 
index 9bb2db3aa4641385d2f6c7ba05fe8bbe5d5d1924..5af4c72f313763a7fb6a459ba59cada1d273d2c2 100755 (executable)
@@ -1,26 +1,35 @@
 #!/usr/bin/env perl
 # vim:ts=4:sw=4:expandtab
-# renders the layout tree using asymptote
-#
-# ./dump-asy.pl
-#   will render the entire tree
-# ./dump-asy.pl 'name'
-#   will render the tree starting from the node with the specified name,
-#   e.g. ./dump-asy.pl 2 will render workspace 2 and below
 
 use strict;
 use warnings;
 use Data::Dumper;
+use Getopt::Long;
+use Pod::Usage;
 use AnyEvent::I3;
 use File::Temp;
+use File::Spec;
 use File::Basename;
 use v5.10;
 use IPC::Cmd qw[can_run];
 
+my %options = (
+    gv => 1,
+    save => undef,
+    help => 0,
+);
+my $result = GetOptions(
+    "gv!" => \$options{gv},
+    "save=s" => \$options{save},
+    "help|?" => \$options{help},
+);
+
+pod2usage(-verbose => 2, -exitcode => 0) if $options{help};
+
 # prerequisites check so we can be specific about failures caused
 # by not having these tools in the path
 can_run('asy') or die 'Please install asymptote';
-can_run('gv') or die 'Please install gv';
+can_run('gv') or die 'Please install gv' unless !$options{gv};
 
 my $i3 = i3();
 
@@ -37,7 +46,7 @@ sub dump_node {
 
     my $o = ($n->{orientation} eq 'none' ? "u" : ($n->{orientation} eq 'horizontal' ? "h" : "v"));
     my $w = (defined($n->{window}) ? $n->{window} : "N");
-    my $na = ($n->{name} or "[Empty]");
+    my $na = ($n->{name} or ($n->{type} eq "floating_con" ? "[Floating con]" : "[Empty]"));
     $na =~ s/#/\\#/g;
     $na =~ s/\$/\\\$/g;
     $na =~ s/&/\\&/g;
@@ -47,14 +56,16 @@ sub dump_node {
     if (!defined($n->{window})) {
         $type = $n->{layout};
     }
-    my $name = qq|``$na'' ($type)|;
+    my $marks = $n->{marks} ? ' [' . join('][', @{$n->{marks}}) . ']' : '';
+    my $name = qq|``$na'' ($type)$marks|;
 
     print $tmp "TreeNode n" . $n->{id} . " = makeNode(";
 
     print $tmp "n" . $parent->{id} . ", " if defined($parent);
     print $tmp "\"" . $name . "\");\n";
 
-       dump_node($_, $n) for @{$n->{nodes}};
+    dump_node($_, $n) for @{$n->{nodes}};
+    dump_node($_, $n) for @{$n->{floating_nodes}};
 }
 
 sub find_node_with_name {
@@ -82,5 +93,60 @@ say $tmp "draw(n" . $root->{id} . ", (0, 0));";
 close($tmp);
 my $rep = "$tmp";
 $rep =~ s/asy$/eps/;
-my $tmp_dir = dirname($rep);
-system("cd $tmp_dir && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep");
+if ($options{gv}) {
+    my $tmp_dir = dirname($rep);
+    $options{save} = File::Spec->rel2abs($options{save}) if $options{save};
+    chdir($tmp_dir);
+} else {
+    $rep = basename($rep);  # Output in current dir.
+}
+system("asy $tmp");  # Create the .eps file.
+system("gv --scale=-1000 --noresize --widgetless $rep") if $options{gv};
+if ($options{save}) {
+    system("mv $rep ${options{save}}");
+} elsif ($options{gv}) {
+    system("rm $rep");
+}
+system("rm $tmp");
+
+__END__
+
+=head1 NAME
+
+dump-asy.pl - Render the layout tree using asymptote
+
+=head1 SYNOPSIS
+
+dump-asy.pl [workspace]
+
+=head1 EXAMPLE
+
+Render the entire tree, run:
+
+  ./dump-asy.pl
+
+Render the tree starting from the node with the specified name, run:
+
+  ./dump-asy.pl 'name'
+
+Render the entire tree, save in file 'file.eps' and show using gv, run:
+
+  ./dump-asy.pl --save file.eps
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<--gv>
+
+=item B<--no-gv>
+
+Enable or disable showing the result through gv. If disabled, an .eps file will
+be saved in the current working directory. Enabled by default.
+
+=item B<--save>
+
+Save result using the specified file-name. If omitted, no file will be saved
+when using '--gv' or a random name will be used when using '--no-gv'.
+
+=back
index 76fe1577b16a58b353cbb69c35016fe5a2fd5a6a..35b3f9f6d50f3908e96844dc715a114ee6cf0407 100644 (file)
@@ -1,3 +1,9 @@
+i3-wm (4.15.1-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Sat, 10 Mar 2018 17:27:26 +0100
+
 i3-wm (4.15-1) unstable; urgency=medium
 
   * New upstream release.
index 46e35dfea15a44def0cdfe27560739add4ec3d80..71a2599c6418a0dbe2025ea1da6ffb40e63da650 100644 (file)
@@ -40,7 +40,7 @@ Description: metapackage (i3 window manager, screen locker, menu, statusbar)
 
 Package: i3-wm
 Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, x11-utils
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
 Provides: x-window-manager
 Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl (>= 0.12), libjson-xs-perl, rxvt-unicode | x-terminal-emulator
 Description: improved dynamic tiling window manager
index dd26f98d924b3783a79a09fb9b0e64ce15a25d94..562a11f21e5f36993ac494f2405502a8345c387c 100644 (file)
@@ -160,7 +160,8 @@ flood kicks.
 
 == Debugging i3bar
 
-To debug i3bar problems, add +verbose yes+ to all +bar {}+ blocks in your i3
+To debug i3bar problems, use the +--verbose+ commandline parameter,
+or add +verbose yes+ to all +bar {}+ blocks in your i3
 config, reload your config and then restart all i3bar instances like this:
 
 ---------------------------------------------------------------------
index cf86531cc5f399774e983770719245729693c17a..826cae53f018bcc21abfa34a1457de07552bf9bd 100644 (file)
@@ -107,7 +107,7 @@ stop_signal::
        processing.
        The default value (if none is specified) is SIGSTOP.
 cont_signal::
-       Specify to i3bar the signal (as an integer)to send to continue your
+       Specify to i3bar the signal (as an integer) to send to continue your
        processing.
        The default value (if none is specified) is SIGCONT.
 click_events::
@@ -118,7 +118,8 @@ click_events::
 
 full_text::
        The +full_text+ will be displayed by i3bar on the status line. This is the
-       only required key.
+       only required key. If +full_text+ is an empty string, the block will be
+       skipped.
 short_text::
        Where appropriate, the +short_text+ (string) entry should also be
        provided. It will be used in case the status line needs to be shortened
@@ -242,6 +243,9 @@ relative_x, relative_y::
     of the block
 width, height::
     Width and height (in px) of the block
+modifiers::
+    An array of the modifiers active when the click occurred. The order in which
+    modifiers are listed is not guaranteed.
 
 *Example*:
 ------------------------------------------
@@ -249,6 +253,7 @@ width, height::
  "name": "ethernet",
  "instance": "eth0",
  "button": 1,
+ "modifiers": ["Shift", "Mod1"],
  "x": 1320,
  "y": 1400,
  "relative_x": 12,
index 8b767adebffa127bab432c2c9607b57ce491567b..bcf8df1aa4abe5ba1c86fced66f689aba2fd05b4 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -65,6 +65,7 @@ to do that).
 | 8 | +GET_BINDING_MODES+ | <<_binding_modes_reply,BINDING_MODES>> | Gets the names of all currently configured binding modes.
 | 9 | +GET_CONFIG+ | <<_config_reply,CONFIG>> | Returns the last loaded i3 config.
 | 10 | +SEND_TICK+ | <<_tick_reply,TICK>> | Sends a tick event with the specified payload.
+| 11 | +SYNC+ | <<_sync_reply,SYNC>> | Sends an i3 sync event with the specified random value to the specified window.
 |======================================================
 
 So, a typical message could look like this:
@@ -327,6 +328,8 @@ window (integer)::
        This field is set to null for split containers or otherwise empty
        containers. This ID corresponds to what xwininfo(1) and other
        X11-related tools display (usually in hex).
+window_properties (map)::
+       X11 window properties title, instance, class, window_role and transient_for.
 urgent (bool)::
        Whether this container (window, split container, floating container or
        workspace) has the urgency hint set, directly or indirectly. All parent
@@ -422,6 +425,12 @@ JSON dump:
            "width": 1280,
            "height": 782
          },
+         "window_properties": {
+           "class": "Evince",
+           "instance": "evince",
+           "title": "Properties",
+           "transient_for": 52428808
+         },
          "floating_nodes": [],
          "nodes": [
 
@@ -654,6 +663,18 @@ events generated prior to the +SEND_TICK+ message (happened-before relation).
 { "success": true }
 -------------------
 
+[[_sync_reply]]
+=== SYNC reply
+
+The reply is a map containing the "success" member. After the reply was
+received, the https://i3wm.org/docs/testsuite.html#i3_sync[i3 sync message] was
+responded to.
+
+*Example:*
+-------------------
+{ "success": true }
+-------------------
+
 == Events
 
 [[events]]
@@ -672,6 +693,14 @@ program does not want to cope which such kinds of race conditions (an
 event based library may not have a problem here), I suggest you create a
 separate connection to receive events.
 
+If an event message needs to be sent and the socket is not writeable (write
+returns EAGAIN, happens when the socket doesn't have enough buffer space for
+writing new data) then i3 uses a queue system to store outgoing messages for
+each client. This is combined with a timer: if the message queue for a client is
+not empty and no data where successfully written in the past 10 seconds, the
+connection is killed. Practically, this means that your client should try to
+always read events from the socket to avoid having its connection closed.
+
 === Subscribing to events
 
 By sending a message of type SUBSCRIBE with a JSON-encoded array as payload
index e90ecada87fdb35c3c4727d55b86faccaa812e74..f31b5e21ab367b48740d189e5a208a021aba15c4 100644 (file)
@@ -219,13 +219,15 @@ the window which matches any of the criteria. As an example:
 
 A layout file as generated by +i3-save-tree(1)+ is not strictly valid JSON:
 
-1. Layout files contain multiple “JSON documents” on the top level, whereas the
-   JSON standard only allows precisely one “document” (array or hash).
+1. Layout files contain multiple “JSON texts” at the top level. The JSON
+   standard doesn't prohibit this, but in practice most JSON parsers only
+   allow precisely one “text” per document/file, and will mark multiple texts
+   as invalid JSON.
 
-2. Layout files contain comments which are not standardized, but understood by
-   many parsers.
+2. Layout files contain comments which are not allowed by the JSON standard,
+   but are understood by many parsers.
 
-Both deviations from the JSON standard are to make manual editing by humans
+Both of these deviations from the norm are to make manual editing by humans
 easier. In case you are writing a more elaborate tool for manipulating these
 layouts, you can either use a JSON parser that supports these deviations (for
 example libyajl), transform the layout file to a JSON-conforming file, or
index ba314af111a62493477dad54d4f030d743fdd0b4..91060ab2cc6df006f4c23a80665976d0c503041b 100644 (file)
@@ -245,9 +245,11 @@ you open a new terminal, it will open below the current one.
 
 So, how can you open a new terminal window to the *right* of the current one?
 The solution is to use +focus parent+, which will focus the +Parent Container+ of
-the current +Container+. In this case, you would focus the +Vertical Split
-Container+ which is *inside* the horizontally oriented workspace. Thus, now new
-windows will be opened to the right of the +Vertical Split Container+:
+the current +Container+. In default configuration, use +$mod+a+ to navigate one 
++Container+ up the tree (you can repeat this multiple times until you get to the
++Workspace Container+). In this case, you would focus the +Vertical Split Container+
+which is *inside* the horizontally oriented workspace. Thus, now new windows will be
+opened to the right of the +Vertical Split Container+:
 
 image::tree-shot3.png["shot3",title="Focus parent, then open new terminal"]
 
@@ -585,6 +587,16 @@ workspace_layout default|stacking|tabbed
 workspace_layout tabbed
 ---------------------
 
+=== Window title alignment
+
+This option determines the window title's text alignment.
+Default is +left+
+
+*Syntax*:
+---------------------------------------------
+title_align left|center|right
+---------------------------------------------
+
 === Default border style for new windows
 
 This option determines which border style new windows will have. The default is
@@ -730,7 +742,8 @@ resource database to achieve an easily maintainable, consistent color theme
 across many X applications.
 
 Defining a resource will load this resource from the resource database and
-assign its value to the specified variable. A fallback must be specified in
+assign its value to the specified variable. This is done verbatim and the value
+must therefore be in the format that i3 uses. A fallback must be specified in
 case the resource cannot be loaded from the database.
 
 *Syntax*:
@@ -754,14 +767,23 @@ set_from_resource $black i3wm.color0 #000000
 
 To automatically make a specific window show up on a specific workspace, you
 can use an *assignment*. You can match windows by using any criteria,
-see <<command_criteria>>. It is recommended that you match on window classes
-(and instances, when appropriate) instead of window titles whenever possible
-because some applications first create their window, and then worry about
-setting the correct title. Firefox with Vimperator comes to mind. The window
-starts up being named Firefox, and only when Vimperator is loaded does the
-title change. As i3 will get the title as soon as the application maps the
-window (mapping means actually displaying it on the screen), you’d need to have
-to match on 'Firefox' in this case.
+see <<command_criteria>>. The difference between +assign+ and
++for_window <criteria> move to workspace+ is that the former will only be
+executed when the application maps the window (mapping means actually displaying
+it on the screen) but the latter will be executed whenever a window changes its
+properties to something that matches the specified criteria.
+
+Thus, it is recommended that you match on window classes (and instances, when
+appropriate) instead of window titles whenever possible because some
+applications first create their window, and then worry about setting the correct
+title. Firefox with Vimperator comes to mind. The window starts up being named
+Firefox, and only when Vimperator is loaded does the title change. As i3 will
+get the title as soon as the application maps the window, you’d need to have to
+match on 'Firefox' in this case.
+Another known issue is with Spotify, which doesn't set the class hints when
+mapping the window, meaning you'll have to use a +for_window+ rule to assign
+Spotify to a specific workspace.
+Finally, using +assign [tiling]+ and +assign [floating]+ is not supported.
 
 You can also assign a window to show up on a specific output. You can use RandR
 names such as +VGA1+ or names relative to the output with the currently focused
@@ -887,7 +909,7 @@ the second screen and so on).
 
 *Syntax*:
 -------------------------------------
-workspace <workspace> output <output>
+workspace <workspace> output <output1> [output2]…
 -------------------------------------
 
 The 'output' is the name of the RandR output you attach your screen to. On a
@@ -906,12 +928,15 @@ monitor name is “Dell UP2414Q”.
 entire monitor, i3 will still use the entire area of the containing monitor
 rather than that of just the output's.)
 
+You can specify multiple outputs. The first available will be used.
+
 If you use named workspaces, they must be quoted:
 
 *Examples*:
 ---------------------------
 workspace 1 output LVDS1
-workspace 5 output VGA1
+workspace 2 output primary
+workspace 5 output VGA1 LVDS1
 workspace "2: vim" output VGA1
 ---------------------------
 
@@ -991,7 +1016,7 @@ ipc-socket ~/.i3/i3-ipc.sock
 ----------------------------
 
 You can then use the +i3-msg+ application to perform any command listed in
-the next section.
+<<list_of_commands>>.
 
 === Focus follows mouse
 
@@ -1117,6 +1142,7 @@ force_xinerama yes
 Also note that your output names are not descriptive (like +HDMI1+) when using
 Xinerama, instead they are counted up, starting at 0: +xinerama-0+, +xinerama-1+, …
 
+[[workspace_auto_back_and_forth]]
 === Automatic back-and-forth when switching to the current workspace
 
 This configuration directive enables automatic +workspace back_and_forth+ (see
@@ -1594,7 +1620,7 @@ bar {
 }
 ------------------------
 
-=== Strip workspace numbers
+=== Strip workspace numbers/name
 
 Specifies whether workspace numbers should be displayed within the workspace
 buttons. This is useful if you want to have a named workspace that stays in
@@ -1605,11 +1631,15 @@ the form "[n]:[NAME]" will display only the name. You could use this, for
 instance, to display Roman numerals rather than digits by naming your
 workspaces to "1:I", "2:II", "3:III", "4:IV", ...
 
+When +strip_workspace_name+ is set to +yes+, any workspace that has a name of
+the form "[n]:[NAME]" will display only the number.
+
 The default is to display the full name within the workspace button.
 
 *Syntax*:
 ------------------------------
 strip_workspace_numbers yes|no
+strip_workspace_name yes|no
 ------------------------------
 
 *Example*:
@@ -1707,6 +1737,7 @@ bar {
 }
 --------------------------------------
 
+[[list_of_commands]]
 == List of commands
 
 Commands are what you bind to specific keypresses. You can also issue commands
@@ -2025,10 +2056,13 @@ Use the +move+ command to move a container.
 # defaults to 10 pixels.
 move <left|right|down|up> [<px> px]
 
-# Moves the container either to a specific location
-# or to the center of the screen. If 'absolute' is
-# used, it is moved to the center of all outputs.
-move [absolute] position <pos_x> [px] <pos_y> [px]
+# Moves the container to the specified pos_x and pos_y
+# coordinates on the screen.
+move position <pos_x> [px] <pos_y> [px]
+
+# Moves the container to the center of the screen.
+# If 'absolute' is used, it is moved to the center of
+# all outputs.
 move [absolute] position center
 
 # Moves the container to the current position of the
@@ -2113,8 +2147,8 @@ for_window [instance=notepad] sticky enable
 
 To change to a specific workspace, use the +workspace+ command, followed by the
 number or name of the workspace. Pass the optional flag
-+--no-auto-back-and-forth+ to disable <<back_and_forth>> for this specific call
-only.
++--no-auto-back-and-forth+ to disable <<workspace_auto_back_and_forth>> for this
+specific call only.
 
 To move containers to specific workspaces, use +move container to workspace+.
 
@@ -2253,8 +2287,7 @@ See <<move_to_outputs>> for how to move a container/workspace to a different
 RandR output.
 
 [[move_to_outputs]]
-[[_moving_containers_workspaces_to_randr_outputs]]
-=== Moving containers/workspaces to RandR outputs
+=== [[_moving_containers_workspaces_to_randr_outputs]]Moving containers/workspaces to RandR outputs
 
 To move a container to another RandR output (addressed by names like +LVDS1+ or
 +VGA1+) or to a RandR output identified by a specific direction (like +left+,
@@ -2313,20 +2346,21 @@ 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 | ppt] <height> [px | ppt]
+resize set [width] <width> [px | ppt]
+resize set height <height> [px | ppt]
+resize set [width] <width> [px | ppt] [height] <height> [px | ppt]
 -------------------------------------------------------
 
 Direction can either be one of +up+, +down+, +left+ or +right+. Or you can be
-less specific and use +width+ or +height+, in which case i3 will take/give
-space from all the other containers. The optional pixel argument specifies by
-how many pixels a *floating container* should be grown or shrunk (the default
-is 10 pixels). The ppt argument means percentage points and specifies by how
-many percentage points a *tiling container* should be grown or shrunk (the
-default is 10 percentage points).
+less specific and use +width+ or +height+, in which case i3 will take/give space
+from all the other containers. The optional pixel argument specifies by how many
+pixels a container should be grown or shrunk (the default is 10 pixels). The
+optional ppt argument means "percentage points", and if specified it indicates
+that a *tiling container* should be grown or shrunk by that many points, instead
+of by the +px+ value.
 
-Notes about +resize set+: a value of 0 for <width> or <height> means "do
-not resize in this direction", and resizing a tiling container by +px+ is not
-implemented.
+Note about +resize set+: a value of 0 for <width> or <height> means "do not
+resize in this direction".
 
 It is recommended to define bindings for resizing in a dedicated binding mode.
 See <<binding_modes>> and the example in the i3
@@ -2469,7 +2503,9 @@ To change the border of the current client, you can use +border normal+ to use t
 border (including window title), +border pixel 1+ to use a 1-pixel border (no window title)
 and +border none+ to make the client borderless.
 
-There is also +border toggle+ which will toggle the different border styles.
+There is also +border toggle+ which will toggle the different border styles. The
+optional pixel argument can be used to specify the border width when switching
+to the normal and pixel styles.
 
 Note that "pixel" refers to logical pixel. On HiDPI displays, a logical pixel
 may be represented by multiple physical pixels, so +pixel 1+ might not
@@ -2477,8 +2513,8 @@ necessarily translate into a single pixel row wide border.
 
 *Syntax*:
 -----------------------------------------------
-border normal|pixel [<n>]
-border none|toggle
+border normal|pixel|toggle [<n>]
+border none
 
 # legacy syntax, equivalent to "border pixel 1"
 border 1pixel
index 3be9831dd9004e00e3469268dc2cb1e5d2f6678d..da51d5703d441c8d33bbb2a6e9a2c0320aed3796 100644 (file)
@@ -147,7 +147,7 @@ bindsym Mod1+Shift+c reload
 # restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
 bindsym Mod1+Shift+r restart
 # exit i3 (logs you out of your X session)
-bindsym Mod1+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
+bindsym Mod1+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -B 'Yes, exit i3' 'i3-msg exit'"
 
 # resize window (you can also use the mouse for that)
 mode "resize" {
index 2d56876c249b4d39d72fd19743b9d9b105731fe8..6fc19426a53a3b53c14e8f258e167e3889ee215a 100644 (file)
@@ -133,7 +133,7 @@ bindcode $mod+Shift+54 reload
 # restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
 bindcode $mod+Shift+27 restart
 # exit i3 (logs you out of your X session)
-bindcode $mod+Shift+26 exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
+bindcode $mod+Shift+26 exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -B 'Yes, exit i3' 'i3-msg exit'"
 
 # resize window (you can also use the mouse for that)
 mode "resize" {
index 4c45b6ed614f3bb3252566e401d971a9e74f1649..052e4c66338c72076b6e7121eedeec3172d6b63f 100755 (executable)
@@ -77,7 +77,7 @@ for my $line (@lines) {
             ($line =~ /
                 ^\s*                  # skip leading whitespace
                 ([a-z_]+ \s* = \s*|)  # optional identifier
-                (.*?) -> \s*          # token 
+                (.*?) -> \s*          # token
                 (.*)                  # optional action
              /x);
 
index b368921f2a9b896c5b23b343da99f42fd48026f2..4b55665773e39821ed175efdb58df00664d87659 100644 (file)
@@ -48,6 +48,9 @@
 #include <xkbcommon/xkbcommon.h>
 #include <xkbcommon/xkbcommon-x11.h>
 
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-launchee.h>
+
 #include <X11/Xlib.h>
 #include <X11/keysym.h>
 #include <X11/XKBlib.h>
@@ -101,7 +104,7 @@ static struct xkb_keymap *xkb_keymap;
 static uint8_t xkb_base_event;
 static uint8_t xkb_base_error;
 
-static void finish();
+static void finish(void);
 
 #include "GENERATED_config_enums.h"
 
@@ -213,7 +216,7 @@ static const char *get_string(const char *identifier) {
 
 static void clear_stack(void) {
     for (int c = 0; c < 10; c++) {
-        if (stack[c].type == STACK_STR && stack[c].val.str != NULL)
+        if (stack[c].type == STACK_STR)
             free(stack[c].val.str);
         stack[c].identifier = NULL;
         stack[c].val.str = NULL;
@@ -293,6 +296,7 @@ static char *next_state(const cmdp_token *token) {
         }
         sasprintf(&res, "bindsym %s%s%s %s%s\n", (modifiers == NULL ? "" : modrep), (modifiers == NULL ? "" : "+"), str, (release == NULL ? "" : release), get_string("command"));
         clear_stack();
+        free(modrep);
         return res;
     }
 
@@ -478,7 +482,7 @@ static void txt(int col, int row, char *text, color_t fg, color_t bg) {
  * Handles expose events, that is, draws the window contents.
  *
  */
-static int handle_expose() {
+static int handle_expose(void) {
     const color_t black = draw_util_hex_to_color("#000000");
     const color_t white = draw_util_hex_to_color("#FFFFFF");
     const color_t green = draw_util_hex_to_color("#00FF00");
@@ -631,15 +635,13 @@ static void handle_button_press(xcb_button_press_event_t *event) {
         modifier = MOD_Mod1;
         handle_expose();
     }
-
-    return;
 }
 
 /*
  * Creates the config file and tells i3 to reload.
  *
  */
-static void finish() {
+static void finish(void) {
     printf("creating \"%s\"...\n", config_path);
 
     struct xkb_context *xkb_context;
@@ -745,10 +747,12 @@ int main(int argc, char *argv[]) {
     char *pattern = "pango:monospace 8";
     char *patternbold = "pango:monospace bold 8";
     int o, option_index = 0;
+    bool headless_run = false;
 
     static struct option long_options[] = {
         {"socket", required_argument, 0, 's'},
         {"version", no_argument, 0, 'v'},
+        {"modifier", required_argument, 0, 'm'},
         {"limit", required_argument, 0, 'l'},
         {"prompt", required_argument, 0, 'P'},
         {"prefix", required_argument, 0, 'p'},
@@ -756,7 +760,7 @@ int main(int argc, char *argv[]) {
         {"help", no_argument, 0, 'h'},
         {0, 0, 0, 0}};
 
-    char *options_string = "s:vh";
+    char *options_string = "sm:vh";
 
     while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
         switch (o) {
@@ -767,9 +771,18 @@ int main(int argc, char *argv[]) {
             case 'v':
                 printf("i3-config-wizard " I3_VERSION "\n");
                 return 0;
+            case 'm':
+                headless_run = true;
+                if (strcmp(optarg, "alt") == 0)
+                    modifier = MOD_Mod1;
+                else if (strcmp(optarg, "win") == 0)
+                    modifier = MOD_Mod4;
+                else
+                    err(EXIT_FAILURE, "Invalid modifier key %s", optarg);
+                break;
             case 'h':
                 printf("i3-config-wizard " I3_VERSION "\n");
-                printf("i3-config-wizard [-s <socket>] [-v]\n");
+                printf("i3-config-wizard [-s <socket>] [-m win|alt] [-v] [-h]\n");
                 return 0;
         }
     }
@@ -826,12 +839,22 @@ int main(int argc, char *argv[]) {
     modmap_cookie = xcb_get_modifier_mapping(conn);
     symbols = xcb_key_symbols_alloc(conn);
 
+    if (headless_run) {
+        finish();
+        return 0;
+    }
+
 /* Place requests for the atoms we need as soon as possible */
 #define xmacro(atom) \
     xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
 #include "atoms.xmacro"
 #undef xmacro
 
+    /* Init startup notification. */
+    SnDisplay *sndisplay = sn_xcb_display_new(conn, NULL, NULL);
+    SnLauncheeContext *sncontext = sn_launchee_context_new_from_environment(sndisplay, screen);
+    sn_display_unref(sndisplay);
+
     root_screen = xcb_aux_get_screen(conn, screen);
     root = root_screen->root;
 
@@ -864,6 +887,9 @@ int main(int argc, char *argv[]) {
             0, /* back pixel: black */
             XCB_EVENT_MASK_EXPOSURE |
                 XCB_EVENT_MASK_BUTTON_PRESS});
+    if (sncontext) {
+        sn_launchee_context_setup_window(sncontext, win);
+    }
 
     /* Map the window (make it visible) */
     xcb_map_window(conn, win);
@@ -925,6 +951,12 @@ int main(int argc, char *argv[]) {
         exit(-1);
     }
 
+    /* Startup complete. */
+    if (sncontext) {
+        sn_launchee_context_complete(sncontext);
+        sn_launchee_context_unref(sncontext);
+    }
+
     xcb_flush(conn);
 
     xcb_generic_event_t *event;
@@ -937,13 +969,12 @@ int main(int argc, char *argv[]) {
         /* Strip off the highest bit (set if the event is generated) */
         int type = (event->response_type & 0x7F);
 
+        /* TODO: handle mappingnotify */
         switch (type) {
             case XCB_KEY_PRESS:
                 handle_key_press(NULL, conn, (xcb_key_press_event_t *)event);
                 break;
 
-            /* TODO: handle mappingnotify */
-
             case XCB_BUTTON_PRESS:
                 handle_button_press((xcb_button_press_event_t *)event);
                 break;
index efb7b20c2b87a48183e7796427c0e4c60e5349b0..d1a2efd7cab6b485b4652542e2f4d9459fa237b1 100644 (file)
@@ -156,7 +156,7 @@ static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_rel
     return 1;
 }
 
-static void finish_input() {
+static void finish_input(void) {
     char *command = (char *)concat_strings(glyphs_utf8, input_position);
 
     /* count the occurrences of %s in the string */
index 91a714e56637b5df408885509f680ad14e6c4b48..fe1114168c2c7a31ba20f0768a9a9c674f43e6c2 100644 (file)
@@ -95,8 +95,10 @@ static int reply_start_map_cb(void *params) {
 
 static int reply_end_map_cb(void *params) {
     if (!last_reply.success) {
-        fprintf(stderr, "ERROR: Your command: %s\n", last_reply.input);
-        fprintf(stderr, "ERROR:               %s\n", last_reply.errorposition);
+        if (last_reply.input) {
+            fprintf(stderr, "ERROR: Your command: %s\n", last_reply.input);
+            fprintf(stderr, "ERROR:               %s\n", last_reply.errorposition);
+        }
         fprintf(stderr, "ERROR: %s\n", last_reply.error);
     }
     return 1;
@@ -164,16 +166,18 @@ int main(int argc, char *argv[]) {
     uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND;
     char *payload = NULL;
     bool quiet = false;
+    bool monitor = false;
 
     static struct option long_options[] = {
         {"socket", required_argument, 0, 's'},
         {"type", required_argument, 0, 't'},
         {"version", no_argument, 0, 'v'},
         {"quiet", no_argument, 0, 'q'},
+        {"monitor", no_argument, 0, 'm'},
         {"help", no_argument, 0, 'h'},
         {0, 0, 0, 0}};
 
-    char *options_string = "s:t:vhq";
+    char *options_string = "s:t:vhqm";
 
     while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
         if (o == 's') {
@@ -202,25 +206,34 @@ int main(int argc, char *argv[]) {
                 message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
             } else if (strcasecmp(optarg, "send_tick") == 0) {
                 message_type = I3_IPC_MESSAGE_TYPE_SEND_TICK;
+            } else if (strcasecmp(optarg, "subscribe") == 0) {
+                message_type = I3_IPC_MESSAGE_TYPE_SUBSCRIBE;
             } else {
                 printf("Unknown message type\n");
-                printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config, send_tick\n");
+                printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config, send_tick, subscribe\n");
                 exit(EXIT_FAILURE);
             }
         } else if (o == 'q') {
             quiet = true;
+        } else if (o == 'm') {
+            monitor = true;
         } else if (o == 'v') {
             printf("i3-msg " I3_VERSION "\n");
             return 0;
         } else if (o == 'h') {
             printf("i3-msg " I3_VERSION "\n");
-            printf("i3-msg [-s <socket>] [-t <type>] <message>\n");
+            printf("i3-msg [-s <socket>] [-t <type>] [-m] <message>\n");
             return 0;
         } else if (o == '?') {
             exit(EXIT_FAILURE);
         }
     }
 
+    if (monitor && message_type != I3_IPC_MESSAGE_TYPE_SUBSCRIBE) {
+        fprintf(stderr, "The monitor option -m is used with -t SUBSCRIBE exclusively.\n");
+        exit(EXIT_FAILURE);
+    }
+
     /* Use all arguments, separated by whitespace, as payload.
      * This way, you don’t have to do i3-msg 'mark foo', you can use
      * i3-msg mark foo */
@@ -244,9 +257,6 @@ int main(int argc, char *argv[]) {
         err(EXIT_FAILURE, "IPC: write()");
     free(payload);
 
-    if (quiet)
-        return 0;
-
     uint32_t reply_length;
     uint32_t reply_type;
     uint8_t *reply;
@@ -273,8 +283,9 @@ int main(int argc, char *argv[]) {
                 errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
         }
 
-        /* NB: We still fall-through and print the reply, because even if one
-         * command failed, that doesn’t mean that all commands failed. */
+        if (!quiet) {
+            printf("%.*s\n", reply_length, reply);
+        }
     } else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) {
         yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
         yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
@@ -287,12 +298,30 @@ int main(int argc, char *argv[]) {
             case yajl_status_error:
                 errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
         }
+    } else if (reply_type == I3_IPC_REPLY_TYPE_SUBSCRIBE) {
+        do {
+            free(reply);
+            if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
+                if (ret == -1)
+                    err(EXIT_FAILURE, "IPC: read()");
+                exit(1);
+            }
+
+            if (!(reply_type & I3_IPC_EVENT_MASK)) {
+                errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected an event", reply_type);
+            }
 
-        goto exit;
+            if (!quiet) {
+                fprintf(stdout, "%.*s\n", reply_length, reply);
+                fflush(stdout);
+            }
+        } while (monitor);
+    } else {
+        if (!quiet) {
+            printf("%.*s\n", reply_length, reply);
+        }
     }
-    printf("%.*s\n", reply_length, reply);
 
-exit:
     free(reply);
 
     close(sockfd);
index e4628e303452feae75294e0de9208cc26ba5bc8e..4ce7493961caf7ed5e35674cbc441843aed0d1eb 100644 (file)
@@ -32,6 +32,9 @@
 #include <xcb/randr.h>
 #include <xcb/xcb_cursor.h>
 
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-launchee.h>
+
 #include "i3-nagbar.h"
 
 /** This is the equivalent of XC_left_ptr. I’m not sure why xcb doesn’t have a
@@ -52,6 +55,7 @@ typedef struct {
     char *action;
     int16_t x;
     uint16_t width;
+    bool terminal;
 } button_t;
 
 static xcb_window_t win;
@@ -99,10 +103,10 @@ void debuglog(char *fmt, ...) {
 }
 
 /*
- * Starts the given application by passing it through a shell. We use double fork
- * to avoid zombie processes. As the started application’s parent exits (immediately),
- * the application is reparented to init (process-id 1), which correctly handles
- * childs, so we don’t have to do it :-).
+ * Starts the given application by passing it through a shell. We use double
+ * fork to avoid zombie processes. As the started application’s parent exits
+ * (immediately), the application is reparented to init (process-id 1), which
+ * correctly handles children, so we don’t have to do it :-).
  *
  * The shell is determined by looking for the SHELL environment variable. If it
  * does not exist, /bin/sh is used.
@@ -115,7 +119,7 @@ static void start_application(const char *command) {
         setsid();
         if (fork() == 0) {
             /* This is the child */
-            execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void *)NULL);
+            execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL);
             /* not reached */
         }
         exit(0);
@@ -184,7 +188,11 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
     }
 
     char *terminal_cmd;
-    sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path);
+    if (button->terminal) {
+        sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path);
+    } else {
+        terminal_cmd = sstrdup(link_path);
+    }
     printf("argv0 = %s\n", argv0);
     printf("terminal_cmd = %s\n", terminal_cmd);
 
@@ -358,12 +366,13 @@ int main(int argc, char *argv[]) {
         {"version", no_argument, 0, 'v'},
         {"font", required_argument, 0, 'f'},
         {"button", required_argument, 0, 'b'},
+        {"button-no-terminal", required_argument, 0, 'B'},
         {"help", no_argument, 0, 'h'},
         {"message", required_argument, 0, 'm'},
         {"type", required_argument, 0, 't'},
         {0, 0, 0, 0}};
 
-    char *options_string = "b:f:m:t:vh";
+    char *options_string = "b:B:f:m:t:vh";
 
     prompt = i3string_from_utf8("Please do not run this program.");
 
@@ -385,12 +394,14 @@ int main(int argc, char *argv[]) {
                 break;
             case 'h':
                 printf("i3-nagbar " I3_VERSION "\n");
-                printf("i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
+                printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
                 return 0;
             case 'b':
+            case 'B':
                 buttons = srealloc(buttons, sizeof(button_t) * (buttoncnt + 1));
                 buttons[buttoncnt].label = i3string_from_utf8(optarg);
                 buttons[buttoncnt].action = argv[optind];
+                buttons[buttoncnt].terminal = (o == 'b');
                 printf("button with label *%s* and action *%s*\n",
                        i3string_as_utf8(buttons[buttoncnt].label),
                        buttons[buttoncnt].action);
@@ -415,6 +426,11 @@ int main(int argc, char *argv[]) {
 #include "atoms.xmacro"
 #undef xmacro
 
+    /* Init startup notification. */
+    SnDisplay *sndisplay = sn_xcb_display_new(conn, NULL, NULL);
+    SnLauncheeContext *sncontext = sn_launchee_context_new_from_environment(sndisplay, screens);
+    sn_display_unref(sndisplay);
+
     root_screen = xcb_aux_get_screen(conn, screens);
     root = root_screen->root;
 
@@ -484,6 +500,9 @@ int main(int argc, char *argv[]) {
                 XCB_EVENT_MASK_BUTTON_PRESS |
                 XCB_EVENT_MASK_BUTTON_RELEASE,
             cursor});
+    if (sncontext) {
+        sn_launchee_context_setup_window(sncontext, win);
+    }
 
     /* Map the window (make it visible) */
     xcb_map_window(conn, win);
@@ -544,6 +563,12 @@ int main(int argc, char *argv[]) {
     /* Initialize the drawable bar */
     draw_util_surface_init(conn, &bar, win, get_visualtype(root_screen), win_pos.width, win_pos.height);
 
+    /* Startup complete. */
+    if (sncontext) {
+        sn_launchee_context_complete(sncontext);
+        sn_launchee_context_unref(sncontext);
+    }
+
     /* Grab the keyboard to get all input */
     xcb_flush(conn);
 
index 1e56a0452128ffbfd172c636426827ab9d728611..da5e6deddedb4bd1078f2b9c091fd8d9c3a0d626 100755 (executable)
@@ -123,9 +123,7 @@ sub strip_containers {
     delete $tree->{current_border_width} if $tree->{current_border_width} == -1;
 
     for my $key (keys %$tree) {
-        next if exists($allowed_keys{$key});
-
-        delete $tree->{$key};
+        delete $tree->{$key} unless exists($allowed_keys{$key});
     }
 
     for my $key (qw(nodes floating_nodes)) {
@@ -169,7 +167,8 @@ sub dump_containers {
     if (leaf_node($tree)) {
         my $swallows = {};
         for my $property (keys %{$tree->{window_properties}}) {
-            $swallows->{$property} = '^' . quotemeta($tree->{window_properties}->{$property}) . '$';
+            $swallows->{$property} = '^' . quotemeta($tree->{window_properties}->{$property}) . '$'
+                if $property ne 'transient_for';
         }
         $tree->{swallows} = [ $swallows ];
     }
index f1eb256ecdf4c0a6cf9543cd9c47222d3a31412f..c3a2e4e0cfe858ffed3913dbcf82ff6d59b597c3 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 kitty guake tilda; do
+for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper; do
     if command -v "$terminal" > /dev/null 2>&1; then
         exec "$terminal" "$@"
     fi
index 9479fac1df8bdfecd0c005000bfd76b7dd10a6e5..3afed81947efbe4fc2c703b105b768a84b3a4a80 100644 (file)
@@ -31,7 +31,7 @@ typedef struct {
      */
     int stop_signal;
     /**
-     * The signal requested by the client to inform it of theun hidden state of i3bar
+     * The signal requested by the client to inform it of the unhidden state of i3bar
      */
     int cont_signal;
 
@@ -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, int x_rel, int y_rel, int width, int height);
+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, int mods);
index 61cac7f6bdcab344de7cbaaa9dfc5f1a8c4fc1f3..b86da2e040bad6c2191e7cbcd3df3ef1f9cdfad9 100644 (file)
@@ -41,17 +41,18 @@ typedef struct tray_output_t {
 } tray_output_t;
 
 typedef struct config_t {
-    int modifier;
+    uint32_t modifier;
 
     TAILQ_HEAD(bindings_head, binding_t)
     bindings;
 
     position_t position;
-    int verbose;
+    bool verbose;
     struct xcb_color_strings_t colors;
     bool disable_binding_mode_indicator;
     bool disable_ws;
     bool strip_ws_numbers;
+    bool strip_ws_name;
     char *bar_id;
     char *command;
     char *fontname;
index 7783e8778575b26e309c8415c1684985d68eb418..760ebcdb6480b9a2b929833d672f0ad26b6bfb5a 100644 (file)
@@ -60,7 +60,7 @@ int separator_symbol_width;
  * depend on 'config'.
  *
  */
-char *init_xcb_early();
+char *init_xcb_early(void);
 
 /**
  * Initialization which depends on 'config' being usable. Called after the
index 1cd7d512aa0e6c128e66996b508d7696add950c1..7c527dc3707f1de7846614112164cfe244842f68 100644 (file)
@@ -8,6 +8,7 @@
  *
  */
 #include "common.h"
+#include "yajl_utils.h"
 
 #include <stdlib.h>
 #include <unistd.h>
@@ -27,6 +28,8 @@
 #include <yajl/yajl_gen.h>
 #include <paths.h>
 
+#include <xcb/xcb_keysyms.h>
+
 /* Global variables for child_*() */
 i3bar_child child;
 
@@ -133,7 +136,7 @@ finish:
  * Stop and free() the stdin- and SIGCHLD-watchers
  *
  */
-void cleanup(void) {
+static void cleanup(void) {
     if (stdin_io != NULL) {
         ev_io_stop(main_loop, stdin_io);
         FREE(stdin_io);
@@ -400,7 +403,7 @@ static bool read_json_input(unsigned char *input, int length) {
  * in statusline
  *
  */
-void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+static void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
     int rec;
     unsigned char *buffer = get_buffer(watcher, &rec);
     if (buffer == NULL)
@@ -420,7 +423,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
  * whether this is JSON or plain text
  *
  */
-void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
     int rec;
     unsigned char *buffer = get_buffer(watcher, &rec);
     if (buffer == NULL)
@@ -457,7 +460,7 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
  * anymore
  *
  */
-void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
+static void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
     int exit_status = WEXITSTATUS(watcher->rstatus);
 
     ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
@@ -477,7 +480,7 @@ void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
     draw_bars(false);
 }
 
-void child_write_output(void) {
+static void child_write_output(void) {
     if (child.click_events) {
         const unsigned char *output;
         size_t size;
@@ -580,7 +583,7 @@ void start_child(char *command) {
     atexit(kill_child_at_exit);
 }
 
-void child_click_events_initialize(void) {
+static void child_click_events_initialize(void) {
     if (!child.click_events_init) {
         yajl_gen_array_open(gen);
         child_write_output();
@@ -588,15 +591,11 @@ void child_click_events_initialize(void) {
     }
 }
 
-void child_click_events_key(const char *key) {
-    yajl_gen_string(gen, (const unsigned char *)key, strlen(key));
-}
-
 /*
  * Generates a click event, if enabled.
  *
  */
-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) {
+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, int mods) {
     if (!child.click_events) {
         return;
     }
@@ -606,34 +605,52 @@ void send_block_clicked(int button, const char *name, const char *instance, int
     yajl_gen_map_open(gen);
 
     if (name) {
-        child_click_events_key("name");
-        yajl_gen_string(gen, (const unsigned char *)name, strlen(name));
+        ystr("name");
+        ystr(name);
     }
 
     if (instance) {
-        child_click_events_key("instance");
-        yajl_gen_string(gen, (const unsigned char *)instance, strlen(instance));
+        ystr("instance");
+        ystr(instance);
     }
 
-    child_click_events_key("button");
+    ystr("button");
     yajl_gen_integer(gen, button);
 
-    child_click_events_key("x");
+    ystr("modifiers");
+    yajl_gen_array_open(gen);
+    if (mods & XCB_MOD_MASK_SHIFT)
+        ystr("Shift");
+    if (mods & XCB_MOD_MASK_CONTROL)
+        ystr("Control");
+    if (mods & XCB_MOD_MASK_1)
+        ystr("Mod1");
+    if (mods & XCB_MOD_MASK_2)
+        ystr("Mod2");
+    if (mods & XCB_MOD_MASK_3)
+        ystr("Mod3");
+    if (mods & XCB_MOD_MASK_4)
+        ystr("Mod4");
+    if (mods & XCB_MOD_MASK_5)
+        ystr("Mod5");
+    yajl_gen_array_close(gen);
+
+    ystr("x");
     yajl_gen_integer(gen, x);
 
-    child_click_events_key("y");
+    ystr("y");
     yajl_gen_integer(gen, y);
 
-    child_click_events_key("relative_x");
+    ystr("relative_x");
     yajl_gen_integer(gen, x_rel);
 
-    child_click_events_key("relative_y");
+    ystr("relative_y");
     yajl_gen_integer(gen, y_rel);
 
-    child_click_events_key("width");
+    ystr("width");
     yajl_gen_integer(gen, width);
 
-    child_click_events_key("height");
+    ystr("height");
     yajl_gen_integer(gen, height);
 
     yajl_gen_map_close(gen);
index a58b9bf80c06b69425872bfe74eb69602a2d8215..c23256291c665545177c0323fca1cbfafbef0146 100644 (file)
@@ -119,6 +119,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
         return 1;
     }
 
+    /* Kept for backwards compatibility. */
     if (!strcmp(cur_key, "modifier")) {
         DLOG("modifier = %.*s\n", len, val);
         if (len == strlen("none") && !strncmp((const char *)val, "none", strlen("none"))) {
@@ -297,9 +298,17 @@ static int config_boolean_cb(void *params_, int val) {
         return 1;
     }
 
+    if (!strcmp(cur_key, "strip_workspace_name")) {
+        DLOG("strip_workspace_name = %d\n", val);
+        config.strip_ws_name = val;
+        return 1;
+    }
+
     if (!strcmp(cur_key, "verbose")) {
-        DLOG("verbose = %d\n", val);
-        config.verbose = val;
+        if (!config.verbose) {
+            DLOG("verbose = %d\n", val);
+            config.verbose = val;
+        }
         return 1;
     }
 
@@ -330,6 +339,12 @@ static int config_integer_cb(void *params_, long long val) {
         return 1;
     }
 
+    if (!strcmp(cur_key, "modifier")) {
+        DLOG("modifier = %lld\n", val);
+        config.modifier = (uint32_t)val;
+        return 1;
+    }
+
     return 0;
 }
 
index cc3563ec0ad0039cc4867f79759c55e16a03e504..df5a12cfc73cb0929c68a4b2cd1ae62cf7d66fef 100644 (file)
@@ -34,7 +34,7 @@ typedef void (*handler_t)(char *);
  * Since i3 does not give us much feedback on commands, we do not much
  *
  */
-void got_command_reply(char *reply) {
+static void got_command_reply(char *reply) {
     /* TODO: Error handling for command replies */
 }
 
@@ -42,7 +42,7 @@ void got_command_reply(char *reply) {
  * Called, when we get a reply with workspaces data
  *
  */
-void got_workspace_reply(char *reply) {
+static void got_workspace_reply(char *reply) {
     DLOG("Got workspace data!\n");
     parse_workspaces_json(reply);
     draw_bars(false);
@@ -53,7 +53,7 @@ void got_workspace_reply(char *reply) {
  * Since i3 does not give us much feedback on commands, we do not much
  *
  */
-void got_subscribe_reply(char *reply) {
+static void got_subscribe_reply(char *reply) {
     DLOG("Got subscribe reply: %s\n", reply);
     /* TODO: Error handling for subscribe commands */
 }
@@ -62,7 +62,7 @@ void got_subscribe_reply(char *reply) {
  * Called, when we get a reply with outputs data
  *
  */
-void got_output_reply(char *reply) {
+static void got_output_reply(char *reply) {
     DLOG("Clearing old output configuration...\n");
     free_outputs();
 
@@ -87,7 +87,7 @@ void got_output_reply(char *reply) {
  * Called when we get the configuration for our bar instance
  *
  */
-void got_bar_config(char *reply) {
+static void got_bar_config(char *reply) {
     DLOG("Received bar config \"%s\"\n", reply);
     /* We initiate the main function by requesting infos about the outputs and
      * workspaces. Everything else (creating the bars, showing the right workspace-
@@ -114,20 +114,25 @@ void got_bar_config(char *reply) {
 
 /* Data structure to easily call the reply handlers later */
 handler_t reply_handlers[] = {
-    &got_command_reply,
-    &got_workspace_reply,
-    &got_subscribe_reply,
-    &got_output_reply,
-    NULL,
-    NULL,
-    &got_bar_config,
+    &got_command_reply,   /* I3_IPC_REPLY_TYPE_COMMAND */
+    &got_workspace_reply, /* I3_IPC_REPLY_TYPE_WORKSPACES */
+    &got_subscribe_reply, /* I3_IPC_REPLY_TYPE_SUBSCRIBE */
+    &got_output_reply,    /* I3_IPC_REPLY_TYPE_OUTPUTS */
+    NULL,                 /* I3_IPC_REPLY_TYPE_TREE */
+    NULL,                 /* I3_IPC_REPLY_TYPE_MARKS */
+    &got_bar_config,      /* I3_IPC_REPLY_TYPE_BAR_CONFIG */
+    NULL,                 /* I3_IPC_REPLY_TYPE_VERSION */
+    NULL,                 /* I3_IPC_REPLY_TYPE_BINDING_MODES */
+    NULL,                 /* I3_IPC_REPLY_TYPE_CONFIG */
+    NULL,                 /* I3_IPC_REPLY_TYPE_TICK */
+    NULL,                 /* I3_IPC_REPLY_TYPE_SYNC */
 };
 
 /*
  * Called, when a workspace event arrives (i.e. the user changed the workspace)
  *
  */
-void got_workspace_event(char *event) {
+static void got_workspace_event(char *event) {
     DLOG("Got workspace event!\n");
     i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
 }
@@ -136,7 +141,7 @@ void got_workspace_event(char *event) {
  * Called, when an output event arrives (i.e. the screen configuration changed)
  *
  */
-void got_output_event(char *event) {
+static void got_output_event(char *event) {
     DLOG("Got output event!\n");
     i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
     if (!config.disable_ws) {
@@ -148,7 +153,7 @@ void got_output_event(char *event) {
  * Called, when a mode event arrives (i3 changed binding mode).
  *
  */
-void got_mode_event(char *event) {
+static void got_mode_event(char *event) {
     DLOG("Got mode event!\n");
     parse_mode_json(event);
     draw_bars(false);
@@ -158,7 +163,7 @@ void got_mode_event(char *event) {
  * Called, when a barconfig_update event arrives (i.e. i3 changed the bar hidden_state or mode)
  *
  */
-void got_bar_config_update(char *event) {
+static void got_bar_config_update(char *event) {
     /* check whether this affect this bar instance by checking the bar_id */
     char *expected_id;
     sasprintf(&expected_id, "\"id\":\"%s\"", config.bar_id);
@@ -208,7 +213,7 @@ handler_t event_handlers[] = {
  * Called, when we get a message from i3
  *
  */
-void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
+static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
     DLOG("Got data!\n");
     int fd = watcher->fd;
 
@@ -273,8 +278,8 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
     buffer[size] = '\0';
 
     /* And call the callback (indexed by the type) */
-    if (type & (1 << 31)) {
-        type ^= 1 << 31;
+    if (type & (1UL << 31)) {
+        type ^= 1UL << 31;
         event_handlers[type](buffer);
     } else {
         if (reply_handlers[type])
@@ -304,7 +309,7 @@ int i3_send_msg(uint32_t type, const char *payload) {
     char *buffer = smalloc(to_write);
     char *walk = buffer;
 
-    strncpy(buffer, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC));
+    memcpy(buffer, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC));
     walk += strlen(I3_IPC_MAGIC);
     memcpy(walk, &len, sizeof(uint32_t));
     walk += sizeof(uint32_t);
index 069803d4f944289afac72323bf15acd8d24fae03..a818dd9710eda58c3647a029f612ff5e0bf6146b 100644 (file)
@@ -44,7 +44,7 @@ void debuglog(char *fmt, ...) {
  * Glob path, i.e. expand ~
  *
  */
-char *expand_path(char *path) {
+static char *expand_path(char *path) {
     static glob_t globbuf;
     if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) {
         ELOG("glob() failed\n");
@@ -55,13 +55,14 @@ char *expand_path(char *path) {
     return result;
 }
 
-void print_usage(char *elf_name) {
+static void print_usage(char *elf_name) {
     printf("Usage: %s -b bar_id [-s sock_path] [-h] [-v]\n", elf_name);
     printf("\n");
     printf("-b, --bar_id  <bar_id>\tBar ID for which to get the configuration\n");
     printf("-s, --socket  <sock_path>\tConnect to i3 via <sock_path>\n");
     printf("-h, --help    Display this help message and exit\n");
     printf("-v, --version Display version number and exit\n");
+    printf("-V, --verbose Enable verbose mode\n");
     printf("\n");
     printf(" PLEASE NOTE that i3bar will be automatically started by i3\n"
            " as soon as there is a 'bar' configuration block in your\n"
@@ -75,7 +76,7 @@ void print_usage(char *elf_name) {
  * in main() with that
  *
  */
-void sig_cb(struct ev_loop *loop, ev_signal *watcher, int revents) {
+static void sig_cb(struct ev_loop *loop, ev_signal *watcher, int revents) {
     switch (watcher->signum) {
         case SIGTERM:
             DLOG("Got a SIGTERM, stopping\n");
@@ -106,9 +107,10 @@ int main(int argc, char **argv) {
         {"bar_id", required_argument, 0, 'b'},
         {"help", no_argument, 0, 'h'},
         {"version", no_argument, 0, 'v'},
+        {"verbose", no_argument, 0, 'V'},
         {NULL, 0, 0, 0}};
 
-    while ((opt = getopt_long(argc, argv, "b:s:hv", long_opt, &option_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, "b:s:hvV", long_opt, &option_index)) != -1) {
         switch (opt) {
             case 's':
                 socket_path = expand_path(optarg);
@@ -120,6 +122,9 @@ int main(int argc, char **argv) {
             case 'b':
                 config.bar_id = sstrdup(optarg);
                 break;
+            case 'V':
+                config.verbose = true;
+                break;
             default:
                 print_usage(argv[0]);
                 exit(EXIT_SUCCESS);
index 233249893ecf10fc96e3d598d9cee64779b0837b..7285d1503eb3cf57a3ad2213d4c3659164b40260 100644 (file)
@@ -106,8 +106,8 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
         const char *ws_name = (const char *)val;
         params->workspaces_walk->canonical_name = sstrndup(ws_name, len);
 
-        if (config.strip_ws_numbers && params->workspaces_walk->num >= 0) {
-            /* Special case: strip off the workspace number */
+        if ((config.strip_ws_numbers || config.strip_ws_name) && params->workspaces_walk->num >= 0) {
+            /* Special case: strip off the workspace number/name */
             static char ws_num[10];
 
             snprintf(ws_num, sizeof(ws_num), "%d", params->workspaces_walk->num);
@@ -119,11 +119,14 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
             if (offset && ws_name[offset] == ':')
                 offset += 1;
 
-            /* Offset may be equal to length, in which case display the number */
-            params->workspaces_walk->name = (offset < len
-                                                 ? i3string_from_markup_with_length(ws_name + offset, len - offset)
-                                                 : i3string_from_markup(ws_num));
-
+            if (config.strip_ws_numbers) {
+                /* Offset may be equal to length, in which case display the number */
+                params->workspaces_walk->name = (offset < len
+                                                     ? i3string_from_markup_with_length(ws_name + offset, len - offset)
+                                                     : i3string_from_markup(ws_num));
+            } else {
+                params->workspaces_walk->name = i3string_from_markup(ws_num);
+            }
         } else {
             /* Default case: just save the name */
             params->workspaces_walk->name = i3string_from_markup_with_length(ws_name, len);
index 542c86c3c645a4c117d15f22775d73fb900476f6..31ae08f05c01d9c87d8fb0488a9c83aa1a25cffe 100644 (file)
@@ -79,7 +79,7 @@ int bar_height;
 
 /* These are only relevant for XKB, which we only need for grabbing modifiers */
 int xkb_base;
-int mod_pressed = 0;
+bool mod_pressed = 0;
 
 /* Event watchers, to interact with the user */
 ev_prepare *xcb_prep;
@@ -92,6 +92,9 @@ static mode binding;
 /* Indicates whether a new binding mode was recently activated */
 bool activated_mode = false;
 
+/* The output in which the tray should be displayed. */
+static i3_output *output_for_tray;
+
 /* The parsed colors */
 struct xcb_colors_t {
     color_t bar_fg;
@@ -146,13 +149,13 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
     return 0;
 }
 
-uint32_t get_sep_offset(struct status_block *block) {
+static uint32_t get_sep_offset(struct status_block *block) {
     if (!block->no_separator && block->sep_block_width > 0)
         return block->sep_block_width / 2 + block->sep_block_width % 2;
     return 0;
 }
 
-int get_tray_width(struct tc_head *trayclients) {
+static int get_tray_width(struct tc_head *trayclients) {
     trayclient *trayclient;
     int tray_width = 0;
     TAILQ_FOREACH_REVERSE(trayclient, trayclients, tc_head, tailq) {
@@ -193,7 +196,7 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
     }
 }
 
-uint32_t predict_statusline_length(bool use_short_text) {
+static uint32_t predict_statusline_length(bool use_short_text) {
     uint32_t width = 0;
     struct status_block *block;
 
@@ -245,7 +248,7 @@ uint32_t predict_statusline_length(bool use_short_text) {
 /*
  * Redraws the statusline to the output's statusline_buffer
  */
-void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_colors, bool use_short_text) {
+static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_colors, bool use_short_text) {
     struct status_block *block;
 
     color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
@@ -330,7 +333,7 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color
  * Hides all bars (unmaps them)
  *
  */
-void hide_bars(void) {
+static void hide_bars(void) {
     if ((config.hide_on_modifier == M_DOCK) || (config.hidden_state == S_SHOW && config.hide_on_modifier == M_HIDE)) {
         return;
     }
@@ -349,7 +352,7 @@ void hide_bars(void) {
  * Unhides all bars (maps them)
  *
  */
-void unhide_bars(void) {
+static void unhide_bars(void) {
     if (config.hide_on_modifier != M_HIDE) {
         return;
     }
@@ -457,7 +460,7 @@ static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_relea
  * wheel was used and change the workspace appropriately
  *
  */
-void handle_button(xcb_button_press_event_t *event) {
+static void handle_button(xcb_button_press_event_t *event) {
     /* Determine, which bar was clicked */
     i3_output *walk;
     xcb_window_t bar = event->event;
@@ -500,13 +503,12 @@ void handle_button(xcb_button_press_event_t *event) {
         /* If the child asked for click events,
          * check if a status block has been clicked. */
         int tray_width = get_tray_width(walk->trayclients);
-        int block_x = 0, last_block_x;
-        int offset = walk->rect.w - walk->statusline_width - tray_width - logical_px(sb_hoff_px);
+        int last_block_x = 0;
+        int offset = walk->rect.w - walk->statusline_width - tray_width - logical_px((tray_width > 0) * sb_hoff_px);
         int32_t statusline_x = x - offset;
 
         if (statusline_x >= 0 && statusline_x < walk->statusline_width) {
             struct status_block *block;
-            int sep_offset_remainder = 0;
 
             TAILQ_FOREACH(block, &statusline_head, blocks) {
                 i3String *text = block->full_text;
@@ -519,16 +521,15 @@ void handle_button(xcb_button_press_event_t *event) {
                 if (i3string_get_num_bytes(text) == 0)
                     continue;
 
-                last_block_x = block_x;
-                block_x += render->width + render->x_offset + render->x_append + get_sep_offset(block) + sep_offset_remainder;
-
-                if (statusline_x <= block_x && statusline_x >= last_block_x) {
+                const int relative_x = statusline_x - last_block_x;
+                if (relative_x >= 0 && (uint32_t)relative_x <= render->width) {
                     send_block_clicked(event->detail, block->name, block->instance,
-                                       event->root_x, event->root_y, statusline_x - last_block_x, event->event_y, block_x - last_block_x, bar_height);
+                                       event->root_x, event->root_y, relative_x, event->event_y, render->width, bar_height,
+                                       event->state);
                     return;
                 }
 
-                sep_offset_remainder = block->sep_block_width - get_sep_offset(block);
+                last_block_x += render->width + render->x_append + render->x_offset + block->sep_block_width;
             }
         }
     }
@@ -604,7 +605,7 @@ void handle_button(xcb_button_press_event_t *event) {
 
     const size_t len = namelen + strlen("workspace \"\"") + 1;
     char *buffer = scalloc(len + num_quotes, 1);
-    strncpy(buffer, "workspace \"", strlen("workspace \""));
+    memcpy(buffer, "workspace \"", strlen("workspace \""));
     size_t inpos, outpos;
     for (inpos = 0, outpos = strlen("workspace \"");
          inpos < namelen;
@@ -694,21 +695,12 @@ static void handle_client_message(xcb_client_message_event_t *event) {
     if (event->type == atoms[I3_SYNC]) {
         xcb_window_t window = event->data.data32[0];
         uint32_t rnd = event->data.data32[1];
-        DLOG("[i3 sync protocol] Forwarding random value %d, X11 window 0x%08x to i3\n", rnd, window);
-
-        void *reply = scalloc(32, 1);
-        xcb_client_message_event_t *ev = reply;
-
-        ev->response_type = XCB_CLIENT_MESSAGE;
-        ev->window = window;
-        ev->type = atoms[I3_SYNC];
-        ev->format = 32;
-        ev->data.data32[0] = window;
-        ev->data.data32[1] = rnd;
-
-        xcb_send_event(conn, false, xcb_root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)ev);
-        xcb_flush(conn);
-        free(reply);
+        /* Forward the request to i3 via the IPC interface so that all pending
+         * IPC messages are guaranteed to be handled. */
+        char *payload = NULL;
+        sasprintf(&payload, "{\"rnd\":%d, \"window\":%d}", rnd, window);
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_SYNC, payload);
+        free(payload);
     } else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
                event->format == 32) {
         DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
@@ -770,58 +762,16 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             }
 
             DLOG("X window %08x requested docking\n", client);
-            i3_output *output = NULL;
-            i3_output *walk = NULL;
-            tray_output_t *tray_output = NULL;
-            /* We need to iterate through the tray_output assignments first in
-             * order to prioritize them. Otherwise, if this bar manages two
-             * outputs and both are assigned as tray_output as well, the first
-             * output in our list would receive the tray rather than the first
-             * one defined via tray_output. */
-            TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) {
-                SLIST_FOREACH(walk, outputs, slist) {
-                    if (!walk->active)
-                        continue;
-
-                    if (strcasecmp(walk->name, tray_output->output) == 0) {
-                        DLOG("Found tray_output assignment for output %s.\n", walk->name);
-                        output = walk;
-                        break;
-                    }
 
-                    if (walk->primary && strcasecmp("primary", tray_output->output) == 0) {
-                        DLOG("Found tray_output assignment on primary output %s.\n", walk->name);
-                        output = walk;
-                        break;
-                    }
-                }
-
-                /* If we found an output, we're done. */
-                if (output != NULL)
-                    break;
-            }
-
-            /* If no tray_output has been specified, we fall back to the first
-             * available output. */
-            if (output == NULL && TAILQ_EMPTY(&(config.tray_outputs))) {
-                SLIST_FOREACH(walk, outputs, slist) {
-                    if (!walk->active)
-                        continue;
-                    DLOG("Falling back to output %s because no primary output is configured\n", walk->name);
-                    output = walk;
-                    break;
-                }
-            }
-
-            if (output == NULL) {
-                ELOG("No output found\n");
+            if (output_for_tray == NULL) {
+                ELOG("No output found for tray\n");
                 return;
             }
 
             xcb_void_cookie_t rcookie = xcb_reparent_window(xcb_connection,
                                                             client,
-                                                            output->bar.id,
-                                                            output->rect.w - icon_size - logical_px(config.tray_padding),
+                                                            output_for_tray->bar.id,
+                                                            output_for_tray->rect.w - icon_size - logical_px(config.tray_padding),
                                                             logical_px(config.tray_padding));
             if (xcb_request_failed(rcookie, "Could not reparent window. Maybe it is using an incorrect depth/visual?"))
                 return;
@@ -848,7 +798,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             ev->format = 32;
             ev->data.data32[0] = XCB_CURRENT_TIME;
             ev->data.data32[1] = XEMBED_EMBEDDED_NOTIFY;
-            ev->data.data32[2] = output->bar.id;
+            ev->data.data32[2] = output_for_tray->bar.id;
             ev->data.data32[3] = xe_version;
             xcb_send_event(xcb_connection,
                            0,
@@ -868,7 +818,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             tc->win = client;
             tc->xe_version = xe_version;
             tc->mapped = false;
-            TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
+            TAILQ_INSERT_TAIL(output_for_tray->trayclients, tc, tailq);
 
             if (map_it) {
                 DLOG("Mapping dock client\n");
@@ -1092,7 +1042,7 @@ static void handle_resize_request(xcb_resize_request_event_t *event) {
  * events from X11, handle them, then flush our outgoing queue.
  *
  */
-void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
+static void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
     xcb_generic_event_t *event;
 
     if (xcb_connection_has_error(xcb_connection)) {
@@ -1117,49 +1067,18 @@ void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
             DLOG("received an xkb event\n");
 
             xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event;
-            if (state->xkbType == XCB_XKB_STATE_NOTIFY && config.modifier != XCB_NONE) {
-                int modstate = state->mods & config.modifier;
-
-#define DLOGMOD(modmask, status)                        \
-    do {                                                \
-        switch (modmask) {                              \
-            case ShiftMask:                             \
-                DLOG("ShiftMask got " #status "!\n");   \
-                break;                                  \
-            case ControlMask:                           \
-                DLOG("ControlMask got " #status "!\n"); \
-                break;                                  \
-            case Mod1Mask:                              \
-                DLOG("Mod1Mask got " #status "!\n");    \
-                break;                                  \
-            case Mod2Mask:                              \
-                DLOG("Mod2Mask got " #status "!\n");    \
-                break;                                  \
-            case Mod3Mask:                              \
-                DLOG("Mod3Mask got " #status "!\n");    \
-                break;                                  \
-            case Mod4Mask:                              \
-                DLOG("Mod4Mask got " #status "!\n");    \
-                break;                                  \
-            case Mod5Mask:                              \
-                DLOG("Mod5Mask got " #status "!\n");    \
-                break;                                  \
-        }                                               \
-    } while (0)
-
-                if (modstate != mod_pressed) {
-                    if (modstate == 0) {
-                        DLOGMOD(config.modifier, released);
-                        if (!activated_mode)
-                            hide_bars();
-                    } else {
-                        DLOGMOD(config.modifier, pressed);
+            const uint32_t mod = (config.modifier & 0xFFFF);
+            const bool new_mod_pressed = (mod != 0 && (state->mods & mod) == mod);
+            if (new_mod_pressed != mod_pressed) {
+                mod_pressed = new_mod_pressed;
+                if (state->xkbType == XCB_XKB_STATE_NOTIFY && config.modifier != XCB_NONE) {
+                    if (mod_pressed) {
                         activated_mode = false;
                         unhide_bars();
+                    } else if (!activated_mode) {
+                        hide_bars();
                     }
-                    mod_pressed = modstate;
                 }
-#undef DLOGMOD
             }
 
             free(event);
@@ -1223,7 +1142,7 @@ void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
  * are triggered
  *
  */
-void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+static void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
 }
 
 /*
@@ -1231,7 +1150,7 @@ void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
  * depend on 'config'.
  *
  */
-char *init_xcb_early() {
+char *init_xcb_early(void) {
     /* FIXME: xcb_connect leaks memory */
     xcb_connection = xcb_connect(NULL, &screen);
     if (xcb_connection_has_error(xcb_connection)) {
@@ -1294,7 +1213,7 @@ char *init_xcb_early() {
  * in xcb.
  *
  */
-void register_xkb_keyevents() {
+static void register_xkb_keyevents(void) {
     const xcb_query_extension_reply_t *extreply;
     extreply = xcb_get_extension_data(conn, &xcb_xkb_id);
     if (!extreply->present) {
@@ -1318,7 +1237,7 @@ void register_xkb_keyevents() {
  * Deregister from xkb keyevents.
  *
  */
-void deregister_xkb_keyevents() {
+static void deregister_xkb_keyevents(void) {
     xcb_xkb_select_events(conn,
                           XCB_XKB_ID_USE_CORE_KBD,
                           0,
@@ -1384,7 +1303,7 @@ static void send_tray_clientmessage(void) {
  * atom. Afterwards, tray clients will send ClientMessages to our window.
  *
  */
-void init_tray(void) {
+static void init_tray(void) {
     DLOG("Initializing system tray functionality\n");
     /* request the tray manager atom for the X11 display we are running on */
     char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
@@ -1613,7 +1532,7 @@ void destroy_window(i3_output *output) {
 
 /* Strut partial tells i3 where to reserve space for i3bar. This is determined
  * by the `position` bar config directive. */
-xcb_void_cookie_t config_strut_partial(i3_output *output) {
+static xcb_void_cookie_t config_strut_partial(i3_output *output) {
     /* A local struct to save the strut_partial property */
     struct {
         uint32_t left;
@@ -1655,6 +1574,56 @@ xcb_void_cookie_t config_strut_partial(i3_output *output) {
                                &strut_partial);
 }
 
+/*
+ * Returns the output which should hold the tray, if one exists.
+ *
+ * An output is returned in these scenarios:
+ *   1. A specific output was listed in tray_outputs which is also in the list
+ *   of outputs managed by this bar.
+ *   2. No tray_output directive was specified. In this case, we use the first
+ *   available output.
+ *   3. 'tray_output primary' was specified. In this case we use the primary
+ *   output.
+ *
+ * Three scenarios in which we specifically don't want to use a tray:
+ *   1. 'tray_output none' was specified.
+ *   2. A specific output was listed as a tray_output, but is not one of the
+ *   outputs managed by this bar. For example, consider tray_outputs == [VGA-1],
+ *   but outputs == [HDMI-1].
+ *   3. 'tray_output primary' was specified and no output in the list is
+ *   primary.
+ */
+static i3_output *get_tray_output(void) {
+    i3_output *output = NULL;
+    if (TAILQ_EMPTY(&(config.tray_outputs))) {
+        /* No tray_output specified, use first active output. */
+        SLIST_FOREACH(output, outputs, slist) {
+            if (output->active) {
+                return output;
+            }
+        }
+        return NULL;
+    } else if (strcasecmp(TAILQ_FIRST(&(config.tray_outputs))->output, "none") == 0) {
+        /* Check for "tray_output none" */
+        return NULL;
+    }
+
+    /* If one or more tray_output assignments were specified, we ensure that at
+     * least one of them is actually an output managed by this instance. */
+    tray_output_t *tray_output;
+    TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) {
+        SLIST_FOREACH(output, outputs, slist) {
+            if (output->active &&
+                (strcasecmp(output->name, tray_output->output) == 0 ||
+                 (strcasecmp(tray_output->output, "primary") == 0 && output->primary))) {
+                return output;
+            }
+        }
+    }
+
+    return NULL;
+}
+
 /*
  * Reconfigure all bars and create new bars for recently activated outputs
  *
@@ -1662,7 +1631,6 @@ xcb_void_cookie_t config_strut_partial(i3_output *output) {
 void reconfig_windows(bool redraw_bars) {
     uint32_t mask;
     uint32_t values[6];
-    static bool tray_configured = false;
 
     i3_output *walk;
     SLIST_FOREACH(walk, outputs, slist) {
@@ -1790,58 +1758,6 @@ void reconfig_windows(bool redraw_bars) {
                 exit(EXIT_FAILURE);
             }
 
-            /* Unless "tray_output none" was specified, we need to initialize the tray. */
-            bool no_tray = false;
-            if (!(TAILQ_EMPTY(&(config.tray_outputs)))) {
-                no_tray = strcasecmp(TAILQ_FIRST(&(config.tray_outputs))->output, "none") == 0;
-            }
-
-            /*
-             * There are three scenarios in which we need to initialize the tray:
-             *   1. A specific output was listed in tray_outputs which is also
-             *      in the list of outputs managed by this bar.
-             *   2. No tray_output directive was specified. In this case, we
-             *      use the first available output.
-             *   3. 'tray_output primary' was specified. In this case we use the
-             *      primary output.
-             *
-             * Three scenarios in which we specifically don't want to
-             * initialize the tray are:
-             *   1. 'tray_output none' was specified.
-             *   2. A specific output was listed as a tray_output, but is not
-             *      one of the outputs managed by this bar. For example, consider
-             *      tray_outputs == [VGA-1], but outputs == [HDMI-1].
-             *   3. 'tray_output primary' was specified and no output in the list
-             *      is primary.
-             */
-            if (!tray_configured && !no_tray) {
-                /* If no tray_output was specified, we go ahead and initialize the tray as
-                 * we will be using the first available output. */
-                if (TAILQ_EMPTY(&(config.tray_outputs))) {
-                    init_tray();
-                }
-
-                /* If one or more tray_output assignments were specified, we ensure that at least one of
-                 * them is actually an output managed by this instance. */
-                tray_output_t *tray_output;
-                TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) {
-                    i3_output *output;
-                    bool found = false;
-                    SLIST_FOREACH(output, outputs, slist) {
-                        if (strcasecmp(output->name, tray_output->output) == 0 ||
-                            (strcasecmp(tray_output->output, "primary") == 0 && output->primary)) {
-                            found = true;
-                            init_tray();
-                            break;
-                        }
-                    }
-
-                    if (found)
-                        break;
-                }
-
-                tray_configured = true;
-            }
         } else {
             /* We already have a bar, so we just reconfigure it */
             mask = XCB_CONFIG_WINDOW_X |
@@ -1935,6 +1851,19 @@ void reconfig_windows(bool redraw_bars) {
             }
         }
     }
+
+    /* Finally, check if we want to initialize the tray or destroy the selection
+     * window. The result of get_tray_output() is cached. */
+    output_for_tray = get_tray_output();
+    if (output_for_tray) {
+        if (selwin == XCB_NONE) {
+            init_tray();
+        }
+    } else if (selwin != XCB_NONE) {
+        DLOG("Destroying tray selection window\n");
+        xcb_destroy_window(xcb_connection, selwin);
+        selwin = XCB_NONE;
+    }
 }
 
 /*
@@ -2048,7 +1977,8 @@ void draw_bars(bool unhide) {
             DLOG("Printing statusline!\n");
 
             int tray_width = get_tray_width(outputs_walk->trayclients);
-            uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - 2 * logical_px(sb_hoff_px);
+            uint32_t hoff = logical_px(((workspace_width > 0) + (tray_width > 0)) * sb_hoff_px);
+            uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - hoff;
             uint32_t clip_left = 0;
             uint32_t statusline_width = full_statusline_width;
             bool use_short_text = false;
@@ -2062,7 +1992,7 @@ void draw_bars(bool unhide) {
             }
 
             int16_t visible_statusline_width = MIN(statusline_width, max_statusline_width);
-            int x_dest = outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width;
+            int x_dest = outputs_walk->rect.w - tray_width - logical_px((tray_width > 0) * sb_hoff_px) - visible_statusline_width;
 
             draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text);
             draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,
@@ -2110,5 +2040,4 @@ void set_current_mode(struct mode *current) {
     I3STRING_FREE(binding.name);
     binding = *current;
     activated_mode = binding.name != NULL;
-    return;
 }
index ecc875d08298f6b33b8b03340b84c8d1e33266a3..e93b066bc59f64058dce80ae77f7d775a61a8bc7 100644 (file)
@@ -82,4 +82,5 @@
 #include "fake_outputs.h"
 #include "display_version.h"
 #include "restore_layout.h"
+#include "sync.h"
 #include "main.h"
index a7b9676d9817e21dcd142c6f62fc9606cada2773..a81948a983fde44853e7da19889743a4adb78f96 100644 (file)
@@ -8,6 +8,7 @@ xmacro(_NET_WM_STATE_FULLSCREEN)
 xmacro(_NET_WM_STATE_DEMANDS_ATTENTION)
 xmacro(_NET_WM_STATE_MODAL)
 xmacro(_NET_WM_STATE_HIDDEN)
+xmacro(_NET_WM_STATE_FOCUSED)
 xmacro(_NET_WM_STATE)
 xmacro(_NET_WM_WINDOW_TYPE)
 xmacro(_NET_WM_WINDOW_TYPE_NORMAL)
index d461dc08a1d0e7dcda554e04184d6010f78438d5..b65a81d8154d09d50e2f6ce643e60ae1b335e9e4 100644 (file)
@@ -17,3 +17,4 @@ xmacro(I3_FLOATING_WINDOW)
 xmacro(_NET_REQUEST_FRAME_EXTENTS)
 xmacro(_NET_FRAME_EXTENTS)
 xmacro(_MOTIF_WM_HINTS)
+xmacro(WM_CHANGE_STATE)
index 1057f021db5a3d2aa45034b3557f51071cf03c07..0137460feae0624218af57ec10d2426ab6b5dc6b 100644 (file)
@@ -216,7 +216,7 @@ void cmd_sticky(I3_CMD, const char *action);
  * Implementation of 'move <direction> [<pixels> [px]]'.
  *
  */
-void cmd_move_direction(I3_CMD, const char *direction, long move_px);
+void cmd_move_direction(I3_CMD, const char *direction_str, long move_px);
 
 /**
  * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
@@ -264,7 +264,7 @@ void cmd_focus_output(I3_CMD, const char *name);
  * Implementation of 'move [window|container] [to] [absolute] position <px> [px] <px> [px]
  *
  */
-void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y);
+void cmd_move_window_to_position(I3_CMD, long x, long y);
 
 /**
  * Implementation of 'move [window|container] [to] [absolute] position center
@@ -314,13 +314,13 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name);
  */
 void cmd_bar(I3_CMD, const char *bar_type, const char *bar_value, const char *bar_id);
 
-/*
+/**
  * Implementation of 'shmlog <size>|toggle|on|off'
  *
  */
 void cmd_shmlog(I3_CMD, const char *argument);
 
-/*
+/**
  * Implementation of 'debuglog toggle|on|off'
  *
  */
index 88b3f6d09c6540972b701907fea56dcf071f8642..b65ae93f85c51e16d49806a232bf1d272b7477a8 100644 (file)
@@ -13,7 +13,7 @@
 
 #include <yajl/yajl_gen.h>
 
-/*
+/**
  * Holds an intermediate represenation of the result of a call to any command.
  * When calling parse_command("floating enable, border none"), the parser will
  * internally use this struct when calling cmd_floating and cmd_border.
index 58123a87f719b95518ccd48cc06fb9a462a21b1c..2c991b0cb6ec90b91ccbe82fc314a7f3edfe6e9c 100644 (file)
@@ -20,7 +20,8 @@
  */
 Con *con_new_skeleton(Con *parent, i3Window *window);
 
-/* A wrapper for con_new_skeleton, to retain the old con_new behaviour
+/**
+ * A wrapper for con_new_skeleton, to retain the old con_new behaviour
  *
  */
 Con *con_new(Con *parent, i3Window *window);
@@ -120,6 +121,14 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation);
  */
 Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode);
 
+/**
+ * Returns the fullscreen node that covers the given workspace if it exists.
+ * This is either a CF_GLOBAL fullscreen container anywhere or a CF_OUTPUT
+ * fullscreen container in the workspace.
+ *
+ */
+Con *con_get_fullscreen_covering_ws(Con *ws);
+
 /**
  * Returns true if the container is internal, such as __i3_scratch
  *
@@ -212,7 +221,7 @@ void con_mark_toggle(Con *con, const char *mark, mark_mode_t mode);
  */
 void con_mark(Con *con, const char *mark, mark_mode_t mode);
 
-/*
+/**
  * Removes marks from containers.
  * If con is NULL, all containers are considered.
  * If name is NULL, this removes all existing marks.
@@ -394,7 +403,7 @@ Con *con_descend_focused(Con *con);
  */
 Con *con_descend_tiling_focused(Con *con);
 
-/*
+/**
  * Returns the leftmost, rightmost, etc. container in sub-tree. For example, if
  * direction is D_LEFT, then we return the rightmost container and if direction
  * is D_RIGHT, we return the leftmost container.  This is because if we are
index 187b550c994230ebfc47ac72379af0fe7f3e03d1..72b59ea2b9171e2d0487bd3827e73732b241cffa 100644 (file)
@@ -56,12 +56,14 @@ CFGFUN(disable_randr15, const char *value);
 CFGFUN(fake_outputs, const char *outputs);
 CFGFUN(force_display_urgency_hint, const long duration_ms);
 CFGFUN(focus_on_window_activation, const char *mode);
+CFGFUN(title_align, const char *alignment);
 CFGFUN(show_marks, const char *value);
 CFGFUN(hide_edge_borders, const char *borders);
 CFGFUN(assign_output, const char *output);
 CFGFUN(assign, const char *workspace, bool is_number);
 CFGFUN(no_focus);
 CFGFUN(ipc_socket, const char *path);
+CFGFUN(ipc_kill_timeout, const long timeout_ms);
 CFGFUN(restart_state, const char *path);
 CFGFUN(popup_during_fullscreen, const char *value);
 CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border);
@@ -81,7 +83,7 @@ CFGFUN(bar_hidden_state, const char *hidden_state);
 CFGFUN(bar_id, const char *bar_id);
 CFGFUN(bar_output, const char *output);
 CFGFUN(bar_verbose, const char *verbose);
-CFGFUN(bar_modifier, const char *modifier);
+CFGFUN(bar_modifier, const char *modifiers);
 CFGFUN(bar_wheel_up_cmd, const char *command);
 CFGFUN(bar_wheel_down_cmd, const char *command);
 CFGFUN(bar_bindsym, const char *button, const char *release, const char *command);
@@ -96,5 +98,6 @@ CFGFUN(bar_status_command, const char *command);
 CFGFUN(bar_binding_mode_indicator, const char *value);
 CFGFUN(bar_workspace_buttons, const char *value);
 CFGFUN(bar_strip_workspace_numbers, const char *value);
+CFGFUN(bar_strip_workspace_name, const char *value);
 CFGFUN(bar_start);
 CFGFUN(bar_finish);
index ace4041dd2ab2e4169b838330e5ef54f690ce0ce..009538f2d40625d31ba19976aeab2e4d88a63a8f 100644 (file)
@@ -16,7 +16,7 @@
 SLIST_HEAD(variables_head, Variable);
 extern pid_t config_error_nagbar_pid;
 
-/*
+/**
  * An intermediate reprsentation of the result of a parse_config call.
  * Currently unused, but the JSON output will be useful in the future when we
  * implement a config parsing IPC command.
index ac8001590a9ff37f7f5086d25a9bed4a2d39a140..6f55ac2a57f7fe7ef280ca956267b46c63b82db9 100644 (file)
@@ -201,6 +201,13 @@ struct Config {
      * decoration. Marks starting with a "_" will be ignored either way. */
     bool show_marks;
 
+    /** Title alignment options. */
+    enum {
+        ALIGN_LEFT,
+        ALIGN_CENTER,
+        ALIGN_RIGHT
+    } title_align;
+
     /** The default border style for new windows. */
     border_style_t default_border;
 
@@ -289,16 +296,7 @@ struct Barconfig {
            S_SHOW = 1 } hidden_state;
 
     /** Bar modifier (to show bar when in hide mode). */
-    enum {
-        M_NONE = 0,
-        M_CONTROL = 1,
-        M_SHIFT = 2,
-        M_MOD1 = 3,
-        M_MOD2 = 4,
-        M_MOD3 = 5,
-        M_MOD4 = 6,
-        M_MOD5 = 7
-    } modifier;
+    uint32_t modifier;
 
     TAILQ_HEAD(bar_bindings_head, Barbinding)
     bar_bindings;
@@ -331,6 +329,10 @@ struct Barconfig {
      * 'strip_workspace_numbers yes'. */
     bool strip_workspace_numbers;
 
+    /** Strip workspace name? Configuration option is
+     * 'strip_workspace_name yes'. */
+    bool strip_workspace_name;
+
     /** Hide mode button? Configuration option is 'binding_mode_indicator no'
      * but we invert the bool for the same reason as hide_workspace_buttons.*/
     bool hide_binding_mode_indicator;
@@ -432,7 +434,7 @@ void ungrab_all_keys(xcb_connection_t *conn);
  * Sends the current bar configuration as an event to all barconfig_update listeners.
  *
  */
-void update_barconfig();
+void update_barconfig(void);
 
 /**
  * Kills the configerror i3-nagbar process, if any.
index 69a46e464331172172873a70940af6d348cb09bd..f55e003d0bd2abcea36f20d89cf992fb5f9fab87 100644 (file)
@@ -477,6 +477,10 @@ struct Window {
     int min_width;
     int min_height;
 
+    /* Maximum size specified for the window. */
+    int max_width;
+    int max_height;
+
     /* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */
     double aspect_ratio;
 };
@@ -573,7 +577,7 @@ struct Assignment {
     /** the criteria to check if a window matches */
     Match match;
 
-    /** destination workspace/command, depending on the type */
+    /** destination workspace/command/output, depending on the type */
     union {
         char *command;
         char *workspace;
index 5844faa660a3886f78d0e1669f3a162c1385fbc9..01ae67f969c943392ab8506b583cd3b911d5e019 100644 (file)
@@ -83,6 +83,12 @@ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows);
  */
 void ewmh_update_sticky(xcb_window_t window, bool sticky);
 
+/**
+ * Set or remove _NEW_WM_STATE_FOCUSED on the window.
+ *
+ */
+void ewmh_update_focused(xcb_window_t window, bool is_focused);
+
 /**
  * Set up the EWMH hints on the root window.
  *
index babfafc9a583f3321dbe32c4394b9d4ca8e22070..4382437bbbb8f01ef262ca99b4db0fb76ddc2dce 100644 (file)
@@ -143,7 +143,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
  * outputs.
  *
  */
-void floating_reposition(Con *con, Rect newrect);
+bool floating_reposition(Con *con, Rect newrect);
 
 /**
  * Sets size of the CT_FLOATING_CON to specified dimensions. Might limit the
index 9e0280c9363322b48ef7f25534bfde2b9ccd9c4f..884a0cf635c75985ef1bebd1a689be9039d48556 100644 (file)
@@ -63,6 +63,9 @@ typedef struct i3_ipc_header {
 /** Send a tick event to all subscribers. */
 #define I3_IPC_MESSAGE_TYPE_SEND_TICK 10
 
+/** Trigger an i3 sync protocol message via IPC. */
+#define I3_IPC_MESSAGE_TYPE_SYNC 11
+
 /*
  * Messages from i3 to clients
  *
@@ -78,12 +81,13 @@ typedef struct i3_ipc_header {
 #define I3_IPC_REPLY_TYPE_BINDING_MODES 8
 #define I3_IPC_REPLY_TYPE_CONFIG 9
 #define I3_IPC_REPLY_TYPE_TICK 10
+#define I3_IPC_REPLY_TYPE_SYNC 11
 
 /*
  * Events from i3 to clients. Events have the first bit set high.
  *
  */
-#define I3_IPC_EVENT_MASK (1 << 31)
+#define I3_IPC_EVENT_MASK (1UL << 31)
 
 /* The workspace event will be triggered upon changes in the workspace list */
 #define I3_IPC_EVENT_WORKSPACE (I3_IPC_EVENT_MASK | 0)
index c6ad35c770745038eb99dbd370966fd489bc262f..a1caea827fb7a549cbcde1a3c5c973e14e1ab987 100644 (file)
@@ -35,6 +35,11 @@ typedef struct ipc_client {
      * event has been sent by i3. */
     bool first_tick_sent;
 
+    struct ev_io *callback;
+    struct ev_timer *timeout;
+    uint8_t *buffer;
+    size_t buffer_size;
+
     TAILQ_ENTRY(ipc_client)
     clients;
 } ipc_client;
@@ -124,3 +129,9 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig);
  * For the binding events, we send the serialized binding struct.
  */
 void ipc_send_binding_event(const char *event_type, Binding *bind);
+
+/**
+  * Set the maximum duration that we allow for a connection with an unwriteable
+  * socket.
+  */
+void ipc_set_kill_timeout(ev_tstamp new);
index b7a1e2aa21ae6b285516f81191a1ab63da37f0a5..790baba98578d3903ee651cba6e4b47fb8eb7456 100644 (file)
@@ -166,6 +166,14 @@ int sasprintf(char **strp, const char *fmt, ...);
  */
 ssize_t writeall(int fd, const void *buf, size_t count);
 
+/**
+ * Like writeall, but instead of retrying upon EAGAIN (returned when a write
+ * would block), the function stops and returns the total number of bytes
+ * written so far.
+ *
+ */
+ssize_t writeall_nonblock(int fd, const void *buf, size_t count);
+
 /**
  * Safe-wrapper around writeall which exits if it returns -1 (meaning that
  * write failed)
@@ -188,11 +196,11 @@ i3String *i3string_from_markup(const char *from_markup);
 
 /**
  * Build an i3String from an UTF-8 encoded string with fixed length.
- * To be used when no proper NUL-terminaison is available.
+ * To be used when no proper NULL-termination is available.
  * Returns the newly-allocated i3String.
  *
  */
-i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes);
+i3String *i3string_from_utf8_with_length(const char *from_utf8, ssize_t num_bytes);
 
 /**
  * Build an i3String from an UTF-8 encoded string in Pango markup with fixed
@@ -312,6 +320,11 @@ int ipc_recv_message(int sockfd, uint32_t *message_type,
  */
 void fake_configure_notify(xcb_connection_t *conn, xcb_rectangle_t r, xcb_window_t window, int border_width);
 
+#define HAS_G_UTF8_MAKE_VALID GLIB_CHECK_VERSION(2, 52, 0)
+#if !HAS_G_UTF8_MAKE_VALID
+gchar *g_utf8_make_valid(const gchar *str, gssize len);
+#endif
+
 /**
  * Returns the colorpixel to use for the given hex color (think of HTML). Only
  * works for true-color (vast majority of cases) at the moment, avoiding a
@@ -330,7 +343,7 @@ uint32_t get_colorpixel(const char *hex) __attribute__((const));
 
 #if defined(__APPLE__)
 
-/*
+/**
  * Taken from FreeBSD
  * Returns a pointer to a new string which is a duplicate of the
  * string, but only copies at most n characters.
@@ -459,7 +472,7 @@ xcb_visualtype_t *get_visualtype(xcb_screen_t *screen);
  * release version), based on the git version number.
  *
  */
-bool is_debug_build() __attribute__((const));
+bool is_debug_build(void) __attribute__((const));
 
 /**
  * Returns the name of a temporary file with the specified prefix.
@@ -506,11 +519,11 @@ int logical_px(const int logical);
 char *resolve_tilde(const char *path);
 
 /**
- * Get the path of the first configuration file found. If override_configpath
- * is specified, that path is returned and saved for further calls. Otherwise,
- * checks the home directory first, then the system directory first, always
- * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
- * $XDG_CONFIG_DIRS)
+ * Get the path of the first configuration file found. If override_configpath is
+ * specified, that path is returned and saved for further calls. Otherwise,
+ * checks the home directory first, then the system directory, always taking
+ * into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
+ * $XDG_CONFIG_DIRS).
  *
  */
 char *get_config_path(const char *override_configpath, bool use_system_paths);
index 4ff8c485bbed18fd45a27e290b7a40368f382882..043c3a8f114e77401a5a5df2f8792b8780e4f150 100644 (file)
@@ -15,7 +15,7 @@
 
 #include <config.h>
 
-/*
+/**
  * Initializes the Match data structure. This function is necessary because the
  * members representing boolean values (like dock) need to be initialized with
  * -1 instead of 0.
index 64b12b80cc7b937c8c2773989fa312a41b43adda..df644a6b8b7b217b1c3dfd8b433a00e5d94621eb 100644 (file)
  *
  */
 void tree_move(Con *con, int direction);
+
+typedef enum { BEFORE,
+               AFTER } position_t;
+
+/**
+ * This function detaches 'con' from its parent and inserts it either before or
+ * after 'target'.
+ *
+ */
+void insert_con_into(Con *con, Con *target, position_t position);
index 31084da19772c74e743d7025f79a0ef6afd772cd..a2ad97b037290f15a887ca9bb44fc68d4e0b0e6d 100644 (file)
@@ -40,5 +40,12 @@ Output *get_output_for_con(Con *con);
  * Iterates over all outputs and pushes sticky windows to the currently visible
  * workspace on that output.
  *
+ * old_focus is used to determine if a sticky window is going to be focused.
+ * old_focus might be different than the currently focused container because the
+ * caller might need to temporarily change the focus and then call
+ * output_push_sticky_windows. For example, workspace_show needs to set focus to
+ * one of its descendants first, then call output_push_sticky_windows that
+ * should focus a sticky window if it was the focused in the previous workspace.
+ *
  */
-void output_push_sticky_windows(Con *to_focus);
+void output_push_sticky_windows(Con *old_focus);
index bfbfd5a90bcbabcec5b261ad39e8c51d6686cb0f..ec533a28045f92f4719c4f810efd8b56d0fa90f2 100644 (file)
@@ -88,6 +88,14 @@ Output *get_output_by_name(const char *name, const bool require_active);
  */
 Output *get_output_containing(unsigned int x, unsigned int y);
 
+/**
+ * Returns the active output which contains the midpoint of the given rect. If
+ * such an output doesn't exist, returns the output which contains most of the
+ * rectangle or NULL if there is no output which intersects with it.
+ *
+ */
+Output *get_output_from_rect(Rect rect);
+
 /**
  * Returns the active output which spans exactly the area specified by
  * rect or NULL if there is no output like this.
@@ -95,15 +103,14 @@ Output *get_output_containing(unsigned int x, unsigned int y);
  */
 Output *get_output_with_dimensions(Rect rect);
 
-/*
- * In contained_by_output, we check if any active output contains part of the container.
+/**
+ * In output_containing_rect, we check if any active output contains part of the container.
  * We do this by checking if the output rect is intersected by the Rect.
  * This is the 2-dimensional counterpart of get_output_containing.
- * Since we don't actually need the outputs intersected by the given Rect (There could
- * be many), we just return true or false for convenience.
+ * Returns the output with the maximum intersecting area.
  *
  */
-bool contained_by_output(Rect rect);
+Output *output_containing_rect(Rect rect);
 
 /**
  * Gets the output which is the next one in the given direction.
@@ -130,7 +137,7 @@ Output *get_output_next(direction_t direction, Output *current, output_close_far
  */
 Output *get_output_next_wrap(direction_t direction, Output *current);
 
-/*
+/**
  * Creates an output covering the root window.
  *
  */
index 750b7d310317901155a626656517db9102afedde..2b2c8dad74b291347144e9dc9189da570cb9c695 100644 (file)
 
 #include <config.h>
 
-/* This is used to keep a state to pass around when rendering a con in render_con(). */
+/**
+ * This is used to keep a state to pass around when rendering a con in render_con().
+ *
+ */
 typedef struct render_params {
     /* A copy of the coordinates of the container which is being rendered. */
     int x;
@@ -39,7 +42,8 @@ typedef struct render_params {
  */
 void render_con(Con *con, bool render_fullscreen);
 
-/*
+/**
  * Returns the height for the decorations
+ *
  */
 int render_deco_height(void);
index 386341561854fcaeefa240661bf10cc59cef6245..72dffc0f55a5d66c68d376938945832dea936d9e 100644 (file)
 
 bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides);
 
-int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
+void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
+
+/**
+ * Resize the two given containers using the given amount of pixels or
+ * percentage points. One of the two needs to be 0. A positive amount means
+ * growing the first container while a negative means shrinking it.
+ * Returns false when the resize would result in one of the two containers
+ * having less than 1 pixel of size.
+ *
+ */
+bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt);
+
+/**
+ * Calculate the minimum percent needed for the given container to be at least 1
+ * pixel.
+ *
+ */
+double percent_for_1px(Con *con);
+
+/**
+ * Calculate the given container's new percent given a change in pixels.
+ *
+ */
+double px_resize_to_percent(Con *con, int px_diff);
index 241653c0dda82024aa9eb13651e6a1fad5f66216..b24ffc080ec4d0600718ff2887ca919cf779ca60 100644 (file)
@@ -29,7 +29,7 @@ void scratchpad_move(Con *con);
  * can press the same key to quickly look something up).
  *
  */
-void scratchpad_show(Con *con);
+bool scratchpad_show(Con *con);
 
 /**
  * When starting i3 initially (and after each change to the connected outputs),
index b90211ef1a9ca5a612e71a3028eb65f984fb77ab..dc8081f1329c8e6a24d2646c19428127efe594ce 100644 (file)
@@ -20,7 +20,7 @@
 /* Default shmlog size if not set by user. */
 extern const int default_shmlog_size;
 
-/*
+/**
  * Header of the shmlog file. Used by i3/src/log.c and i3/i3-dump-log/main.c.
  *
  */
index 5da221e1679ca364da489db753145f0eb32266c8..feece575c36f8a9d8361fdb08ef81b5b1a01ce43 100644 (file)
@@ -21,7 +21,7 @@
  * Starts the given application by passing it through a shell. We use double
  * fork to avoid zombie processes. As the started application’s parent exits
  * (immediately), the application is reparented to init (process-id 1), which
- * correctly handles childs, so we don’t have to do it :-).
+ * correctly handles children, so we don’t have to do it :-).
  *
  * The shell used to start applications is the system's bourne shell (i.e.,
  * /bin/sh).
diff --git a/include/sync.h b/include/sync.h
new file mode 100644 (file)
index 0000000..e726f99
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * sync.c: i3 sync protocol: https://i3wm.org/docs/testsuite.html#i3_sync
+ *
+ */
+#pragma once
+
+#include <xcb/xcb.h>
+
+void sync_respond(xcb_window_t window, uint32_t rnd);
index 62461def988bda0cf7e044b003c667098e75dad2..41a630366bfca43411f7934e696b57be975a81ab 100644 (file)
@@ -78,7 +78,7 @@ void tree_next(char way, orientation_t orientation);
  * container) and focus should be set there.
  *
  */
-bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus);
+bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent);
 
 /**
  * Loads tree from ~/.i3/_restart.json (used for in-place restarts).
index 3547d8d7b50df6533fc566aec682efe0e63bc1a3..d08ac69df9cc4205dfcc791b7d8b76f77baa943e 100644 (file)
@@ -25,9 +25,6 @@
 #define STARTS_WITH(string, needle) (strncasecmp((string), (needle), strlen((needle))) == 0)
 #define CIRCLEQ_NEXT_OR_NULL(head, elm, field) (CIRCLEQ_NEXT(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_NEXT(elm, field) : NULL)
 #define CIRCLEQ_PREV_OR_NULL(head, elm, field) (CIRCLEQ_PREV(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_PREV(elm, field) : NULL)
-#define FOR_TABLE(workspace)                             \
-    for (int cols = 0; cols < (workspace)->cols; cols++) \
-        for (int rows = 0; rows < (workspace)->rows; rows++)
 
 #define NODES_FOREACH(head)                                                    \
     for (Con *child = (Con *)-1; (child == (Con *)-1) && ((child = 0), true);) \
@@ -128,7 +125,7 @@ void i3_restart(bool forget_layout);
 
 #if defined(__OpenBSD__) || defined(__APPLE__)
 
-/*
+/**
  * Taken from FreeBSD
  * Find the first occurrence of the byte string s in byte string l.
  *
@@ -177,3 +174,9 @@ bool parse_long(const char *str, long *out, int base);
  *
  */
 ssize_t slurp(const char *path, char **buf);
+
+/**
+ * Convert a direction to its corresponding orientation.
+ *
+ */
+orientation_t orientation_from_direction(direction_t direction);
index 8d109e9e31726fe04ada3915501e64809c2dbadd..28d9eb66cd18f8179bd373e5b8362d6ce0a7bd2f 100644 (file)
 #define NET_WM_DESKTOP_NONE 0xFFFFFFF0
 #define NET_WM_DESKTOP_ALL 0xFFFFFFFF
 
+/**
+ * Returns the workspace with the given name or NULL if such a workspace does
+ * not exist.
+ *
+ */
+Con *get_existing_workspace_by_name(const char *name);
+
+/**
+ * Returns the workspace with the given number or NULL if such a workspace does
+ * not exist.
+ *
+ */
+Con *get_existing_workspace_by_num(int num);
+
+/**
+ * Returns true if the first output assigned to a workspace with the given
+ * workspace assignment is the same as the given output.
+ *
+ */
+bool output_triggers_assignment(Output *output, struct Workspace_Assignment *assignment);
+
 /**
  * Returns a pointer to the workspace with the given number (starting at 0),
  * creating the workspace if necessary (by allocating the necessary amount of
@@ -193,4 +214,4 @@ Con *workspace_encapsulate(Con *ws);
  * This returns true if and only if moving the workspace was successful.
  *
  */
-bool workspace_move_to_output(Con *ws, const char *output);
+bool workspace_move_to_output(Con *ws, Output *output);
index 3e81bc3646f36225a8d4f24d3495b5efa9654b7f..8b7664f2616f6f52d47c4a694d756c70af9bd84c 100644 (file)
@@ -49,6 +49,12 @@ void x_reinit(Con *con);
  */
 void x_con_kill(Con *con);
 
+/*
+ * Completely reinitializes the container's frame, without destroying the old window.
+ *
+ */
+void x_con_reframe(Con *con);
+
 /**
  * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
  *
index 92be7b895511f5f16913fe178a58db878993af27..53c932bfb1a11cbb3f919da17b32e9772573afa7 100644 (file)
@@ -53,6 +53,7 @@
                                                                   ConfigureNotify */                  \
                          XCB_EVENT_MASK_POINTER_MOTION |                                              \
                          XCB_EVENT_MASK_PROPERTY_CHANGE |                                             \
+                         XCB_EVENT_MASK_FOCUS_CHANGE |                                                \
                          XCB_EVENT_MASK_ENTER_WINDOW)
 
 #define xmacro(atom) xcb_atom_t A_##atom;
index a2c40319a52b492fcd57b6168495cc5d127257af..d15e35be78effe198e52e569d85bd2c8025a927e 100644 (file)
@@ -49,14 +49,12 @@ void init_dpi(void) {
         dpi = 0;
         goto init_dpi_end;
     }
-    dpi = (long)round(in_dpi);
+    dpi = lround(in_dpi);
 
     DLOG("Found Xft.dpi = %ld.\n", dpi);
 
 init_dpi_end:
-    if (resource != NULL) {
-        free(resource);
-    }
+    free(resource);
 
     if (database != NULL) {
         xcb_xrm_database_free(database);
index 6a2e93dcd8e3a413099e2e34b119fcfbf13faf93..f88360dc32ecab74a146a6e7e35789c9c35efc0b 100644 (file)
@@ -121,7 +121,7 @@ static void draw_util_set_source_color(surface_t *surface, color_t color) {
     cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha);
 }
 
-/**
+/*
  * Draw the given text using libi3.
  * This function also marks the surface dirty which is needed if other means of
  * drawing are used. This will be the case when using XCB to draw text.
@@ -140,7 +140,7 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_
     cairo_surface_mark_dirty(surface->surface);
 }
 
-/**
+/*
  * Draws a filled rectangle.
  * This function is a convenience wrapper and takes care of flushing the
  * surface as well as restoring the cairo state.
@@ -167,7 +167,7 @@ void draw_util_rectangle(surface_t *surface, color_t color, double x, double y,
     cairo_restore(surface->cr);
 }
 
-/**
+/*
  * Clears a surface with the given color.
  *
  */
@@ -191,7 +191,7 @@ void draw_util_clear_surface(surface_t *surface, color_t color) {
     cairo_restore(surface->cr);
 }
 
-/**
+/*
  * Copies a surface onto another surface.
  *
  */
index 81091ea74a31da11d392864c1158ed387b0da1cd..c06bae00c86fd6a32be6f897fd36d03ae01c24bc 100644 (file)
@@ -109,9 +109,8 @@ static void draw_text_pango(const char *text, size_t text_len,
     cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue);
     pango_cairo_update_layout(cr, layout);
     pango_layout_get_pixel_size(layout, NULL, &height);
-    /* Center the piece of text vertically if its height is smaller than the
-     * cached font height, and just let "high" symbols fall out otherwise. */
-    int yoffset = (height < savedFont->height ? 0.5 : 1) * (height - savedFont->height);
+    /* Center the piece of text vertically. */
+    int yoffset = (height - savedFont->height) / 2;
     cairo_move_to(cr, x, y - yoffset);
     pango_cairo_show_layout(cr, layout);
 
@@ -224,9 +223,7 @@ i3Font load_font(const char *pattern, const bool fallback) {
                      error->error_code);
         }
     }
-    if (error != NULL) {
-        free(error);
-    }
+    free(error);
 
     font.pattern = sstrdup(pattern);
     LOG("Using X font %s\n", pattern);
@@ -275,17 +272,13 @@ void free_font(void) {
         case FONT_TYPE_XCB: {
             /* Close the font and free the info */
             xcb_close_font(conn, savedFont->specific.xcb.id);
-            if (savedFont->specific.xcb.info)
-                free(savedFont->specific.xcb.info);
+            free(savedFont->specific.xcb.info);
             break;
         }
         case FONT_TYPE_PANGO:
             /* Free the font description */
             pango_font_description_free(savedFont->specific.pango_desc);
             break;
-        default:
-            assert(false);
-            break;
     }
 
     savedFont = NULL;
@@ -315,9 +308,6 @@ void set_font_colors(xcb_gcontext_t gc, color_t foreground, color_t background)
             pango_font_green = foreground.green;
             pango_font_blue = foreground.blue;
             break;
-        default:
-            assert(false);
-            break;
     }
 }
 
@@ -388,8 +378,6 @@ void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc,
             draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text),
                             drawable, visual, x, y, max_width, i3string_is_markup(text));
             return;
-        default:
-            assert(false);
     }
 }
 
@@ -425,8 +413,6 @@ void draw_text_ascii(const char *text, xcb_drawable_t drawable,
             draw_text_pango(text, strlen(text),
                             drawable, root_visual_type, x, y, max_width, false);
             return;
-        default:
-            assert(false);
     }
 }
 
@@ -519,8 +505,6 @@ int predict_text_width(i3String *text) {
             /* Calculate extents using Pango */
             return predict_text_width_pango(i3string_as_utf8(text), i3string_get_num_bytes(text),
                                             i3string_is_markup(text));
-        default:
-            assert(false);
-            return 0;
     }
+    assert(false);
 }
index 59e947811ec0fdce22abeddf3eca42aa38eec713..770e383d7af7640fbbb679928119ca07d89f64d6 100644 (file)
@@ -11,8 +11,8 @@
 #include <stdint.h>
 #include <string.h>
 
-#ifndef STARTS_WITH
-#define STARTS_WITH(string, needle) (strncasecmp((string), (needle), strlen((needle))) == 0)
+#ifndef CS_STARTS_WITH
+#define CS_STARTS_WITH(string, needle) (strncmp((string), (needle), strlen((needle))) == 0)
 #endif
 
 /*
@@ -28,7 +28,7 @@ char *format_placeholders(char *format, placeholder_t *placeholders, int num) {
     int buffer_len = strlen(format) + 1;
     for (char *walk = format; *walk != '\0'; walk++) {
         for (int i = 0; i < num; i++) {
-            if (!STARTS_WITH(walk, placeholders[i].name))
+            if (!CS_STARTS_WITH(walk, placeholders[i].name))
                 continue;
 
             buffer_len = buffer_len - strlen(placeholders[i].name) + strlen(placeholders[i].value);
@@ -48,7 +48,7 @@ char *format_placeholders(char *format, placeholder_t *placeholders, int num) {
 
         bool matched = false;
         for (int i = 0; i < num; i++) {
-            if (!STARTS_WITH(walk, placeholders[i].name)) {
+            if (!CS_STARTS_WITH(walk, placeholders[i].name)) {
                 continue;
             }
 
diff --git a/libi3/g_utf8_make_valid.c b/libi3/g_utf8_make_valid.c
new file mode 100644 (file)
index 0000000..b15873b
--- /dev/null
@@ -0,0 +1,93 @@
+/* g_utf8_make_valid.c - Coerce string into UTF-8
+ *
+ * Copyright (C) 1999 Tom Tromey
+ * Copyright (C) 2000 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "libi3.h"
+
+#include <string.h>
+#include <glib.h>
+
+/* Copied from:
+ * https://gitlab.gnome.org/GNOME/glib/blob/f928dfdf57bf92c883b53b16d7a9d49add504f52/glib/gutf8.c#L1752-1815 */
+/* clang-format off */
+#if !HAS_G_UTF8_MAKE_VALID
+/**
+ * g_utf8_make_valid:
+ * @str: string to coerce into UTF-8
+ * @len: the maximum length of @str to use, in bytes. If @len < 0,
+ *     then the string is nul-terminated.
+ *
+ * If the provided string is valid UTF-8, return a copy of it. If not,
+ * return a copy in which bytes that could not be interpreted as valid Unicode
+ * are replaced with the Unicode replacement character (U+FFFD).
+ *
+ * For example, this is an appropriate function to use if you have received
+ * a string that was incorrectly declared to be UTF-8, and you need a valid
+ * UTF-8 version of it that can be logged or displayed to the user, with the
+ * assumption that it is close enough to ASCII or UTF-8 to be mostly
+ * readable as-is.
+ *
+ * Returns: (transfer full): a valid UTF-8 string whose content resembles @str
+ *
+ * Since: 2.52
+ */
+gchar *
+g_utf8_make_valid (const gchar *str,
+                   gssize       len)
+{
+  GString *string;
+  const gchar *remainder, *invalid;
+  gsize remaining_bytes, valid_bytes;
+
+  g_return_val_if_fail (str != NULL, NULL);
+
+  if (len < 0)
+    len = strlen (str);
+
+  string = NULL;
+  remainder = str;
+  remaining_bytes = len;
+
+  while (remaining_bytes != 0)
+    {
+      if (g_utf8_validate (remainder, remaining_bytes, &invalid))
+       break;
+      valid_bytes = invalid - remainder;
+
+      if (string == NULL)
+       string = g_string_sized_new (remaining_bytes);
+
+      g_string_append_len (string, remainder, valid_bytes);
+      /* append U+FFFD REPLACEMENT CHARACTER */
+      g_string_append (string, "\357\277\275");
+
+      remaining_bytes -= valid_bytes + 1;
+      remainder = invalid + 1;
+    }
+
+  if (string == NULL)
+    return g_strndup (str, len);
+
+  g_string_append_len (string, remainder, remaining_bytes);
+  g_string_append_c (string, '\0');
+
+  g_assert (g_utf8_validate (string->str, -1, NULL));
+
+  return g_string_free (string, FALSE);
+}
+#endif
index 9bdd0a7191e7f151b49fe44df2b7922aeeb0c9dc..49a9e3b40c3486e269bc565f8f1b7c2dec6f732a 100644 (file)
@@ -43,7 +43,7 @@ uint32_t get_colorpixel(const char *hex) {
 
     /* Shortcut: if our screen is true color, no need to do a roundtrip to X11 */
     if (root_screen == NULL || root_screen->root_depth == 24 || root_screen->root_depth == 32) {
-        return (0xFF << 24) | (r << 16 | g << 8 | b);
+        return (0xFFUL << 24) | (r << 16 | g << 8 | b);
     }
 
     /* Lookup this colorpixel in the cache */
@@ -60,8 +60,7 @@ uint32_t get_colorpixel(const char *hex) {
 
     xcb_alloc_color_reply_t *reply;
 
-    reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, root_screen->default_colormap,
-                                                        r16, g16, b16),
+    reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, root_screen->default_colormap, r16, g16, b16),
                                   NULL);
 
     if (!reply) {
index efece5cde792f44949423897983445a033865dd1..4909e116ba8f9e9e13a2b3ab8be7e0ba9121c5c5 100644 (file)
@@ -21,11 +21,11 @@ static bool path_exists(const char *path) {
 }
 
 /*
- * Get the path of the first configuration file found. If override_configpath
- * is specified, that path is returned and saved for further calls. Otherwise,
- * checks the home directory first, then the system directory first, always
- * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
- * $XDG_CONFIG_DIRS)
+ * Get the path of the first configuration file found. If override_configpath is
+ * specified, that path is returned and saved for further calls. Otherwise,
+ * checks the home directory first, then the system directory, always taking
+ * into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
+ * $XDG_CONFIG_DIRS).
  *
  */
 char *get_config_path(const char *override_configpath, bool use_system_paths) {
@@ -38,40 +38,41 @@ char *get_config_path(const char *override_configpath, bool use_system_paths) {
         return sstrdup(saved_configpath);
     }
 
-    if (saved_configpath != NULL)
+    if (saved_configpath != NULL) {
         return sstrdup(saved_configpath);
+    }
 
-    /* 1: check the traditional path under the home directory */
-    config_path = resolve_tilde("~/.i3/config");
-    if (path_exists(config_path))
-        return config_path;
-    free(config_path);
-
-    /* 2: check for $XDG_CONFIG_HOME/i3/config */
-    if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
+    /* 1: check for $XDG_CONFIG_HOME/i3/config */
+    if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) {
         xdg_config_home = "~/.config";
+    }
 
     xdg_config_home = resolve_tilde(xdg_config_home);
     sasprintf(&config_path, "%s/i3/config", xdg_config_home);
     free(xdg_config_home);
 
-    if (path_exists(config_path))
+    if (path_exists(config_path)) {
         return config_path;
+    }
+    free(config_path);
+
+    /* 2: check the traditional path under the home directory */
+    config_path = resolve_tilde("~/.i3/config");
+    if (path_exists(config_path)) {
+        return config_path;
+    }
     free(config_path);
 
     /* The below paths are considered system-level, and can be skipped if the
      * caller only wants user-level configs. */
-    if (!use_system_paths)
+    if (!use_system_paths) {
         return NULL;
+    }
 
-    /* 3: check the traditional path under /etc */
-    config_path = SYSCONFDIR "/i3/config";
-    if (path_exists(config_path))
-        return sstrdup(config_path);
-
-    /* 4: check for $XDG_CONFIG_DIRS/i3/config */
-    if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
+    /* 3: check for $XDG_CONFIG_DIRS/i3/config */
+    if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL) {
         xdg_config_dirs = SYSCONFDIR "/xdg";
+    }
 
     char *buf = sstrdup(xdg_config_dirs);
     char *tok = strtok(buf, ":");
@@ -88,5 +89,11 @@ char *get_config_path(const char *override_configpath, bool use_system_paths) {
     }
     free(buf);
 
+    /* 4: check the traditional path under /etc */
+    config_path = SYSCONFDIR "/i3/config";
+    if (path_exists(config_path)) {
+        return sstrdup(config_path);
+    }
+
     return NULL;
 }
index 4e583622b3f78ec1050b1755cf40d2c908dc5faa..52187bdaecae374fac59618d188b61c7f2b26301 100644 (file)
@@ -15,7 +15,7 @@
  * release version), based on the git version number.
  *
  */
-bool is_debug_build() {
+bool is_debug_build(void) {
     /* i3_version contains either something like this:
      *     "4.0.2 (2011-11-11, branch "release")".
      * or: "4.0.2-123-gCOFFEEBABE (2011-11-11, branch "next")".
index 1fc8c34607222ad381b269fad2fed20e1ad81bdc..f5281bd74164c5e1268a2cd3f3925ff1a9d28063 100644 (file)
@@ -44,10 +44,7 @@ int mkdirp(const char *path, mode_t mode) {
 
     char *sep = strrchr(copy, '/');
     if (sep == NULL) {
-        if (copy != NULL) {
-            free(copy);
-            copy = NULL;
-        }
+        free(copy);
         return -1;
     }
     *sep = '\0';
index 51d642db37ec37f3f1dc014f44d51b31dd324390..6dbf132fa7605d59e3c7f96c4ce7ae2c57e0e51f 100644 (file)
@@ -35,9 +35,10 @@ char *resolve_tilde(const char *path) {
     } else {
         head = globbuf.gl_pathv[0];
         result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1, 1);
-        strncpy(result, head, strlen(head));
-        if (tail)
-            strncat(result, tail, strlen(tail));
+        strcpy(result, head);
+        if (tail) {
+            strcat(result, tail);
+        }
     }
     globfree(&globbuf);
 
index 94ad4ee6c60a73fa4a28ae6cb4d353d9f5bfff4b..1802b327ee4442802cc7acc96dfa67e262a114a6 100644 (file)
@@ -68,10 +68,9 @@ int sasprintf(char **strp, const char *fmt, ...) {
 
 ssize_t writeall(int fd, const void *buf, size_t count) {
     size_t written = 0;
-    ssize_t n = 0;
 
     while (written < count) {
-        n = write(fd, buf + written, count - written);
+        const ssize_t n = write(fd, ((char *)buf) + written, count - written);
         if (n == -1) {
             if (errno == EINTR || errno == EAGAIN)
                 continue;
@@ -83,6 +82,25 @@ ssize_t writeall(int fd, const void *buf, size_t count) {
     return written;
 }
 
+ssize_t writeall_nonblock(int fd, const void *buf, size_t count) {
+    size_t written = 0;
+
+    while (written < count) {
+        const ssize_t n = write(fd, ((char *)buf) + written, count - written);
+        if (n == -1) {
+            if (errno == EAGAIN) {
+                return written;
+            } else if (errno == EINTR) {
+                continue;
+            } else {
+                return n;
+            }
+        }
+        written += (size_t)n;
+    }
+    return written;
+}
+
 ssize_t swrite(int fd, const void *buf, size_t count) {
     ssize_t n;
 
index edd588da1492a26a0acb2dc6d2998af4392416cf..9efa369037a9b96a8d115dfd4c4366afa7873fdc 100644 (file)
@@ -30,15 +30,7 @@ struct _i3String {
  *
  */
 i3String *i3string_from_utf8(const char *from_utf8) {
-    i3String *str = scalloc(1, sizeof(i3String));
-
-    /* Get the text */
-    str->utf8 = sstrdup(from_utf8);
-
-    /* Compute and store the length */
-    str->num_bytes = strlen(str->utf8);
-
-    return str;
+    return i3string_from_utf8_with_length(from_utf8, -1);
 }
 
 /*
@@ -56,20 +48,18 @@ i3String *i3string_from_markup(const char *from_markup) {
 
 /*
  * Build an i3String from an UTF-8 encoded string with fixed length.
- * To be used when no proper NUL-terminaison is available.
+ * To be used when no proper NULL-termination is available.
  * Returns the newly-allocated i3String.
  *
  */
-i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes) {
+i3String *i3string_from_utf8_with_length(const char *from_utf8, ssize_t num_bytes) {
     i3String *str = scalloc(1, sizeof(i3String));
 
-    /* Copy the actual text to our i3String */
-    str->utf8 = scalloc(num_bytes + 1, 1);
-    strncpy(str->utf8, from_utf8, num_bytes);
-    str->utf8[num_bytes] = '\0';
+    /* g_utf8_make_valid NULL-terminates the string. */
+    str->utf8 = g_utf8_make_valid(from_utf8, num_bytes);
 
-    /* Store the length */
-    str->num_bytes = num_bytes;
+    /* num_bytes < 0 means NULL-terminated string, need to calculate length */
+    str->num_bytes = num_bytes < 0 ? strlen(str->utf8) : (size_t)num_bytes;
 
     return str;
 }
@@ -109,7 +99,7 @@ i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs) {
     return str;
 }
 
-/**
+/*
  * Copies the given i3string.
  * Note that this will not free the source string.
  */
index 75cff78adbdf4a5739da0834438d3c792105eb0c..398c1ae58112566da771035a578ce222f99d2a8e 100644 (file)
@@ -83,8 +83,7 @@ xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen) {
     }
 
     /* Do the conversion */
-    size_t rc = iconv(ucs2_conversion_descriptor, (char **)&input,
-                      &input_size, (char **)&output, &output_size);
+    size_t rc = iconv(ucs2_conversion_descriptor, &input, &input_size, (char **)&output, &output_size);
     if (rc == (size_t)-1) {
         perror("Converting to UCS-2 failed");
         free(buffer);
index 5a9ca39e4daeb5de60e1f2cdebcc49ecca84450c..e8cce007818767751791b1924fdd45bc5d2c3bba 100644 (file)
@@ -9,7 +9,21 @@ i3-config-wizard - creates a keysym based config based on your layout
 
 == SYNOPSIS
 
-i3-config-wizard
+i3-config-wizard [*-s* 'socket'] [*-m* 'modifier'] [*-v*] [*-h*]
+
+== OPTIONS
+
+*-s, --socket* 'socket'::
+Overwrites the path to the i3 IPC socket.
+
+*-m, --modifier* 'modifier'::
+Generates the configuration file headlessly. Accepts win or alt.
+
+*-v, --version*::
+Display version number and exit.
+
+*-h, --help*::
+Display a short help message and exit.
 
 == FILES
 
index 07a91783115e8394092207842dd341a332bea5be..dc145914749ab758962729649be6bf73a3355b14 100644 (file)
@@ -1,5 +1,5 @@
 i3-input(1)
-=========
+===========
 Michael Stapelberg <michael+i3@stapelberg.de>
 v4.1.2, April 2012
 
index 7f050f594861b5d30962d58ae5f61435ddbf9a3d..625131de5855f16cac7b4fc10aeef881c5ef656f 100644 (file)
@@ -31,6 +31,11 @@ with an error.
 *-t* 'type'::
 Send ipc message, see below. This option defaults to "command".
 
+*-m*, *--monitor*::
+Instead of exiting right after receiving the first subscribed event,
+wait indefinitely for all of them. Can only be used with "-t subscribe".
+See the "subscribe" IPC message type below for details.
+
 *message*::
 Send ipc message, see below.
 
@@ -69,6 +74,17 @@ get_version::
 Gets the version of i3. The reply will be a JSON-encoded dictionary with the
 major, minor, patch and human-readable version.
 
+get_config::
+Gets the currently loaded i3 configuration.
+
+send_tick::
+Sends a tick to all IPC connections which subscribe to tick events.
+
+subscribe::
+The payload of the message describes the events to subscribe to.
+Upon reception, each event will be dumped as a JSON-encoded object.
+See the -m option for continuous monitoring.
+
 == DESCRIPTION
 
 i3-msg is a sample implementation for a client using the unix socket IPC
@@ -85,6 +101,9 @@ i3-msg border normal
 
 # Dump the layout tree
 i3-msg -t get_tree
+
+# Monitor window changes
+i3-msg -t subscribe -m '[ "window" ]'
 ------------------------------------------------
 
 == ENVIRONMENT
index 77fdd80b504ed5dd24c0f345ea1eefb2abfd086b..ef3a354588c23378ab97f5dc257b4b46a7132573 100644 (file)
@@ -9,7 +9,7 @@ i3-nagbar - displays an error bar on top of your screen
 
 == SYNOPSIS
 
-i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v]
+i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]
 
 == OPTIONS
 
@@ -32,6 +32,12 @@ Select font that is being used.
 *-b, --button* 'button' 'action'::
 Create a button with text 'button'. The 'action' are the shell commands that
 will be executed by this button. Multiple buttons can be defined.
+Will launch the shell commands inside a terminal emulator, using
+i3-sensible-terminal.
+
+*-B, --button-no-terminal* 'button' 'action'::
+Same as above, but will execute the shell commands directly, without launching a
+terminal emulator.
 
 == DESCRIPTION
 
index 4f16d6c1e0e10098dee2645e098144142d25bc61..31e82ca3e71f5b882086d64c2bf1fdf7a2a5212c 100644 (file)
@@ -1,5 +1,5 @@
 i3-sensible-editor(1)
-===================
+=====================
 Michael Stapelberg <michael+i3@stapelberg.de>
 v4.1, November 2011
 
index 22754c0bd96686e74e5cb372384b9381db9b8ef4..2339bef053f73f96ed4eecf27b2d9e8b1136737d 100644 (file)
@@ -1,5 +1,5 @@
 i3-sensible-pager(1)
-===================
+====================
 Michael Stapelberg <michael+i3@stapelberg.de>
 v4.1, November 2011
 
index 894af9120ed404da00610a8a8d797b5c92aa7e8c..bda5a7239842a6d11613baf4da0478a150bda4f6 100644 (file)
@@ -47,6 +47,8 @@ It tries to start one of the following (in that order):
 * kitty
 * guake
 * tilda
+* alacritty
+* hyper
 
 Please don’t complain about the order: If the user has any preference, they will
 have $TERMINAL set or modified their i3 configuration file.
index 1f595ce827d1e2bc8d7b95e94f3c7b0b95639fa9..640b5ac81a36e95703f8a627711cde624e9137a7 100644 (file)
@@ -170,10 +170,10 @@ Exits i3.
 
 When starting, i3 looks for configuration files in the following order:
 
-1. ~/.i3/config
-2. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
-3. /etc/i3/config
-4. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
+1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
+2. ~/.i3/config
+3. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
+4. /etc/i3/config
 
 You can specify a custom path using the -c option.
 
index 0289fa1ab6018a404cb2445f5fa57b61eaf283eb..6b015188e3584dd4cd4718ece5c531447bdaa2c5 100644 (file)
@@ -86,16 +86,16 @@ state DEBUGLOG:
 # border normal|pixel [<n>]
 # border none|1pixel|toggle
 state BORDER:
-  border_style = 'normal', 'pixel'
+  border_style = 'normal', 'pixel', 'toggle'
     -> BORDER_WIDTH
-  border_style = 'none', 'toggle'
+  border_style = 'none'
     -> call cmd_border($border_style, 0)
-  border_style = '1pixel'
-    -> call cmd_border($border_style, 1)
+  '1pixel'
+    -> call cmd_border("pixel", 1)
 
 state BORDER_WIDTH:
   end
-    -> call cmd_border($border_style, 2)
+    -> call cmd_border($border_style, -1)
   border_width = number
     -> call cmd_border($border_style, &border_width)
 
@@ -243,7 +243,7 @@ state RESIZE_TILING:
   'or'
       -> RESIZE_TILING_OR
   end
-      -> call cmd_resize($way, $direction, &resize_px, 10)
+      -> call cmd_resize($way, $direction, &resize_px, 0)
 
 state RESIZE_TILING_OR:
   resize_ppt = number
@@ -254,12 +254,24 @@ state RESIZE_TILING_FINAL:
       -> call cmd_resize($way, $direction, &resize_px, &resize_ppt)
 
 state RESIZE_SET:
+  'height'
+      -> RESIZE_HEIGHT_GET_NUMBER
+  'width'
+      ->
   width = number
       -> RESIZE_WIDTH
 
 state RESIZE_WIDTH:
   mode_width = 'px', 'ppt'
       ->
+  end
+      -> call cmd_resize_set(&width, $mode_width, 0, 0)
+  'height'
+      -> RESIZE_HEIGHT_GET_NUMBER
+  height = number
+      -> RESIZE_HEIGHT
+
+state RESIZE_HEIGHT_GET_NUMBER:
   height = number
       -> RESIZE_HEIGHT
 
@@ -396,7 +408,7 @@ state MOVE_TO_POSITION_X:
 
 state MOVE_TO_POSITION_Y:
   'px', end
-      -> call cmd_move_window_to_position($method, &coord_x, &coord_y)
+      -> call cmd_move_window_to_position(&coord_x, &coord_y)
 
 # mode <string>
 state MODE:
index 60a1fc67a329294d0ca33f0aa2b6d2c8c81b74f2..43181c59e1bb96f52563e5063e719daa9a7848fe 100644 (file)
@@ -45,9 +45,11 @@ state INITIAL:
   'fake_outputs', 'fake-outputs'           -> FAKE_OUTPUTS
   'force_display_urgency_hint'             -> FORCE_DISPLAY_URGENCY_HINT
   'focus_on_window_activation'             -> FOCUS_ON_WINDOW_ACTIVATION
+  'title_align'                            -> TITLE_ALIGN
   'show_marks'                             -> SHOW_MARKS
   'workspace'                              -> WORKSPACE
   'ipc_socket', 'ipc-socket'               -> IPC_SOCKET
+  'ipc_kill_timeout'                       -> IPC_KILL_TIMEOUT
   'restart_state'                          -> RESTART_STATE
   'popup_during_fullscreen'                -> POPUP_DURING_FULLSCREEN
   exectype = 'exec_always', 'exec'         -> EXEC
@@ -247,6 +249,11 @@ state FORCE_DISPLAY_URGENCY_HINT:
   duration_ms = number
       -> FORCE_DISPLAY_URGENCY_HINT_MS
 
+# title_align [left|center|right]
+state TITLE_ALIGN:
+  alignment = 'left', 'center', 'right'
+      -> call cfg_title_align($alignment)
+
 # show_marks
 state SHOW_MARKS:
   value = word
@@ -273,7 +280,7 @@ state WORKSPACE_OUTPUT:
       -> WORKSPACE_OUTPUT_STR
 
 state WORKSPACE_OUTPUT_STR:
-  output = word
+  output = string
       -> call cfg_workspace($workspace, $output)
 
 # ipc-socket <path>
@@ -281,6 +288,11 @@ state IPC_SOCKET:
   path = string
       -> call cfg_ipc_socket($path)
 
+# ipc_kill_timeout
+state IPC_KILL_TIMEOUT:
+  timeout = number
+      -> call cfg_ipc_kill_timeout(&timeout)
+
 # restart_state <path> (for testcases)
 state RESTART_STATE:
   path = string
@@ -455,6 +467,7 @@ state BAR:
   'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR
   'workspace_buttons'      -> BAR_WORKSPACE_BUTTONS
   'strip_workspace_numbers' -> BAR_STRIP_WORKSPACE_NUMBERS
+  'strip_workspace_name' -> BAR_STRIP_WORKSPACE_NAME
   'verbose'                -> BAR_VERBOSE
   'colors'                 -> BAR_COLORS_BRACE
   '}'
@@ -490,8 +503,14 @@ state BAR_ID:
       -> call cfg_bar_id($bar_id); BAR
 
 state BAR_MODIFIER:
-  modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift', 'none', 'off'
-      -> call cfg_bar_modifier($modifier); BAR
+  'off', 'none'
+      -> call cfg_bar_modifier(NULL); BAR
+  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl'
+      ->
+  '+'
+      ->
+  end
+      -> call cfg_bar_modifier($modifiers); BAR
 
 state BAR_WHEEL_UP_CMD:
   command = string
@@ -555,6 +574,10 @@ state BAR_STRIP_WORKSPACE_NUMBERS:
   value = word
       -> call cfg_bar_strip_workspace_numbers($value); BAR
 
+state BAR_STRIP_WORKSPACE_NAME:
+  value = word
+      -> call cfg_bar_strip_workspace_name($value); BAR
+
 state BAR_VERBOSE:
   value = word
       -> call cfg_bar_verbose($value); BAR
index cfb4beae4c6357e0db7e3bb114f9a01bee1665a3..0190fcfb4f575c1bd77061219bb7a175f2d26071 100755 (executable)
@@ -1,9 +1,9 @@
 #!/bin/zsh
 # This script is used to prepare a new release of i3.
 
-export RELEASE_VERSION="4.14.1"
+export RELEASE_VERSION="4.15"
 export PREVIOUS_VERSION="4.14"
-export RELEASE_BRANCH="master"
+export RELEASE_BRANCH="next"
 
 if [ ! -e "../i3.github.io" ]
 then
@@ -85,12 +85,12 @@ if [ "${RELEASE_BRANCH}" = "master" ]; then
        git checkout master
        git merge --no-ff release-${RELEASE_VERSION} -m "Merge branch 'release-${RELEASE_VERSION}'"
        git checkout next
-       git merge --no-ff -X ours master -m "Merge branch 'master' into next"
+       git merge --no-ff -s recursive -X ours -X no-renames master -m "Merge branch 'master' into next"
 else
        git checkout next
        git merge --no-ff release-${RELEASE_VERSION} -m "Merge branch 'release-${RELEASE_VERSION}'"
        git checkout master
-       git merge --no-ff -X theirs next -m "Merge branch 'next' into master"
+       git merge --no-ff -s recursive -X theirs -X no-renames next -m "Merge branch 'next' into master"
 fi
 
 git remote remove origin
@@ -126,6 +126,7 @@ WORKDIR /usr/src
 RUN mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' i3-${RELEASE_VERSION}/debian/control
 WORKDIR /usr/src/i3-${RELEASE_VERSION}
 RUN dpkg-buildpackage -sa -j8
+RUN dpkg-buildpackage -S -sa -j8
 EOT
 
 CONTAINER_NAME=$(echo "i3-${TMPDIR}" | sed 's,/,,g')
@@ -139,7 +140,7 @@ echo "Content of resulting package’s .changes file:"
 cat ${TMPDIR}/debian/*.changes
 
 # debsign is in devscripts, which is available in fedora and debian
-debsign -k4AC8EE1D ${TMPDIR}/debian/*.changes
+debsign --no-re-sign -k4AC8EE1D ${TMPDIR}/debian/*.changes
 
 # TODO: docker cleanup
 
@@ -227,7 +228,7 @@ echo "  cd ${TMPDIR}/i3.github.io"
 echo "  git push"
 echo ""
 echo "  cd ${TMPDIR}/debian"
-echo "  dput *.changes"
+echo "  dput"
 echo ""
 echo "  cd ${TMPDIR}"
 echo "  sendmail -t < email.txt"
index a0f5d5a747b79f02c23d1dfe478b5ef59c9e03ae..abacc0a3658990b0e752749e6ff8146afdf24039 100644 (file)
@@ -22,7 +22,7 @@ void run_assignments(i3Window *window) {
     /* Check if any assignments match */
     Assignment *current;
     TAILQ_FOREACH(current, &assignments, assignments) {
-        if (!match_matches_window(&(current->match), window))
+        if (current->type != A_COMMAND || !match_matches_window(&(current->match), window))
             continue;
 
         bool skip = false;
@@ -45,19 +45,16 @@ void run_assignments(i3Window *window) {
         window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment *) * window->nr_assignments);
         window->ran_assignments[window->nr_assignments - 1] = current;
 
-        DLOG("matching assignment, would do:\n");
-        if (current->type == A_COMMAND) {
-            DLOG("execute command %s\n", current->dest.command);
-            char *full_command;
-            sasprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
-            CommandResult *result = parse_command(full_command, NULL);
-            free(full_command);
+        DLOG("matching assignment, execute command %s\n", current->dest.command);
+        char *full_command;
+        sasprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
+        CommandResult *result = parse_command(full_command, NULL);
+        free(full_command);
 
-            if (result->needs_tree_render)
-                needs_tree_render = true;
+        if (result->needs_tree_render)
+            needs_tree_render = true;
 
-            command_result_free(result);
-        }
+        command_result_free(result);
     }
 
     /* If any of the commands required re-rendering, we will do that now. */
index c145b95602dd0b34c34a5db891a17dba30ec0c48..6704c816e148170a987088c505c2298245c7e33c 100644 (file)
@@ -198,6 +198,7 @@ void regrab_all_buttons(xcb_connection_t *conn) {
  */
 static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_release, uint16_t input_code, input_type_t input_type) {
     Binding *bind;
+    Binding *result = NULL;
 
     if (!is_release) {
         /* On a press event, we first reset all B_UPON_KEYRELEASE_IGNORE_MODS
@@ -227,30 +228,27 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas
         /* For keyboard bindings where a symbol was specified by the user, we
          * need to look in the array of translated keycodes for the event’s
          * keycode */
+        bool found_keycode = false;
         if (input_type == B_KEYBOARD && bind->symbol != NULL) {
             xcb_keycode_t input_keycode = (xcb_keycode_t)input_code;
-            bool found_keycode = false;
             struct Binding_Keycode *binding_keycode;
             TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) {
                 const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF);
                 const bool mods_match = (modifiers_mask == modifiers_state);
                 DLOG("binding_keycode->modifiers = %d, modifiers_mask = %d, modifiers_state = %d, mods_match = %s\n",
                      binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no"));
-                if (binding_keycode->keycode == input_keycode && mods_match) {
+                if (binding_keycode->keycode == input_keycode &&
+                    (mods_match || (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS && is_release))) {
                     found_keycode = true;
                     break;
                 }
             }
-            if (!found_keycode) {
-                continue;
-            }
         } else {
             /* This case is easier: The user specified a keycode */
             if (bind->keycode != input_code) {
                 continue;
             }
 
-            bool found_keycode = false;
             struct Binding_Keycode *binding_keycode;
             TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) {
                 const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF);
@@ -262,9 +260,9 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas
                     break;
                 }
             }
-            if (!found_keycode) {
-                continue;
-            }
+        }
+        if (!found_keycode) {
+            continue;
         }
 
         /* If this binding is a release binding, it matches the key which the
@@ -274,23 +272,26 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas
         if (bind->release == B_UPON_KEYRELEASE && !is_release) {
             bind->release = B_UPON_KEYRELEASE_IGNORE_MODS;
             DLOG("marked bind %p as B_UPON_KEYRELEASE_IGNORE_MODS\n", bind);
-            /* The correct binding has been found, so abort the search, but
-             * also don’t return this binding, since it should not be executed
-             * yet (only when the keys are released). */
-            bind = TAILQ_END(bindings);
-            break;
+            if (result) {
+                break;
+            }
+            continue;
         }
 
         /* Check if the binding is for a press or a release event */
-        if ((bind->release == B_UPON_KEYPRESS && is_release) ||
-            (bind->release >= B_UPON_KEYRELEASE && !is_release)) {
+        if ((bind->release == B_UPON_KEYPRESS && is_release)) {
             continue;
         }
 
-        break;
+        if (is_release) {
+            return bind;
+        } else if (!result) {
+            /* Continue looping to mark needed B_UPON_KEYRELEASE_IGNORE_MODS. */
+            result = bind;
+        }
     }
 
-    return (bind == TAILQ_END(bindings) ? NULL : bind);
+    return result;
 }
 
 /*
@@ -361,6 +362,14 @@ struct resolve {
     struct xkb_state *xkb_state_numlock_no_shift;
 };
 
+#define ADD_TRANSLATED_KEY(code, mods)                                                     \
+    do {                                                                                   \
+        struct Binding_Keycode *binding_keycode = smalloc(sizeof(struct Binding_Keycode)); \
+        binding_keycode->modifiers = (mods);                                               \
+        binding_keycode->keycode = (code);                                                 \
+        TAILQ_INSERT_TAIL(&(bind->keycodes_head), binding_keycode, keycodes);              \
+    } while (0)
+
 /*
  * add_keycode_if_matches is called for each keycode in the keymap and will add
  * the keycode to |data->bind| if the keycode can result in the keysym
@@ -390,18 +399,10 @@ static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key,
     }
     Binding *bind = resolving->bind;
 
-#define ADD_TRANSLATED_KEY(mods)                                                           \
-    do {                                                                                   \
-        struct Binding_Keycode *binding_keycode = smalloc(sizeof(struct Binding_Keycode)); \
-        binding_keycode->modifiers = (mods);                                               \
-        binding_keycode->keycode = key;                                                    \
-        TAILQ_INSERT_TAIL(&(bind->keycodes_head), binding_keycode, keycodes);              \
-    } while (0)
-
-    ADD_TRANSLATED_KEY(bind->event_state_mask);
+    ADD_TRANSLATED_KEY(key, bind->event_state_mask);
 
     /* Also bind the key with active CapsLock */
-    ADD_TRANSLATED_KEY(bind->event_state_mask | XCB_MOD_MASK_LOCK);
+    ADD_TRANSLATED_KEY(key, bind->event_state_mask | XCB_MOD_MASK_LOCK);
 
     /* If this binding is not explicitly for NumLock, check whether we need to
      * add a fallback. */
@@ -413,17 +414,15 @@ static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key,
         xkb_keysym_t sym_numlock = xkb_state_key_get_one_sym(numlock_state, key);
         if (sym_numlock == resolving->keysym) {
             /* Also bind the key with active NumLock */
-            ADD_TRANSLATED_KEY(bind->event_state_mask | xcb_numlock_mask);
+            ADD_TRANSLATED_KEY(key, bind->event_state_mask | xcb_numlock_mask);
 
             /* Also bind the key with active NumLock+CapsLock */
-            ADD_TRANSLATED_KEY(bind->event_state_mask | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
+            ADD_TRANSLATED_KEY(key, bind->event_state_mask | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
         } else {
             DLOG("Skipping automatic numlock fallback, key %d resolves to 0x%x with numlock\n",
                  key, sym_numlock);
         }
     }
-
-#undef ADD_TRANSLATED_KEY
 }
 
 /*
@@ -431,41 +430,22 @@ static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key,
  *
  */
 void translate_keysyms(void) {
-    struct xkb_state *dummy_state = xkb_state_new(xkb_keymap);
-    if (dummy_state == NULL) {
-        ELOG("Could not create XKB state, cannot translate keysyms.\n");
-        return;
-    }
-
-    struct xkb_state *dummy_state_no_shift = xkb_state_new(xkb_keymap);
-    if (dummy_state_no_shift == NULL) {
-        ELOG("Could not create XKB state, cannot translate keysyms.\n");
-        return;
-    }
-
-    struct xkb_state *dummy_state_numlock = xkb_state_new(xkb_keymap);
-    if (dummy_state_numlock == NULL) {
-        ELOG("Could not create XKB state, cannot translate keysyms.\n");
-        return;
-    }
+    struct xkb_state *dummy_state = NULL;
+    struct xkb_state *dummy_state_no_shift = NULL;
+    struct xkb_state *dummy_state_numlock = NULL;
+    struct xkb_state *dummy_state_numlock_no_shift = NULL;
+    bool has_errors = false;
 
-    struct xkb_state *dummy_state_numlock_no_shift = xkb_state_new(xkb_keymap);
-    if (dummy_state_numlock_no_shift == NULL) {
+    if ((dummy_state = xkb_state_new(xkb_keymap)) == NULL ||
+        (dummy_state_no_shift = xkb_state_new(xkb_keymap)) == NULL ||
+        (dummy_state_numlock = xkb_state_new(xkb_keymap)) == NULL ||
+        (dummy_state_numlock_no_shift = xkb_state_new(xkb_keymap)) == NULL) {
         ELOG("Could not create XKB state, cannot translate keysyms.\n");
-        return;
+        goto out;
     }
 
-    bool has_errors = false;
     Binding *bind;
     TAILQ_FOREACH(bind, bindings, bindings) {
-#define ADD_TRANSLATED_KEY(code, mods)                                                     \
-    do {                                                                                   \
-        struct Binding_Keycode *binding_keycode = smalloc(sizeof(struct Binding_Keycode)); \
-        binding_keycode->modifiers = (mods);                                               \
-        binding_keycode->keycode = (code);                                                 \
-        TAILQ_INSERT_TAIL(&(bind->keycodes_head), binding_keycode, keycodes);              \
-    } while (0)
-
         if (bind->input_type == B_MOUSE) {
             long button;
             if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) {
@@ -616,10 +596,9 @@ void translate_keysyms(void) {
         DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n",
              bind->event_state_mask, bind->symbol, keysym, keycodes, num_keycodes);
         free(keycodes);
-
-#undef ADD_TRANSLATED_KEY
     }
 
+out:
     xkb_state_unref(dummy_state);
     xkb_state_unref(dummy_state_no_shift);
     xkb_state_unref(dummy_state_numlock);
@@ -630,6 +609,8 @@ void translate_keysyms(void) {
     }
 }
 
+#undef ADD_TRANSLATED_KEY
+
 /*
  * Switches the key bindings to the given mode, if the mode exists
  *
@@ -648,6 +629,14 @@ void switch_mode(const char *new_mode) {
         translate_keysyms();
         grab_all_keys(conn);
 
+        /* Reset all B_UPON_KEYRELEASE_IGNORE_MODS bindings to avoid possibly
+         * activating one of them. */
+        Binding *bind;
+        TAILQ_FOREACH(bind, bindings, bindings) {
+            if (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS)
+                bind->release = B_UPON_KEYRELEASE;
+        }
+
         char *event_msg;
         sasprintf(&event_msg, "{\"change\":\"%s\", \"pango_markup\":%s}",
                   mode->name, (mode->pango_markup ? "true" : "false"));
@@ -658,7 +647,7 @@ void switch_mode(const char *new_mode) {
         return;
     }
 
-    ELOG("ERROR: Mode not found\n");
+    ELOG("Mode not found\n");
 }
 
 static int reorder_binding_cmp(const void *a, const void *b) {
@@ -870,8 +859,6 @@ static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) {
     xcb_intern_atom_reply_t *atom_reply;
     size_t content_max_words = 256;
 
-    xcb_window_t root = root_screen->root;
-
     atom_reply = xcb_intern_atom_reply(
         conn, xcb_intern_atom(conn, 0, strlen("_XKB_RULES_NAMES"), "_XKB_RULES_NAMES"), NULL);
     if (atom_reply == NULL)
@@ -968,10 +955,7 @@ bool load_keymap(void) {
             .options = NULL};
         if (fill_rmlvo_from_root(&names) == -1) {
             ELOG("Could not get _XKB_RULES_NAMES atom from root window, falling back to defaults.\n");
-            if ((new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0)) == NULL) {
-                ELOG("xkb_keymap_new_from_names(NULL) failed\n");
-                return false;
-            }
+            /* Using NULL for the fields of xkb_rule_names. */
         }
         new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0);
         free((char *)names.rules);
@@ -980,7 +964,7 @@ bool load_keymap(void) {
         free((char *)names.variant);
         free((char *)names.options);
         if (new_keymap == NULL) {
-            ELOG("xkb_keymap_new_from_names(RMLVO) failed\n");
+            ELOG("xkb_keymap_new_from_names failed\n");
             return false;
         }
     }
index b036c5f8033c29e84b2d91b475b57daff8d64572..1218a4c21be5864517224fc063d61f71aefc8da2 100644 (file)
@@ -44,9 +44,6 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
         case BORDER_BOTTOM:
             search_direction = D_DOWN;
             break;
-        default:
-            assert(false);
-            break;
     }
 
     bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
@@ -233,15 +230,12 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
          event->detail == XCB_BUTTON_SCROLL_LEFT ||
          event->detail == XCB_BUTTON_SCROLL_RIGHT)) {
         DLOG("Scrolling on a window decoration\n");
-        orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ);
-        /* Focus the currently focused container on the same level that the
-         * user scrolled on. e.g. the tabbed decoration contains
-         * "urxvt | i3: V[xterm geeqie] | firefox",
-         * focus is on the xterm, but the user scrolled on urxvt.
-         * The splitv container will be focused. */
+        orientation_t orientation = con_orientation(con->parent);
+        /* Use the focused child of the tabbed / stacked container, not the
+         * container the user scrolled on. */
         Con *focused = con->parent;
         focused = TAILQ_FIRST(&(focused->focus_head));
-        con_activate(focused);
+        con_activate(con_descend_focused(focused));
         /* To prevent scrolling from going outside the container (see ticket
          * #557), we first check if scrolling is possible at all. */
         bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
@@ -260,7 +254,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
 
     /* 3: For floating containers, we also want to raise them on click.
      * We will skip handling events on floating cons in fullscreen mode */
-    Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
+    Con *fs = con_get_fullscreen_covering_ws(ws);
     if (floatingcon != NULL && fs != con) {
         /* 4: floating_modifier plus left mouse button drags */
         if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) {
index 899bbb90c8f0854254b4d36b52e0e2a21619e034..eecd59fc063f4f7cff0ff9c398ce1bcc3c205c3c 100644 (file)
@@ -269,6 +269,33 @@ static void move_matches_to_workspace(Con *ws) {
     }
 }
 
+#define CHECK_MOVE_CON_TO_WORKSPACE                                                          \
+    do {                                                                                     \
+        HANDLE_EMPTY_MATCH;                                                                  \
+        if (TAILQ_EMPTY(&owindows)) {                                                        \
+            yerror("Nothing to move: specified criteria don't match any window");            \
+            return;                                                                          \
+        } else {                                                                             \
+            bool found = false;                                                              \
+            owindow *current = TAILQ_FIRST(&owindows);                                       \
+            while (current) {                                                                \
+                owindow *next = TAILQ_NEXT(current, owindows);                               \
+                                                                                             \
+                if (current->con->type == CT_WORKSPACE && !con_has_children(current->con)) { \
+                    TAILQ_REMOVE(&owindows, current, owindows);                              \
+                } else {                                                                     \
+                    found = true;                                                            \
+                }                                                                            \
+                                                                                             \
+                current = next;                                                              \
+            }                                                                                \
+            if (!found) {                                                                    \
+                yerror("Nothing to move: workspace empty");                                  \
+                return;                                                                      \
+            }                                                                                \
+        }                                                                                    \
+    } while (0)
+
 /*
  * Implementation of 'move [window|container] [to] workspace
  * next|prev|next_on_output|prev_on_output|current'.
@@ -277,17 +304,7 @@ static void move_matches_to_workspace(Con *ws) {
 void cmd_move_con_to_workspace(I3_CMD, const char *which) {
     DLOG("which=%s\n", which);
 
-    /* We have nothing to move:
-     *  when criteria was specified but didn't match any window or
-     *  when criteria wasn't specified and we don't have any window focused. */
-    if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
-        (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
-         !con_has_children(focused))) {
-        ysuccess(false);
-        return;
-    }
-
-    HANDLE_EMPTY_MATCH;
+    CHECK_MOVE_CON_TO_WORKSPACE;
 
     /* get the workspace */
     Con *ws;
@@ -302,8 +319,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) {
     else if (strcmp(which, "current") == 0)
         ws = con_get_workspace(focused);
     else {
-        ELOG("BUG: called with which=%s\n", which);
-        ysuccess(false);
+        yerror("BUG: called with which=%s", which);
         return;
     }
 
@@ -314,7 +330,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) {
     ysuccess(true);
 }
 
-/**
+/*
  * Implementation of 'move [window|container] [to] workspace back_and_forth'.
  *
  */
@@ -338,36 +354,21 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
  * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace <name>'.
  *
  */
-void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_forth) {
+void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *no_auto_back_and_forth) {
     if (strncasecmp(name, "__", strlen("__")) == 0) {
-        LOG("You cannot move containers to i3-internal workspaces (\"%s\").\n", name);
-        ysuccess(false);
+        yerror("You cannot move containers to i3-internal workspaces (\"%s\").", name);
         return;
     }
 
-    const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
-
-    /* We have nothing to move:
-     *  when criteria was specified but didn't match any window or
-     *  when criteria wasn't specified and we don't have any window focused. */
-    if (!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) {
-        ELOG("No windows match your criteria, cannot move.\n");
-        ysuccess(false);
-        return;
-    } else if (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
-               !con_has_children(focused)) {
-        ysuccess(false);
-        return;
-    }
+    CHECK_MOVE_CON_TO_WORKSPACE;
 
     LOG("should move window to workspace %s\n", name);
     /* get the workspace */
     Con *ws = workspace_get(name, NULL);
 
-    if (!no_auto_back_and_forth)
+    if (no_auto_back_and_forth == NULL) {
         ws = maybe_auto_back_and_forth_workspace(ws);
-
-    HANDLE_EMPTY_MATCH;
+    }
 
     move_matches_to_workspace(ws);
 
@@ -380,43 +381,26 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_au
  * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace number <name>'.
  *
  */
-void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) {
-    const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
-
-    /* We have nothing to move:
-     *  when criteria was specified but didn't match any window or
-     *  when criteria wasn't specified and we don't have any window focused. */
-    if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
-        (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
-         !con_has_children(focused))) {
-        ysuccess(false);
-        return;
-    }
+void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth) {
+    CHECK_MOVE_CON_TO_WORKSPACE;
 
     LOG("should move window to workspace %s\n", which);
-    /* get the workspace */
-    Con *output, *ws = NULL;
 
     long parsed_num = ws_name_to_number(which);
-
     if (parsed_num == -1) {
         LOG("Could not parse initial part of \"%s\" as a number.\n", which);
         yerror("Could not parse number \"%s\"", which);
         return;
     }
 
-    TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
-    GREP_FIRST(ws, output_get_content(output),
-               child->num == parsed_num);
-
+    Con *ws = get_existing_workspace_by_num(parsed_num);
     if (!ws) {
         ws = workspace_get(which, NULL);
     }
 
-    if (!no_auto_back_and_forth)
+    if (no_auto_back_and_forth == NULL) {
         ws = maybe_auto_back_and_forth_workspace(ws);
-
-    HANDLE_EMPTY_MATCH;
+    }
 
     move_matches_to_workspace(ws);
 
@@ -425,110 +409,104 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no
     ysuccess(true);
 }
 
-static void cmd_resize_floating(I3_CMD, const char *way, const char *direction, Con *floating_con, int px) {
-    LOG("floating resize\n");
+/*
+ * Convert a string direction ("left", "right", etc.) to a direction_t. Assumes
+ * valid direction string.
+ */
+static direction_t parse_direction(const char *str) {
+    if (strcmp(str, "left") == 0) {
+        return D_LEFT;
+    } else if (strcmp(str, "right") == 0) {
+        return D_RIGHT;
+    } else if (strcmp(str, "up") == 0) {
+        return D_UP;
+    } else if (strcmp(str, "down") == 0) {
+        return D_DOWN;
+    } else {
+        ELOG("Invalid direction. This is a parser bug.\n");
+        assert(false);
+    }
+}
+
+static void cmd_resize_floating(I3_CMD, const char *way, const char *direction_str, Con *floating_con, int px) {
     Rect old_rect = floating_con->rect;
     Con *focused_con = con_descend_focused(floating_con);
 
+    direction_t direction;
+    if (strcmp(direction_str, "height") == 0) {
+        direction = D_DOWN;
+    } else if (strcmp(direction_str, "width") == 0) {
+        direction = D_RIGHT;
+    } else {
+        direction = parse_direction(direction_str);
+    }
+    orientation_t orientation = orientation_from_direction(direction);
+
     /* ensure that resize will take place even if pixel increment is smaller than
      * height increment or width increment.
      * fixes #1011 */
     const i3Window *window = focused_con->window;
     if (window != NULL) {
-        if (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0 ||
-            strcmp(direction, "height") == 0) {
-            if (px < 0)
+        if (orientation == VERT) {
+            if (px < 0) {
                 px = (-px < window->height_increment) ? -window->height_increment : px;
-            else
+            } else {
                 px = (px < window->height_increment) ? window->height_increment : px;
-        } else if (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0) {
-            if (px < 0)
+            }
+        } else {
+            if (px < 0) {
                 px = (-px < window->width_increment) ? -window->width_increment : px;
-            else
+            } else {
                 px = (px < window->width_increment) ? window->width_increment : px;
+            }
         }
     }
 
-    if (strcmp(direction, "up") == 0) {
-        floating_con->rect.height += px;
-    } else if (strcmp(direction, "down") == 0 || strcmp(direction, "height") == 0) {
+    if (orientation == VERT) {
         floating_con->rect.height += px;
-    } else if (strcmp(direction, "left") == 0) {
-        floating_con->rect.width += px;
     } else {
         floating_con->rect.width += px;
     }
-
     floating_check_size(floating_con);
 
     /* Did we actually resize anything or did the size constraints prevent us?
      * If we could not resize, exit now to not move the window. */
-    if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0)
+    if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0) {
         return;
+    }
 
-    if (strcmp(direction, "up") == 0) {
+    if (direction == D_UP) {
         floating_con->rect.y -= (floating_con->rect.height - old_rect.height);
-    } else if (strcmp(direction, "left") == 0) {
+    } else if (direction == D_LEFT) {
         floating_con->rect.x -= (floating_con->rect.width - old_rect.width);
     }
 
     /* If this is a scratchpad window, don't auto center it from now on. */
-    if (floating_con->scratchpad_state == SCRATCHPAD_FRESH)
+    if (floating_con->scratchpad_state == SCRATCHPAD_FRESH) {
         floating_con->scratchpad_state = SCRATCHPAD_CHANGED;
+    }
 }
 
-static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
-    LOG("tiling resize\n");
+static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *direction, int px, int ppt) {
     Con *second = NULL;
     Con *first = current;
-    direction_t search_direction;
-    if (!strcmp(direction, "left"))
-        search_direction = D_LEFT;
-    else if (!strcmp(direction, "right"))
-        search_direction = D_RIGHT;
-    else if (!strcmp(direction, "up"))
-        search_direction = D_UP;
-    else
-        search_direction = D_DOWN;
+    direction_t search_direction = parse_direction(direction);
 
     bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
     if (!res) {
-        LOG("No second container in this direction found.\n");
-        ysuccess(false);
+        yerror("No second container found in this direction.");
         return false;
     }
 
-    /* get the default percentage */
-    int children = con_num_children(first->parent);
-    LOG("ins. %d children\n", children);
-    double percentage = 1.0 / children;
-    LOG("default percentage = %f\n", percentage);
-
-    /* resize */
-    LOG("first->percent before = %f\n", first->percent);
-    LOG("second->percent before = %f\n", second->percent);
-    if (first->percent == 0.0)
-        first->percent = percentage;
-    if (second->percent == 0.0)
-        second->percent = percentage;
-    double new_first_percent = first->percent + ((double)ppt / 100.0);
-    double new_second_percent = second->percent - ((double)ppt / 100.0);
-    LOG("new_first_percent = %f\n", new_first_percent);
-    LOG("new_second_percent = %f\n", new_second_percent);
-    /* Ensure that the new percentages are positive. */
-    if (new_first_percent > 0.0 && new_second_percent > 0.0) {
-        first->percent = new_first_percent;
-        second->percent = new_second_percent;
-        LOG("first->percent after = %f\n", first->percent);
-        LOG("second->percent after = %f\n", second->percent);
-    } else {
-        LOG("Not resizing, already at minimum size\n");
+    if (ppt) {
+        /* For backwards compatibility, 'X px or Y ppt' means that ppt is
+         * preferred. */
+        px = 0;
     }
-
-    return true;
+    return resize_neighboring_cons(first, second, px, ppt);
 }
 
-static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
+static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *direction, int px, double ppt) {
     LOG("width/height resize\n");
 
     /* get the appropriate current container (skip stacked/tabbed cons) */
@@ -536,7 +514,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way
     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);
+        yerror("Failed to find appropriate tiling containers for resize operation");
         return false;
     }
 
@@ -554,25 +532,34 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way
             child->percent = percentage;
     }
 
-    double new_current_percent = current->percent + ((double)ppt / 100.0);
-    double subtract_percent = ((double)ppt / 100.0) / (children - 1);
+    double new_current_percent;
+    double subtract_percent;
+    if (ppt != 0.0) {
+        new_current_percent = current->percent + ppt;
+    } else {
+        new_current_percent = px_resize_to_percent(current, px);
+        ppt = new_current_percent - current->percent;
+    }
+    subtract_percent = ppt / (children - 1);
+    if (ppt < 0.0 && new_current_percent < percent_for_1px(current)) {
+        yerror("Not resizing, container would end with less than 1px");
+        return false;
+    }
+
     LOG("new_current_percent = %f\n", new_current_percent);
     LOG("subtract_percent = %f\n", subtract_percent);
     /* Ensure that the new percentages are positive. */
-    TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
-        if (child == current)
-            continue;
-        if (child->percent - subtract_percent <= 0.0) {
-            LOG("Not resizing, already at minimum size (child %p would end up with a size of %.f\n", child, child->percent - subtract_percent);
-            ysuccess(false);
-            return false;
+    if (subtract_percent >= 0.0) {
+        TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
+            if (child == current) {
+                continue;
+            }
+            if (child->percent - subtract_percent < percent_for_1px(child)) {
+                yerror("Not resizing, already at minimum size (child %p would end up with a size of %.f", child, child->percent - subtract_percent);
+                return false;
+            }
         }
     }
-    if (new_current_percent <= 0.0) {
-        LOG("Not resizing, already at minimum size\n");
-        ysuccess(false);
-        return false;
-    }
 
     current->percent = new_current_percent;
     LOG("current->percent after = %f\n", current->percent);
@@ -614,12 +601,15 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
         } else {
             if (strcmp(direction, "width") == 0 ||
                 strcmp(direction, "height") == 0) {
+                const double ppt = (double)resize_ppt / 100.0;
                 if (!cmd_resize_tiling_width_height(current_match, cmd_output,
-                                                    current->con, way, direction, resize_ppt))
+                                                    current->con, direction,
+                                                    resize_px, ppt))
                     return;
             } else {
                 if (!cmd_resize_tiling_direction(current_match, cmd_output,
-                                                 current->con, way, direction, resize_ppt))
+                                                 current->con, direction,
+                                                 resize_px, resize_ppt))
                     return;
             }
         }
@@ -630,6 +620,35 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
     ysuccess(true);
 }
 
+static bool resize_set_tiling(I3_CMD, Con *target, orientation_t resize_orientation, bool is_ppt, long target_size) {
+    direction_t search_direction;
+    char *mode;
+    if (resize_orientation == HORIZ) {
+        search_direction = D_LEFT;
+        mode = "width";
+    } else {
+        search_direction = D_DOWN;
+        mode = "height";
+    }
+
+    /* Get the appropriate current container (skip stacked/tabbed cons) */
+    Con *dummy;
+    resize_find_tiling_participants(&target, &dummy, search_direction, true);
+
+    /* Calculate new size for the target container */
+    double ppt = 0.0;
+    int px = 0;
+    if (is_ppt) {
+        ppt = (double)target_size / 100.0 - target->percent;
+    } else {
+        px = target_size - (resize_orientation == HORIZ ? target->rect.width : target->rect.height);
+    }
+
+    /* Perform resizing and report failure if not possible */
+    return cmd_resize_tiling_width_height(current_match, cmd_output,
+                                          target, mode, px, ppt);
+}
+
 /*
  * Implementation of 'resize set <width> [px | ppt] <height> [px | ppt]'.
  *
@@ -650,12 +669,12 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
         if ((floating_con = con_inside_floating(current->con))) {
             Con *output = con_get_output(floating_con);
             if (cwidth == 0) {
-                cwidth = output->rect.width;
+                cwidth = floating_con->rect.width;
             } else if (mode_width && strcmp(mode_width, "ppt") == 0) {
                 cwidth = output->rect.width * ((double)cwidth / 100.0);
             }
             if (cheight == 0) {
-                cheight = output->rect.height;
+                cheight = floating_con->rect.height;
             } else if (mode_height && strcmp(mode_height, "ppt") == 0) {
                 cheight = output->rect.height * ((double)cheight / 100.0);
             }
@@ -666,56 +685,15 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
                 continue;
             }
 
-            if (cwidth > 0 && mode_width && strcmp(mode_width, "ppt") == 0) {
-                /* get the appropriate current container (skip stacked/tabbed cons) */
-                Con *target = current->con;
-                Con *dummy;
-                resize_find_tiling_participants(&target, &dummy, D_LEFT, true);
-
-                /* Calculate new size for the target container */
-                double current_percent = target->percent;
-                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 (cwidth > 0) {
+                bool is_ppt = mode_width && strcmp(mode_width, "ppt") == 0;
+                success &= resize_set_tiling(current_match, cmd_output, current->con,
+                                             HORIZ, is_ppt, cwidth);
             }
-
-            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;
-                }
+            if (cheight > 0) {
+                bool is_ppt = mode_height && strcmp(mode_height, "ppt") == 0;
+                success &= resize_set_tiling(current_match, cmd_output, current->con,
+                                             VERT, is_ppt, cheight);
             }
         }
     }
@@ -724,6 +702,26 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
     ysuccess(success);
 }
 
+static int border_width_from_style(border_style_t border_style, long border_width, Con *con) {
+    if (border_style == BS_NONE) {
+        return 0;
+    }
+    if (border_width >= 0) {
+        return logical_px(border_width);
+    }
+
+    const bool is_floating = con_inside_floating(con) != NULL;
+    /* Load the configured defaults. */
+    if (is_floating && border_style == config.default_floating_border) {
+        return config.default_floating_border_width;
+    } else if (!is_floating && border_style == config.default_border) {
+        return config.default_border_width;
+    } else {
+        /* Use some hardcoded values. */
+        return logical_px(border_style == BS_NORMAL ? 2 : 1);
+    }
+}
+
 /*
  * Implementation of 'border normal|pixel [<n>]', 'border none|1pixel|toggle'.
  *
@@ -736,40 +734,26 @@ void cmd_border(I3_CMD, const char *border_style_str, long border_width) {
 
     TAILQ_FOREACH(current, &owindows, owindows) {
         DLOG("matching: %p / %s\n", current->con, current->con->name);
-        int border_style = current->con->border_style;
-        int con_border_width = border_width;
 
+        border_style_t border_style;
         if (strcmp(border_style_str, "toggle") == 0) {
-            border_style++;
-            border_style %= 3;
-            if (border_style == BS_NORMAL)
-                con_border_width = 2;
-            else if (border_style == BS_NONE)
-                con_border_width = 0;
-            else if (border_style == BS_PIXEL)
-                con_border_width = 1;
+            border_style = (current->con->border_style + 1) % 3;
+        } else if (strcmp(border_style_str, "normal") == 0) {
+            border_style = BS_NORMAL;
+        } else if (strcmp(border_style_str, "pixel") == 0) {
+            border_style = BS_PIXEL;
+        } else if (strcmp(border_style_str, "none") == 0) {
+            border_style = BS_NONE;
         } else {
-            if (strcmp(border_style_str, "normal") == 0) {
-                border_style = BS_NORMAL;
-            } else if (strcmp(border_style_str, "pixel") == 0) {
-                border_style = BS_PIXEL;
-            } else if (strcmp(border_style_str, "1pixel") == 0) {
-                border_style = BS_PIXEL;
-                con_border_width = 1;
-            } else if (strcmp(border_style_str, "none") == 0) {
-                border_style = BS_NONE;
-            } else {
-                ELOG("BUG: called with border_style=%s\n", border_style_str);
-                ysuccess(false);
-                return;
-            }
+            yerror("BUG: called with border_style=%s", border_style_str);
+            return;
         }
 
-        con_set_border_style(current->con, border_style, logical_px(con_border_width));
+        const int con_border_width = border_width_from_style(border_style, border_width, current->con);
+        con_set_border_style(current->con, border_style, con_border_width);
     }
 
     cmd_output->needs_tree_render = true;
-    // XXX: default reply for now, make this a better reply
     ysuccess(true);
 }
 
@@ -789,11 +773,10 @@ void cmd_nop(I3_CMD, const char *comment) {
  *
  */
 void cmd_append_layout(I3_CMD, const char *cpath) {
-    char *path = sstrdup(cpath);
-    LOG("Appending layout \"%s\"\n", path);
+    LOG("Appending layout \"%s\"\n", cpath);
 
     /* Make sure we allow paths like '~/.i3/layout.json' */
-    path = resolve_tilde(path);
+    char *path = resolve_tilde(cpath);
 
     char *buf = NULL;
     ssize_t len;
@@ -870,8 +853,7 @@ void cmd_workspace(I3_CMD, const char *which) {
     DLOG("which=%s\n", which);
 
     if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
-        LOG("Cannot switch workspace while in global fullscreen\n");
-        ysuccess(false);
+        yerror("Cannot switch workspace while in global fullscreen");
         return;
     }
 
@@ -884,8 +866,7 @@ void cmd_workspace(I3_CMD, const char *which) {
     else if (strcmp(which, "prev_on_output") == 0)
         ws = workspace_prev_on_output();
     else {
-        ELOG("BUG: called with which=%s\n", which);
-        ysuccess(false);
+        yerror("BUG: called with which=%s", which);
         return;
     }
 
@@ -902,26 +883,19 @@ void cmd_workspace(I3_CMD, const char *which) {
  */
 void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) {
     const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
-    Con *output, *workspace = NULL;
 
     if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
-        LOG("Cannot switch workspace while in global fullscreen\n");
-        ysuccess(false);
+        yerror("Cannot switch workspace while in global fullscreen");
         return;
     }
 
     long parsed_num = ws_name_to_number(which);
-
     if (parsed_num == -1) {
-        LOG("Could not parse initial part of \"%s\" as a number.\n", which);
-        yerror("Could not parse number \"%s\"", which);
+        yerror("Could not parse initial part of \"%s\" as a number.", which);
         return;
     }
 
-    TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
-    GREP_FIRST(workspace, output_get_content(output),
-               child->num == parsed_num);
-
+    Con *workspace = get_existing_workspace_by_num(parsed_num);
     if (!workspace) {
         LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num);
         ysuccess(true);
@@ -946,8 +920,7 @@ void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_a
  */
 void cmd_workspace_back_and_forth(I3_CMD) {
     if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
-        LOG("Cannot switch workspace while in global fullscreen\n");
-        ysuccess(false);
+        yerror("Cannot switch workspace while in global fullscreen");
         return;
     }
 
@@ -966,14 +939,12 @@ void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_
     const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
 
     if (strncasecmp(name, "__", strlen("__")) == 0) {
-        LOG("You cannot switch to the i3-internal workspaces (\"%s\").\n", name);
-        ysuccess(false);
+        yerror("You cannot switch to the i3-internal workspaces (\"%s\").", name);
         return;
     }
 
     if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
-        LOG("Cannot switch workspace while in global fullscreen\n");
-        ysuccess(false);
+        yerror("Cannot switch workspace while in global fullscreen");
         return;
     }
 
@@ -998,7 +969,7 @@ void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle) {
 
     owindow *current = TAILQ_FIRST(&owindows);
     if (current == NULL) {
-        ysuccess(false);
+        yerror("Given criteria don't match a window");
         return;
     }
 
@@ -1140,16 +1111,26 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) {
             continue;
         }
 
-        bool success = workspace_move_to_output(ws, name);
+        Output *current_output = get_output_for_con(ws);
+        if (current_output == NULL) {
+            yerror("Cannot get current output. This is a bug in i3.");
+            return;
+        }
+
+        Output *target_output = get_output_from_string(current_output, name);
+        if (!target_output) {
+            yerror("Could not get output from string \"%s\"", name);
+            return;
+        }
+
+        bool success = workspace_move_to_output(ws, target_output);
         if (!success) {
-            ELOG("Failed to move workspace to output.\n");
-            ysuccess(false);
+            yerror("Failed to move workspace to output.");
             return;
         }
     }
 
     cmd_output->needs_tree_render = true;
-    // XXX: default reply for now, make this a better reply
     ysuccess(true);
 }
 
@@ -1208,8 +1189,7 @@ void cmd_kill(I3_CMD, const char *kill_mode_str) {
     else if (strcmp(kill_mode_str, "client") == 0)
         kill_mode = KILL_CLIENT;
     else {
-        ELOG("BUG: called with kill_mode=%s\n", kill_mode_str);
-        ysuccess(false);
+        yerror("BUG: called with kill_mode=%s", kill_mode_str);
         return;
     }
 
@@ -1235,7 +1215,6 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) {
     DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
     start_application(command, no_startup_id);
 
-    // XXX: default reply for now, make this a better reply
     ysuccess(true);
 }
 
@@ -1244,20 +1223,19 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) {
  *
  */
 void cmd_focus_direction(I3_CMD, const char *direction) {
-    DLOG("direction = *%s*\n", direction);
-
-    if (strcmp(direction, "left") == 0)
-        tree_next('p', HORIZ);
-    else if (strcmp(direction, "right") == 0)
-        tree_next('n', HORIZ);
-    else if (strcmp(direction, "up") == 0)
-        tree_next('p', VERT);
-    else if (strcmp(direction, "down") == 0)
-        tree_next('n', VERT);
-    else {
-        ELOG("Invalid focus direction (%s)\n", direction);
-        ysuccess(false);
-        return;
+    switch (parse_direction(direction)) {
+        case D_LEFT:
+            tree_next('p', HORIZ);
+            break;
+        case D_RIGHT:
+            tree_next('n', HORIZ);
+            break;
+        case D_UP:
+            tree_next('p', VERT);
+            break;
+        case D_DOWN:
+            tree_next('n', VERT);
+            break;
     }
 
     cmd_output->needs_tree_render = true;
@@ -1272,7 +1250,7 @@ void cmd_focus_direction(I3_CMD, const char *direction) {
 static void cmd_focus_force_focus(Con *con) {
     /* Disable fullscreen container in workspace with container to be focused. */
     Con *ws = con_get_workspace(con);
-    Con *fullscreen_on_ws = (focused && focused->fullscreen_mode == CF_GLOBAL) ? focused : con_get_fullscreen_con(ws, CF_OUTPUT);
+    Con *fullscreen_on_ws = con_get_fullscreen_covering_ws(ws);
     if (fullscreen_on_ws && fullscreen_on_ws != con && !con_has_parent(con, fullscreen_on_ws)) {
         con_disable_fullscreen(fullscreen_on_ws);
     }
@@ -1482,29 +1460,37 @@ void cmd_sticky(I3_CMD, const char *action) {
  * Implementation of 'move <direction> [<pixels> [px]]'.
  *
  */
-void cmd_move_direction(I3_CMD, const char *direction, long move_px) {
+void cmd_move_direction(I3_CMD, const char *direction_str, long move_px) {
     owindow *current;
     HANDLE_EMPTY_MATCH;
 
     Con *initially_focused = focused;
+    direction_t direction = parse_direction(direction_str);
 
     TAILQ_FOREACH(current, &owindows, owindows) {
-        DLOG("moving in direction %s, px %ld\n", direction, move_px);
+        DLOG("moving in direction %s, px %ld\n", direction_str, move_px);
         if (con_is_floating(current->con)) {
             DLOG("floating move with %ld pixels\n", move_px);
             Rect newrect = current->con->parent->rect;
-            if (strcmp(direction, "left") == 0) {
-                newrect.x -= move_px;
-            } else if (strcmp(direction, "right") == 0) {
-                newrect.x += move_px;
-            } else if (strcmp(direction, "up") == 0) {
-                newrect.y -= move_px;
-            } else if (strcmp(direction, "down") == 0) {
-                newrect.y += move_px;
+
+            switch (direction) {
+                case D_LEFT:
+                    newrect.x -= move_px;
+                    break;
+                case D_RIGHT:
+                    newrect.x += move_px;
+                    break;
+                case D_UP:
+                    newrect.y -= move_px;
+                    break;
+                case D_DOWN:
+                    newrect.y += move_px;
+                    break;
             }
+
             floating_reposition(current->con->parent, newrect);
         } else {
-            tree_move(current->con, (strcmp(direction, "right") == 0 ? D_RIGHT : (strcmp(direction, "left") == 0 ? D_LEFT : (strcmp(direction, "up") == 0 ? D_UP : D_DOWN))));
+            tree_move(current->con, direction);
             cmd_output->needs_tree_render = true;
         }
     }
@@ -1670,8 +1656,7 @@ void cmd_focus_output(I3_CMD, const char *name) {
     output = get_output_from_string(current_output, name);
 
     if (!output) {
-        LOG("No such output found.\n");
-        ysuccess(false);
+        yerror("No such output found.");
         return;
     }
 
@@ -1679,7 +1664,7 @@ void cmd_focus_output(I3_CMD, const char *name) {
     Con *ws = NULL;
     GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
     if (!ws) {
-        ysuccess(false);
+        yerror("BUG: No workspace found on output.");
         return;
     }
 
@@ -1694,7 +1679,7 @@ void cmd_focus_output(I3_CMD, const char *name) {
  * Implementation of 'move [window|container] [to] [absolute] position <px> [px] <px> [px]
  *
  */
-void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) {
+void cmd_move_window_to_position(I3_CMD, long x, long y) {
     bool has_error = false;
 
     owindow *current;
@@ -1712,27 +1697,18 @@ void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) {
             continue;
         }
 
-        if (strcmp(method, "absolute") == 0) {
-            current->con->parent->rect.x = x;
-            current->con->parent->rect.y = y;
-
-            DLOG("moving to absolute position %ld %ld\n", x, y);
-            floating_maybe_reassign_ws(current->con->parent);
-            cmd_output->needs_tree_render = true;
-        }
-
-        if (strcmp(method, "position") == 0) {
-            Rect newrect = current->con->parent->rect;
+        Rect newrect = current->con->parent->rect;
 
-            DLOG("moving to position %ld %ld\n", x, y);
-            newrect.x = x;
-            newrect.y = y;
+        DLOG("moving to position %ld %ld\n", x, y);
+        newrect.x = x;
+        newrect.y = y;
 
-            floating_reposition(current->con->parent, newrect);
+        if (!floating_reposition(current->con->parent, newrect)) {
+            yerror("Cannot move window/container out of bounds.");
+            has_error = true;
         }
     }
 
-    // XXX: default reply for now, make this a better reply
     if (!has_error)
         ysuccess(true);
 }
@@ -1832,19 +1808,20 @@ void cmd_move_scratchpad(I3_CMD) {
 void cmd_scratchpad_show(I3_CMD) {
     DLOG("should show scratchpad window\n");
     owindow *current;
+    bool result = false;
 
     if (match_is_empty(current_match)) {
-        scratchpad_show(NULL);
+        result = scratchpad_show(NULL);
     } else {
         TAILQ_FOREACH(current, &owindows, owindows) {
             DLOG("matching: %p / %s\n", current->con, current->con->name);
-            scratchpad_show(current->con);
+            result |= scratchpad_show(current->con);
         }
     }
 
     cmd_output->needs_tree_render = true;
-    // XXX: default reply for now, make this a better reply
-    ysuccess(true);
+
+    ysuccess(result);
 }
 
 /*
@@ -1856,7 +1833,11 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
 
     owindow *match = TAILQ_FIRST(&owindows);
     if (match == NULL) {
-        DLOG("No match found for swapping.\n");
+        yerror("No match found for swapping.");
+        return;
+    }
+    if (match->con == NULL) {
+        yerror("Match %p has no container.", match);
         return;
     }
 
@@ -1864,7 +1845,7 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
     if (strcmp(mode, "id") == 0) {
         long target;
         if (!parse_long(arg, &target, 0)) {
-            yerror("Failed to parse %s into a window id.\n", arg);
+            yerror("Failed to parse %s into a window id.", arg);
             return;
         }
 
@@ -1872,7 +1853,7 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
     } else if (strcmp(mode, "con_id") == 0) {
         long target;
         if (!parse_long(arg, &target, 0)) {
-            yerror("Failed to parse %s into a container id.\n", arg);
+            yerror("Failed to parse %s into a container id.", arg);
             return;
         }
 
@@ -1880,29 +1861,24 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
     } else if (strcmp(mode, "mark") == 0) {
         con = con_by_mark(arg);
     } else {
-        yerror("Unhandled swap mode \"%s\". This is a bug.\n", mode);
+        yerror("Unhandled swap mode \"%s\". This is a bug.", mode);
         return;
     }
 
     if (con == NULL) {
-        yerror("Could not find container for %s = %s\n", mode, arg);
+        yerror("Could not find container for %s = %s", mode, arg);
         return;
     }
 
     if (match != TAILQ_LAST(&owindows, owindows_head)) {
-        DLOG("More than one container matched the swap command, only using the first one.");
-    }
-
-    if (match->con == NULL) {
-        DLOG("Match %p has no container.\n", match);
-        ysuccess(false);
-        return;
+        LOG("More than one container matched the swap command, only using the first one.");
     }
 
     DLOG("Swapping %p with %p.\n", match->con, con);
     bool result = con_swap(match->con, con);
 
     cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
     ysuccess(result);
 }
 
@@ -1955,8 +1931,7 @@ void cmd_title_format(I3_CMD, const char *format) {
  */
 void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
     if (strncasecmp(new_name, "__", strlen("__")) == 0) {
-        LOG("Cannot rename workspace to \"%s\": names starting with __ are i3-internal.\n", new_name);
-        ysuccess(false);
+        yerror("Cannot rename workspace to \"%s\": names starting with __ are i3-internal.", new_name);
         return;
     }
     if (old_name) {
@@ -1965,11 +1940,9 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
         LOG("Renaming current workspace to \"%s\"\n", new_name);
     }
 
-    Con *output, *workspace = NULL;
+    Con *workspace;
     if (old_name) {
-        TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
-        GREP_FIRST(workspace, output_get_content(output),
-                   !strcasecmp(child->name, old_name));
+        workspace = get_existing_workspace_by_name(old_name);
     } else {
         workspace = con_get_workspace(focused);
         old_name = workspace->name;
@@ -1980,10 +1953,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
         return;
     }
 
-    Con *check_dest = NULL;
-    TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
-    GREP_FIRST(check_dest, output_get_content(output),
-               !strcasecmp(child->name, new_name));
+    Con *check_dest = get_existing_workspace_by_name(new_name);
 
     /* If check_dest == workspace, the user might be changing the case of the
      * workspace, or it might just be a no-op. */
@@ -2003,9 +1973,11 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
 
     /* By re-attaching, the sort order will be correct afterwards. */
     Con *previously_focused = focused;
+    Con *previously_focused_content = focused->type == CT_WORKSPACE ? focused->parent : NULL;
     Con *parent = workspace->parent;
     con_detach(workspace);
     con_attach(workspace, parent, false);
+    ipc_send_workspace_event("rename", workspace, NULL);
 
     /* Move the workspace to the correct output if it has an assignment */
     struct Workspace_Assignment *assignment = NULL;
@@ -2016,21 +1988,40 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
             continue;
         }
 
-        workspace_move_to_output(workspace, assignment->output);
-
-        if (previously_focused)
-            workspace_show(con_get_workspace(previously_focused));
+        Output *target_output = get_output_by_name(assignment->output, true);
+        if (!target_output) {
+            LOG("Could not get output named \"%s\"\n", assignment->output);
+            continue;
+        }
+        if (!output_triggers_assignment(target_output, assignment)) {
+            continue;
+        }
+        workspace_move_to_output(workspace, target_output);
 
         break;
     }
 
-    /* Restore the previous focus since con_attach messes with the focus. */
-    con_activate(previously_focused);
+    bool can_restore_focus = previously_focused != NULL;
+    /* NB: If previously_focused is a workspace we can't work directly with it
+     * since it might have been cleaned up by workspace_show() already,
+     * depending on the focus order/number of other workspaces on the output.
+     * Instead, we loop through the available workspaces and only focus
+     * previously_focused if we still find it. */
+    if (previously_focused_content) {
+        Con *workspace = NULL;
+        GREP_FIRST(workspace, previously_focused_content, child == previously_focused);
+        can_restore_focus &= (workspace != NULL);
+    }
+
+    if (can_restore_focus) {
+        /* Restore the previous focus since con_attach messes with the focus. */
+        workspace_show(con_get_workspace(previously_focused));
+        con_focus(previously_focused);
+    }
 
     cmd_output->needs_tree_render = true;
     ysuccess(true);
 
-    ipc_send_workspace_event("rename", workspace, NULL);
     ewmh_update_desktop_names();
     ewmh_update_desktop_viewport();
     ewmh_update_current_desktop();
@@ -2043,7 +2034,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
  * Implementation of 'bar mode dock|hide|invisible|toggle [<bar_id>]'
  *
  */
-bool cmd_bar_mode(const char *bar_mode, const char *bar_id) {
+static bool cmd_bar_mode(const char *bar_mode, const char *bar_id) {
     int mode = M_DOCK;
     bool toggle = false;
     if (strcmp(bar_mode, "dock") == 0)
@@ -2088,7 +2079,7 @@ bool cmd_bar_mode(const char *bar_mode, const char *bar_id) {
  * Implementation of 'bar hidden_state hide|show|toggle [<bar_id>]'
  *
  */
-bool cmd_bar_hidden_state(const char *bar_hidden_state, const char *bar_id) {
+static bool cmd_bar_hidden_state(const char *bar_hidden_state, const char *bar_id) {
     int hidden_state = S_SHOW;
     bool toggle = false;
     if (strcmp(bar_hidden_state, "hide") == 0)
@@ -2162,21 +2153,22 @@ void cmd_shmlog(I3_CMD, const char *argument) {
     else if (!strcmp(argument, "off"))
         shmlog_size = 0;
     else {
+        long new_size = 0;
+        if (!parse_long(argument, &new_size, 0)) {
+            yerror("Failed to parse %s into a shmlog size.", argument);
+            return;
+        }
         /* If shm logging now, restart logging with the new size. */
         if (shmlog_size > 0) {
             shmlog_size = 0;
             LOG("Restarting shm logging...\n");
             init_logging();
         }
-        shmlog_size = atoi(argument);
-        /* Make a weakly attempt at ensuring the argument is valid. */
-        if (shmlog_size <= 0)
-            shmlog_size = default_shmlog_size;
+        shmlog_size = (int)new_size;
     }
     LOG("%s shm logging\n", shmlog_size > 0 ? "Enabling" : "Disabling");
     init_logging();
     update_shmlog_atom();
-    // XXX: default reply for now, make this a better reply
     ysuccess(true);
 }
 
index 98f0665907dd36d759abebd4a55b09024a2f55c6..4299c008335570ced9c8daebc12bdaa1cefd74e9 100644 (file)
@@ -157,7 +157,7 @@ static long get_long(const char *identifier) {
 // TODO move to a common util
 static void clear_stack(void) {
     for (int c = 0; c < 10; c++) {
-        if (stack[c].type == STACK_STR && stack[c].val.str != NULL)
+        if (stack[c].type == STACK_STR)
             free(stack[c].val.str);
         stack[c].identifier = NULL;
         stack[c].val.str = NULL;
index 985d07da2dd8e58058fe7e0bea207f62ab7ac572..21d2f097af4d667c7ccd0b94afa7d25b209e1cb7 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -286,14 +286,14 @@ void con_close(Con *con, kill_window_t kill_window) {
         for (child = TAILQ_FIRST(&(con->focus_head)); child;) {
             nextchild = TAILQ_NEXT(child, focused);
             DLOG("killing child = %p.\n", child);
-            tree_close_internal(child, kill_window, false, false);
+            tree_close_internal(child, kill_window, false);
             child = nextchild;
         }
 
         return;
     }
 
-    tree_close_internal(con, kill_window, false, false);
+    tree_close_internal(con, kill_window, false);
 }
 
 /*
@@ -312,7 +312,7 @@ bool con_has_managed_window(Con *con) {
     return (con != NULL && con->window != NULL && con->window->id != XCB_WINDOW_NONE && con_get_workspace(con) != NULL);
 }
 
-/**
+/*
  * Returns true if this node has regular or floating children.
  *
  */
@@ -509,7 +509,24 @@ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) {
     return NULL;
 }
 
-/**
+/*
+ * Returns the fullscreen node that covers the given workspace if it exists.
+ * This is either a CF_GLOBAL fullscreen container anywhere or a CF_OUTPUT
+ * fullscreen container in the workspace.
+ *
+ */
+Con *con_get_fullscreen_covering_ws(Con *ws) {
+    if (!ws) {
+        return NULL;
+    }
+    Con *fs = con_get_fullscreen_con(croot, CF_GLOBAL);
+    if (!fs) {
+        return con_get_fullscreen_con(ws, CF_OUTPUT);
+    }
+    return fs;
+}
+
+/*
  * Returns true if the container is internal, such as __i3_scratch
  *
  */
@@ -879,7 +896,7 @@ int con_num_children(Con *con) {
     return children;
 }
 
-/**
+/*
  * Returns the number of visible non-floating children of this container.
  * For example, if the container contains a hsplit which has two children,
  * this will return 2 instead of 1.
@@ -919,6 +936,10 @@ int con_num_windows(Con *con) {
         num += con_num_windows(current);
     }
 
+    TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
+        num += con_num_windows(current);
+    }
+
     return num;
 }
 
@@ -1097,7 +1118,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
 
     /* Prevent moving if this would violate the fullscreen focus restrictions. */
     Con *target_ws = con_get_workspace(target);
-    if (!con_fullscreen_permits_focusing(target_ws)) {
+    if (!ignore_focus && !con_fullscreen_permits_focusing(target_ws)) {
         LOG("Cannot move out of a fullscreen container.\n");
         return false;
     }
@@ -1140,7 +1161,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
 
     /* 1: save the container which is going to be focused after the current
      * container is moved away */
-    Con *focus_next = con_next_focused(con);
+    Con *focus_next = NULL;
+    if (!ignore_focus && source_ws == current_ws) {
+        focus_next = con_descend_focused(source_ws);
+        if (focus_next == con || con_has_parent(focus_next, con)) {
+            focus_next = con_next_focused(con);
+        }
+    }
 
     /* 2: we go up one level, but only when target is a normal container */
     if (target->type != CT_WORKSPACE) {
@@ -1148,13 +1175,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
         target = target->parent;
     }
 
-    /* 3: if the target container is floating, we get the workspace instead.
-     * Only tiling windows need to get inserted next to the current container.
-     * */
-    Con *floatingcon = con_inside_floating(target);
-    if (floatingcon != NULL) {
+    /* 3: if the original target is the direct child of a floating container, we
+     * can't move con next to it - floating containers have only one child - so
+     * we get the workspace instead. */
+    if (target->type == CT_FLOATING_CON) {
         DLOG("floatingcon, going up even further\n");
-        target = floatingcon->parent;
+        orig_target = target;
+        target = target->parent;
     }
 
     if (con->type == CT_FLOATING_CON) {
@@ -1170,20 +1197,6 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
             floating_fix_coordinates(con, &(source_output->rect), &(dest_output->rect));
         } else
             DLOG("Not fixing coordinates, fix_coordinates flag = %d\n", fix_coordinates);
-
-        /* If moving to a visible workspace, call show so it can be considered
-         * focused. Must do before attaching because workspace_show checks to see
-         * if focused container is in its area. */
-        if (!ignore_focus && workspace_is_visible(target_ws)) {
-            workspace_show(target_ws);
-
-            /* Don’t warp if told so (when dragging floating windows with the
-             * mouse for example) */
-            if (dont_warp)
-                x_set_warp_to(NULL);
-            else
-                x_set_warp_to(&(con->rect));
-        }
     }
 
     /* If moving a fullscreen container and the destination already has a
@@ -1217,20 +1230,21 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
         /* We need to save the focused workspace on the output in case the
          * new workspace is hidden and it's necessary to immediately switch
          * back to the originally-focused workspace. */
-        Con *old_focus = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head));
+        Con *old_focus_ws = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head));
+        Con *old_focus = focused;
         con_activate(con_descend_focused(con));
 
-        /* Restore focus if the output's focused workspace has changed. */
-        if (con_get_workspace(focused) != old_focus)
+        if (old_focus_ws == current_ws && old_focus->type != CT_WORKSPACE) {
+            /* Restore focus to the currently focused container. */
             con_activate(old_focus);
+        } else if (con_get_workspace(focused) != old_focus_ws) {
+            /* Restore focus if the output's focused workspace has changed. */
+            con_focus(con_descend_focused(old_focus_ws));
+        }
     }
 
     /* 7: when moving to another workspace, we leave the focus on the current
      * workspace. (see also #809) */
-
-    /* Descend focus stack in case focus_next is a workspace which can
-     * occur if we move to the same workspace.  Also show current workspace
-     * to ensure it is focused. */
     if (!ignore_focus) {
         workspace_show(current_ws);
         if (dont_warp) {
@@ -1241,7 +1255,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
 
     /* Set focus only if con was on current workspace before moving.
      * Otherwise we would give focus to some window on different workspace. */
-    if (!ignore_focus && source_ws == current_ws)
+    if (focus_next)
         con_activate(con_descend_focused(focus_next));
 
     /* 8. If anything within the container is associated with a startup sequence,
@@ -1310,7 +1324,7 @@ bool con_move_to_mark(Con *con, const char *mark) {
         return true;
     }
 
-    if (con->type == CT_WORKSPACE) {
+    if (target->type == CT_WORKSPACE) {
         DLOG("target container is a workspace, simply moving the container there.\n");
         con_move_to_workspace(con, target, true, false, false);
         return true;
@@ -1418,20 +1432,16 @@ orientation_t con_orientation(Con *con) {
             return HORIZ;
 
         case L_DEFAULT:
-            DLOG("Someone called con_orientation() on a con with L_DEFAULT, this is a bug in the code.\n");
+            ELOG("Someone called con_orientation() on a con with L_DEFAULT, this is a bug in the code.\n");
             assert(false);
-            return HORIZ;
 
         case L_DOCKAREA:
         case L_OUTPUT:
-            DLOG("con_orientation() called on dockarea/output (%d) container %p\n", con->layout, con);
-            assert(false);
-            return HORIZ;
-
-        default:
-            DLOG("con_orientation() ran into default\n");
+            ELOG("con_orientation() called on dockarea/output (%d) container %p\n", con->layout, con);
             assert(false);
     }
+    /* should not be reached */
+    assert(false);
 }
 
 /*
@@ -1441,52 +1451,20 @@ orientation_t con_orientation(Con *con) {
  *
  */
 Con *con_next_focused(Con *con) {
-    Con *next;
-    /* floating containers are attached to a workspace, so we focus either the
-     * next floating container (if any) or the workspace itself. */
-    if (con->type == CT_FLOATING_CON) {
-        DLOG("selecting next for CT_FLOATING_CON\n");
-        next = TAILQ_NEXT(con, floating_windows);
-        DLOG("next = %p\n", next);
-        if (!next) {
-            next = TAILQ_PREV(con, floating_head, floating_windows);
-            DLOG("using prev, next = %p\n", next);
-        }
-        if (!next) {
-            Con *ws = con_get_workspace(con);
-            next = ws;
-            DLOG("no more floating containers for next = %p, restoring workspace focus\n", next);
-            while (next != TAILQ_END(&(ws->focus_head)) && !TAILQ_EMPTY(&(next->focus_head))) {
-                next = TAILQ_FIRST(&(next->focus_head));
-                if (next == con) {
-                    DLOG("skipping container itself, we want the next client\n");
-                    next = TAILQ_NEXT(next, focused);
-                }
-            }
-            if (next == TAILQ_END(&(ws->focus_head))) {
-                DLOG("Focus list empty, returning ws\n");
-                next = ws;
-            }
-        } else {
-            /* Instead of returning the next CT_FLOATING_CON, we descend it to
-             * get an actual window to focus. */
-            next = con_descend_focused(next);
-        }
-        return next;
-    }
-
     /* dock clients cannot be focused, so we focus the workspace instead */
     if (con->parent->type == CT_DOCKAREA) {
         DLOG("selecting workspace for dock client\n");
         return con_descend_focused(output_get_content(con->parent->parent));
     }
+    if (con_is_floating(con)) {
+        con = con->parent;
+    }
 
     /* if 'con' is not the first entry in the focus stack, use the first one as
      * it’s currently focused already */
-    Con *first = TAILQ_FIRST(&(con->parent->focus_head));
-    if (first != con) {
-        DLOG("Using first entry %p\n", first);
-        next = first;
+    Con *next = TAILQ_FIRST(&(con->parent->focus_head));
+    if (next != con) {
+        DLOG("Using first entry %p\n", next);
     } else {
         /* try to focus the next container on the same level as this one or fall
          * back to its parent */
@@ -1501,6 +1479,10 @@ Con *con_next_focused(Con *con) {
         next = TAILQ_FIRST(&(next->focus_head));
     }
 
+    if (con->type == CT_FLOATING_CON && next != con->parent) {
+        next = con_descend_focused(next);
+    }
+
     return next;
 }
 
@@ -1734,8 +1716,7 @@ adjacent_t con_adjacent_borders(Con *con) {
  *
  */
 int con_border_style(Con *con) {
-    Con *fs = con_get_fullscreen_con(con->parent, CF_OUTPUT);
-    if (fs == con) {
+    if (con->fullscreen_mode == CF_OUTPUT || con->fullscreen_mode == CF_GLOBAL) {
         DLOG("this one is fullscreen! overriding BS_NONE\n");
         return BS_NONE;
     }
@@ -1935,7 +1916,6 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
              * now let's activate the current layout (next in list) */
             if (current_layout_found) {
                 new_layout = layout;
-                free(tm_dup);
                 break;
             }
 
@@ -1943,6 +1923,7 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
                 current_layout_found = true;
             }
         }
+        free(tm_dup);
 
         if (new_layout != L_DEFAULT) {
             con_set_layout(con, new_layout);
@@ -1995,7 +1976,7 @@ static void con_on_remove_child(Con *con) {
         if (TAILQ_EMPTY(&(con->focus_head)) && !workspace_is_visible(con)) {
             LOG("Closing old workspace (%p / %s), it is empty\n", con, con->name);
             yajl_gen gen = ipc_marshal_workspace_event("empty", con, NULL);
-            tree_close_internal(con, DONT_KILL_WINDOW, false, false);
+            tree_close_internal(con, DONT_KILL_WINDOW, false);
 
             const unsigned char *payload;
             ylength length;
@@ -2016,7 +1997,7 @@ static void con_on_remove_child(Con *con) {
     int children = con_num_children(con);
     if (children == 0) {
         DLOG("Container empty, closing\n");
-        tree_close_internal(con, DONT_KILL_WINDOW, false, false);
+        tree_close_internal(con, DONT_KILL_WINDOW, false);
         return;
     }
 }
@@ -2413,6 +2394,10 @@ bool con_swap(Con *first, Con *second) {
 
     /* Move first to second. */
     result &= _con_move_to_con(first, second, false, false, false, true, false);
+    /* If swapping the containers didn't work we don't need to mess with the focus. */
+    if (!result) {
+        goto swap_end;
+    }
 
     /* If we moved the container holding the focused window to another
      * workspace we need to ensure the visible workspace has the focused
@@ -2425,8 +2410,6 @@ bool con_swap(Con *first, Con *second) {
 
     /* Move second to where first has been originally. */
     result &= _con_move_to_con(second, fake, false, false, false, true, false);
-
-    /* If swapping the containers didn't work we don't need to mess with the focus. */
     if (!result) {
         goto swap_end;
     }
index 24c7b541e447e4f80ab2b676c4d3f0b140bdff6c..9631b2160237732ed404ad02757ab064b8d87b9b 100644 (file)
@@ -18,7 +18,7 @@ Config config;
 struct modes_head modes;
 struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);
 
-/**
+/*
  * Ungrabs all keys, to be called before re-grabbing the keys because of a
  * mapping_notify event or a configuration file reload
  *
@@ -32,7 +32,7 @@ void ungrab_all_keys(xcb_connection_t *conn) {
  * Sends the current bar configuration as an event to all barconfig_update listeners.
  *
  */
-void update_barconfig() {
+void update_barconfig(void) {
     Barconfig *current;
     TAILQ_FOREACH(current, &barconfigs, configs) {
         ipc_send_barconfig_update_event(current);
@@ -49,7 +49,8 @@ bool parse_configuration(const char *override_configpath, bool use_nagbar) {
     char *path = get_config_path(override_configpath, true);
     if (path == NULL) {
         die("Unable to find the configuration file (looked at "
-            "~/.i3/config, $XDG_CONFIG_HOME/i3/config, " SYSCONFDIR "/i3/config and $XDG_CONFIG_DIRS/i3/config)");
+            "$XDG_CONFIG_HOME/i3/config, ~/.i3/config, $XDG_CONFIG_DIRS/i3/config "
+            "and " SYSCONFDIR "/i3/config)");
     }
 
     LOG("Parsing configfile %s\n", path);
@@ -96,18 +97,27 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
             FREE(mode);
         }
 
-        struct Assignment *assign;
         while (!TAILQ_EMPTY(&assignments)) {
-            assign = TAILQ_FIRST(&assignments);
-            if (assign->type == A_TO_WORKSPACE)
+            struct Assignment *assign = TAILQ_FIRST(&assignments);
+            if (assign->type == A_TO_WORKSPACE || assign->type == A_TO_WORKSPACE_NUMBER)
                 FREE(assign->dest.workspace);
             else if (assign->type == A_COMMAND)
                 FREE(assign->dest.command);
+            else if (assign->type == A_TO_OUTPUT)
+                FREE(assign->dest.output);
             match_free(&(assign->match));
             TAILQ_REMOVE(&assignments, assign, assignments);
             FREE(assign);
         }
 
+        while (!TAILQ_EMPTY(&ws_assignments)) {
+            struct Workspace_Assignment *assign = TAILQ_FIRST(&ws_assignments);
+            FREE(assign->name);
+            FREE(assign->output);
+            TAILQ_REMOVE(&ws_assignments, assign, ws_assignments);
+            FREE(assign);
+        }
+
         /* Clear bar configs */
         Barconfig *barconfig;
         while (!TAILQ_EMPTY(&barconfigs)) {
@@ -160,10 +170,16 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
             FREE(barconfig);
         }
 
-        /* Invalidate pixmap caches in case font or colors changed */
         Con *con;
-        TAILQ_FOREACH(con, &all_cons, all_cons)
-        FREE(con->deco_render_params);
+        TAILQ_FOREACH(con, &all_cons, all_cons) {
+            /* Assignments changed, previously ran assignments are invalid. */
+            if (con->window) {
+                con->window->nr_assignments = 0;
+                FREE(con->window->ran_assignments);
+            }
+            /* Invalidate pixmap caches in case font or colors changed. */
+            FREE(con->deco_render_params);
+        }
 
         /* Get rid of the current font */
         free_font();
@@ -183,10 +199,6 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
 
     bindings = default_mode->bindings;
 
-#define REQUIRED_OPTION(name) \
-    if (config.name == NULL)  \
-        die("You did not specify required configuration option " #name "\n");
-
     /* Clear the old config or initialize the data structure */
     memset(&config, 0, sizeof(config));
 
index ad6d65b5fe368c11b52925ac22cf58ee582c973c..0b01d54a4acaf4ffa4a1fa0a6b3ff26ccf1109fc 100644 (file)
@@ -318,31 +318,46 @@ CFGFUN(focus_on_window_activation, const char *mode) {
     DLOG("Set new focus_on_window_activation mode = %i.\n", config.focus_on_window_activation);
 }
 
+CFGFUN(title_align, const char *alignment) {
+    if (strcmp(alignment, "left") == 0) {
+        config.title_align = ALIGN_LEFT;
+    } else if (strcmp(alignment, "center") == 0) {
+        config.title_align = ALIGN_CENTER;
+    } else if (strcmp(alignment, "right") == 0) {
+        config.title_align = ALIGN_RIGHT;
+    } else {
+        assert(false);
+    }
+}
+
 CFGFUN(show_marks, const char *value) {
     config.show_marks = eval_boolstr(value);
 }
 
-CFGFUN(workspace, const char *workspace, const char *output) {
-    DLOG("Assigning workspace \"%s\" to output \"%s\"\n", workspace, output);
+CFGFUN(workspace, const char *workspace, const char *outputs) {
+    DLOG("Assigning workspace \"%s\" to outputs \"%s\"\n", workspace, outputs);
     /* Check for earlier assignments of the same workspace so that we
      * don’t have assignments of a single workspace to different
      * outputs */
     struct Workspace_Assignment *assignment;
-    bool duplicate = false;
     TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
         if (strcasecmp(assignment->name, workspace) == 0) {
             ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
                  workspace);
-            assignment->output = sstrdup(output);
-            duplicate = true;
+            return;
         }
     }
-    if (!duplicate) {
+
+    char *buf = sstrdup(outputs);
+    char *output = strtok(buf, " ");
+    while (output != NULL) {
         assignment = scalloc(1, sizeof(struct Workspace_Assignment));
         assignment->name = sstrdup(workspace);
         assignment->output = sstrdup(output);
         TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
+        output = strtok(NULL, " ");
     }
+    free(buf);
 }
 
 CFGFUN(ipc_socket, const char *path) {
@@ -403,6 +418,11 @@ CFGFUN(assign_output, const char *output) {
         return;
     }
 
+    if (current_match->window_mode != WM_ANY) {
+        ELOG("Assignments using window mode (floating/tiling) is not supported\n");
+        return;
+    }
+
     DLOG("New assignment, using above criteria, to output \"%s\".\n", output);
     Assignment *assignment = scalloc(1, sizeof(Assignment));
     match_copy(&(assignment->match), current_match);
@@ -417,6 +437,11 @@ CFGFUN(assign, const char *workspace, bool is_number) {
         return;
     }
 
+    if (current_match->window_mode != WM_ANY) {
+        ELOG("Assignments using window mode (floating/tiling) is not supported\n");
+        return;
+    }
+
     if (is_number && ws_name_to_number(workspace) == -1) {
         ELOG("Could not parse initial part of \"%s\" as a number.\n", workspace);
         return;
@@ -443,6 +468,10 @@ CFGFUN(no_focus) {
     TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
 }
 
+CFGFUN(ipc_kill_timeout, const long timeout_ms) {
+    ipc_set_kill_timeout(timeout_ms / 1000.0);
+}
+
 /*******************************************************************************
  * Bar configuration (i3bar)
  ******************************************************************************/
@@ -482,25 +511,8 @@ CFGFUN(bar_verbose, const char *verbose) {
     current_bar->verbose = eval_boolstr(verbose);
 }
 
-CFGFUN(bar_modifier, const char *modifier) {
-    if (strcmp(modifier, "Mod1") == 0)
-        current_bar->modifier = M_MOD1;
-    else if (strcmp(modifier, "Mod2") == 0)
-        current_bar->modifier = M_MOD2;
-    else if (strcmp(modifier, "Mod3") == 0)
-        current_bar->modifier = M_MOD3;
-    else if (strcmp(modifier, "Mod4") == 0)
-        current_bar->modifier = M_MOD4;
-    else if (strcmp(modifier, "Mod5") == 0)
-        current_bar->modifier = M_MOD5;
-    else if (strcmp(modifier, "Control") == 0 ||
-             strcmp(modifier, "Ctrl") == 0)
-        current_bar->modifier = M_CONTROL;
-    else if (strcmp(modifier, "Shift") == 0)
-        current_bar->modifier = M_SHIFT;
-    else if (strcmp(modifier, "none") == 0 ||
-             strcmp(modifier, "off") == 0)
-        current_bar->modifier = M_NONE;
+CFGFUN(bar_modifier, const char *modifiers) {
+    current_bar->modifier = modifiers ? event_state_from_str(modifiers) : XCB_NONE;
 }
 
 static void bar_configure_binding(const char *button, const char *release, const char *command) {
@@ -627,12 +639,16 @@ CFGFUN(bar_strip_workspace_numbers, const char *value) {
     current_bar->strip_workspace_numbers = eval_boolstr(value);
 }
 
+CFGFUN(bar_strip_workspace_name, const char *value) {
+    current_bar->strip_workspace_name = eval_boolstr(value);
+}
+
 CFGFUN(bar_start) {
     current_bar = scalloc(1, sizeof(struct Barconfig));
     TAILQ_INIT(&(current_bar->bar_bindings));
     TAILQ_INIT(&(current_bar->tray_outputs));
     current_bar->tray_padding = 2;
-    current_bar->modifier = M_MOD4;
+    current_bar->modifier = XCB_KEY_BUT_MASK_MOD_4;
 }
 
 CFGFUN(bar_finish) {
index 2d3f3bb919eb2d134baa61f9e510880275fd8210..9f972fed8f80b4510931cac6edb52cef431c832a 100644 (file)
@@ -171,7 +171,7 @@ static long get_long(const char *identifier) {
 
 static void clear_stack(void) {
     for (int c = 0; c < 10; c++) {
-        if (stack[c].type == STACK_STR && stack[c].val.str != NULL)
+        if (stack[c].type == STACK_STR)
             free(stack[c].val.str);
         stack[c].identifier = NULL;
         stack[c].val.str = NULL;
index f8422bdadaf1f103f146ecde24a4a94cd279a30a..e5dcafcb012a6572e336e23594fae26301bd3a95 100644 (file)
@@ -284,6 +284,20 @@ void ewmh_update_sticky(xcb_window_t window, bool sticky) {
     }
 }
 
+/*
+ * Set or remove _NEW_WM_STATE_FOCUSED on the window.
+ *
+ */
+void ewmh_update_focused(xcb_window_t window, bool is_focused) {
+    if (is_focused) {
+        DLOG("Setting _NET_WM_STATE_FOCUSED for window = %d.\n", window);
+        xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
+    } else {
+        DLOG("Removing _NET_WM_STATE_FOCUSED for window = %d.\n", window);
+        xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
+    }
+}
+
 /*
  * Set up the EWMH hints on the root window.
  *
index e958153d6a647ab5242188d932f28e1b3ae19e98..a99d0970f356156abbd29e96a4167e7185ece558 100644 (file)
@@ -59,7 +59,7 @@ static void floating_set_hint_atom(Con *con, bool floating) {
     xcb_flush(conn);
 }
 
-/**
+/*
  * Called when a floating window is created or resized.
  * This function resizes the window if its size is higher or lower than the
  * configured maximum/minimum size, respectively.
@@ -96,6 +96,18 @@ void floating_check_size(Con *floating_con) {
             floating_con->rect.height += border_rect.height;
         }
 
+        if (focused_con->window->max_width) {
+            floating_con->rect.width -= border_rect.width;
+            floating_con->rect.width = min(floating_con->rect.width, focused_con->window->max_width);
+            floating_con->rect.width += border_rect.width;
+        }
+
+        if (focused_con->window->max_height) {
+            floating_con->rect.height -= border_rect.height;
+            floating_con->rect.height = min(floating_con->rect.height, focused_con->window->max_height);
+            floating_con->rect.height += border_rect.height;
+        }
+
         if (focused_con->window->height_increment &&
             floating_con->rect.height >= focused_con->window->base_height + border_rect.height) {
             floating_con->rect.height -= focused_con->window->base_height + border_rect.height;
@@ -176,11 +188,36 @@ void floating_enable(Con *con, bool automatic) {
         return;
     }
 
+    Con *focus_head_placeholder = NULL;
+    bool focus_before_parent = true;
+    if (!set_focus) {
+        /* Find recursively the ancestor container which is a child of our workspace.
+         * We need to reuse its focus position later. */
+        Con *ancestor = con;
+        while (ancestor->parent->type != CT_WORKSPACE) {
+            focus_before_parent &= TAILQ_FIRST(&(ancestor->parent->focus_head)) == ancestor;
+            ancestor = ancestor->parent;
+        }
+        /* Consider the part of the focus stack of our current workspace:
+         * [ ... S_{i-1} S_{i} S_{i+1} ... ]
+         * Where S_{x} is a container tree and the container 'con' that is beeing switched to
+         * floating belongs in S_{i}. The new floating container, 'nc', will have the
+         * workspace as its parent so it needs to be placed in this stack. If C was focused
+         * we just need to call con_focus(). Otherwise, nc must be placed before or after S_{i}.
+         * We should avoid using the S_{i} container for our operations since it might get
+         * killed if it has no other children. So, the two possible positions are after S_{i-1}
+         * or before S_{i+1}.
+         */
+        if (focus_before_parent) {
+            focus_head_placeholder = TAILQ_PREV(ancestor, focus_head, focused);
+        } else {
+            focus_head_placeholder = TAILQ_NEXT(ancestor, focused);
+        }
+    }
+
     /* 1: detach the container from its parent */
     /* TODO: refactor this with tree_close_internal() */
-    TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
-    TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
-
+    con_detach(con);
     con_fix_percent(con->parent);
 
     /* 2: create a new container to render the decoration on, add
@@ -196,12 +233,23 @@ void floating_enable(Con *con, bool automatic) {
     /* We insert nc already, even though its rect is not yet calculated. This
      * is necessary because otherwise the workspace might be empty (and get
      * closed in tree_close_internal()) even though it’s not. */
-    if (set_focus) {
-        TAILQ_INSERT_TAIL(&(ws->floating_head), nc, floating_windows);
+    TAILQ_INSERT_HEAD(&(ws->floating_head), nc, floating_windows);
+
+    struct focus_head *fh = &(ws->focus_head);
+    if (focus_before_parent) {
+        if (focus_head_placeholder) {
+            TAILQ_INSERT_AFTER(fh, focus_head_placeholder, nc, focused);
+        } else {
+            TAILQ_INSERT_HEAD(fh, nc, focused);
+        }
     } else {
-        TAILQ_INSERT_HEAD(&(ws->floating_head), nc, floating_windows);
+        if (focus_head_placeholder) {
+            TAILQ_INSERT_BEFORE(focus_head_placeholder, nc, focused);
+        } else {
+            /* Also used for the set_focus case */
+            TAILQ_INSERT_TAIL(fh, nc, focused);
+        }
     }
-    TAILQ_INSERT_TAIL(&(ws->focus_head), nc, focused);
 
     /* check if the parent container is empty and close it if so */
     if ((con->parent->type == CT_CON || con->parent->type == CT_FLOATING_CON) &&
@@ -210,7 +258,7 @@ void floating_enable(Con *con, bool automatic) {
         Con *parent = con->parent;
         /* clear the pointer before calling tree_close_internal in which the memory is freed */
         con->parent = NULL;
-        tree_close_internal(parent, DONT_KILL_WINDOW, false, false);
+        tree_close_internal(parent, DONT_KILL_WINDOW, false);
     }
 
     char *name;
@@ -284,10 +332,7 @@ void floating_enable(Con *con, bool automatic) {
 
     /* Sanity check: Are the coordinates on the appropriate output? If not, we
      * need to change them */
-    Output *current_output = get_output_containing(nc->rect.x +
-                                                       (nc->rect.width / 2),
-                                                   nc->rect.y + (nc->rect.height / 2));
-
+    Output *current_output = get_output_from_rect(nc->rect);
     Con *correct_output = con_get_output(ws);
     if (!current_output || current_output->con != correct_output) {
         DLOG("This floating window is on the wrong output, fixing coordinates (currently (%d, %d))\n",
@@ -295,11 +340,13 @@ void floating_enable(Con *con, bool automatic) {
 
         /* If moving from one output to another, keep the relative position
          * consistent (e.g. a centered dialog will remain centered). */
-        if (current_output)
+        if (current_output) {
             floating_fix_coordinates(nc, &current_output->con->rect, &correct_output->rect);
-        else {
-            nc->rect.x = correct_output->rect.x;
-            nc->rect.y = correct_output->rect.y;
+            /* Make sure that the result is in the correct output. */
+            current_output = get_output_from_rect(nc->rect);
+        }
+        if (!current_output || current_output->con != correct_output) {
+            floating_center(nc, ws->rect);
         }
     }
 
@@ -320,21 +367,6 @@ void floating_enable(Con *con, bool automatic) {
     if (set_focus)
         con_activate(con);
 
-    /* Check if we need to re-assign it to a different workspace because of its
-     * coordinates and exit if that was done successfully. */
-    if (floating_maybe_reassign_ws(nc)) {
-        goto done;
-    }
-
-    /* Sanitize coordinates: Check if they are on any output */
-    if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) {
-        goto done;
-    }
-
-    ELOG("No output found at destination coordinates, centering floating window on current ws\n");
-    floating_center(nc, ws->rect);
-
-done:
     floating_set_hint_atom(nc, true);
     ipc_send_window_event("floating", con);
 }
@@ -345,45 +377,26 @@ void floating_disable(Con *con, bool automatic) {
         return;
     }
 
-    const bool set_focus = (con == focused);
-
     Con *ws = con_get_workspace(con);
-    Con *parent = con->parent;
-
-    /* 1: detach from parent container */
-    TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
-    TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
-
-    /* 2: kill parent container */
-    TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows);
-    TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused);
-    /* clear the pointer before calling tree_close_internal in which the memory is freed */
-    con->parent = NULL;
-    tree_close_internal(parent, DONT_KILL_WINDOW, true, false);
-
-    /* 3: re-attach to the parent of the currently focused con on the workspace
-     * this floating con was on */
-    Con *focused = con_descend_tiling_focused(ws);
-
-    /* if there is no other container on this workspace, focused will be the
-     * workspace itself */
-    if (focused->type == CT_WORKSPACE)
-        con->parent = focused;
-    else
-        con->parent = focused->parent;
+    if (con_is_internal(ws)) {
+        LOG("Can't disable floating for container in internal workspace.\n");
+        return;
+    }
+    Con *tiling_focused = con_descend_tiling_focused(ws);
 
-    /* con_fix_percent will adjust the percent value */
-    con->percent = 0.0;
+    if (tiling_focused->type == CT_WORKSPACE) {
+        Con *parent = con->parent;
+        con_detach(con);
+        con->parent = NULL;
+        tree_close_internal(parent, DONT_KILL_WINDOW, true);
+        con_attach(con, tiling_focused, false);
+        con->percent = 0.0;
+        con_fix_percent(con->parent);
+    } else {
+        insert_con_into(con, tiling_focused, AFTER);
+    }
 
     con->floating = FLOATING_USER_OFF;
-
-    con_attach(con, con->parent, false);
-
-    con_fix_percent(con->parent);
-
-    if (set_focus)
-        con_activate(con);
-
     floating_set_hint_atom(con, false);
     ipc_send_window_event("floating", con);
 }
@@ -429,9 +442,7 @@ void floating_raise_con(Con *con) {
  *
  */
 bool floating_maybe_reassign_ws(Con *con) {
-    Output *output = get_output_containing(
-        con->rect.x + (con->rect.width / 2),
-        con->rect.y + (con->rect.height / 2));
+    Output *output = get_output_from_rect(con->rect);
 
     if (!output) {
         ELOG("No output found at destination coordinates?\n");
@@ -542,8 +553,10 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
     }
 
     /* If the user cancelled, undo the changes. */
-    if (drag_result == DRAG_REVERT)
+    if (drag_result == DRAG_REVERT) {
         floating_reposition(con, initial_rect);
+        return;
+    }
 
     /* If this is a scratchpad window, don't auto center it from now on. */
     if (con->scratchpad_state == SCRATCHPAD_FRESH)
@@ -687,8 +700,7 @@ struct drag_x11_cb {
     const void *extra;
 };
 
-static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
-    struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
+static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
     xcb_motion_notify_event_t *last_motion_notify = NULL;
     xcb_generic_event_t *event;
 
@@ -748,13 +760,20 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
             free(event);
 
         if (dragloop->result != DRAGGING) {
-            free(last_motion_notify);
-            return;
+            ev_break(EV_A_ EVBREAK_ONE);
+            if (dragloop->result == DRAG_SUCCESS) {
+                /* Ensure motion notify events are handled. */
+                break;
+            } else {
+                free(last_motion_notify);
+                return true;
+            }
         }
     }
 
-    if (last_motion_notify == NULL)
-        return;
+    if (last_motion_notify == NULL) {
+        return true;
+    }
 
     /* Ensure that we are either dragging the resize handle (con is NULL) or that the
      * container still exists. The latter might not be true, e.g., if the window closed
@@ -767,9 +786,17 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
             last_motion_notify->root_y,
             dragloop->extra);
     }
-    free(last_motion_notify);
+    FREE(last_motion_notify);
 
     xcb_flush(conn);
+    return dragloop->result != DRAGGING;
+}
+
+static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
+    struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
+    while (!drain_drag_events(EV_A, dragloop)) {
+        /* repeatedly drain events: draining might produce additional ones */
+    }
 }
 
 /*
@@ -780,8 +807,7 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
  * rect of the client, the event and the new coordinates (x, y).
  *
  */
-drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
-                                                                                confine_to,
+drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to,
                            border_t border, int cursor, callback_t callback, const void *extra) {
     xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE;
 
@@ -844,8 +870,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
     main_set_x11_cb(false);
     ev_prepare_start(main_loop, prepare);
 
-    while (loop.result == DRAGGING)
-        ev_run(main_loop, EVRUN_ONCE);
+    ev_loop(main_loop, 0);
 
     ev_prepare_stop(main_loop, prepare);
     main_set_x11_cb(true);
@@ -864,23 +889,28 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
  * outputs.
  *
  */
-void floating_reposition(Con *con, Rect newrect) {
+bool floating_reposition(Con *con, Rect newrect) {
     /* Sanity check: Are the new coordinates on any output? If not, we
      * ignore that request. */
-    if (!contained_by_output(newrect)) {
+    if (!output_containing_rect(newrect)) {
         ELOG("No output found at destination coordinates. Not repositioning.\n");
-        return;
+        return false;
     }
 
     con->rect = newrect;
 
-    floating_maybe_reassign_ws(con);
+    bool reassigned = floating_maybe_reassign_ws(con);
 
     /* If this is a scratchpad window, don't auto center it from now on. */
     if (con->scratchpad_state == SCRATCHPAD_FRESH)
         con->scratchpad_state = SCRATCHPAD_CHANGED;
 
-    tree_render();
+    /* Workspace change will already result in a tree_render. */
+    if (!reassigned) {
+        render_con(con, false);
+        x_push_node(con);
+    }
+    return true;
 }
 
 /*
index e1671c3b4bec2df918fdf70d89d38a29ee77c0d1..b96779178c35151fbefa2e6ac159d18f704ab27c 100644 (file)
@@ -184,8 +184,6 @@ static void handle_enter_notify(xcb_enter_notify_event_t *event) {
     focused_id = XCB_NONE;
     con_focus(con_descend_focused(con));
     tree_render();
-
-    return;
 }
 
 /*
@@ -249,8 +247,6 @@ static void handle_mapping_notify(xcb_mapping_notify_event_t *event) {
     ungrab_all_keys(conn);
     translate_keysyms();
     grab_all_keys(conn);
-
-    return;
 }
 
 /*
@@ -266,7 +262,6 @@ static void handle_map_request(xcb_map_request_event_t *event) {
     add_ignore_event(event->sequence, -1);
 
     manage_window(event->window, cookie, false);
-    return;
 }
 
 /*
@@ -315,16 +310,12 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
 
     DLOG("Configure request!\n");
 
-    Con *workspace = con_get_workspace(con),
-        *fullscreen = NULL;
-
-    /* There might not be a corresponding workspace for dock cons, therefore we
-     * have to be careful here. */
-    if (workspace) {
-        fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
-        if (!fullscreen)
-            fullscreen = con_get_fullscreen_con(workspace, CF_GLOBAL);
+    Con *workspace = con_get_workspace(con);
+    if (workspace && (strcmp(workspace->name, "__i3_scratch") == 0)) {
+        DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
+        goto out;
     }
+    Con *fullscreen = con_get_fullscreen_covering_ws(workspace);
 
     if (fullscreen != con && con_is_floating(con) && con_is_leaf(con)) {
         /* find the height for the decorations */
@@ -337,12 +328,6 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
             bsr.height -= deco_height;
         }
         Con *floatingcon = con->parent;
-
-        if (strcmp(con_get_workspace(floatingcon)->name, "__i3_scratch") == 0) {
-            DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
-            return;
-        }
-
         Rect newrect = floatingcon->rect;
 
         if (event->value_mask & XCB_CONFIG_WINDOW_X) {
@@ -400,15 +385,14 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
                 DLOG("Dock client will not be moved, we only support moving it to another output.\n");
             }
         }
-        fake_absolute_configure_notify(con);
-        return;
+        goto out;
     }
 
     if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) {
         DLOG("window 0x%08x wants to be stacked %d\n", event->window, event->stack_mode);
 
         /* Emacs and IntelliJ Idea “request focus” by stacking their window
-             * above all others. */
+         * above all others. */
         if (event->stack_mode != XCB_STACK_MODE_ABOVE) {
             DLOG("stack_mode != XCB_STACK_MODE_ABOVE, ignoring ConfigureRequest\n");
             goto out;
@@ -419,23 +403,17 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
             goto out;
         }
 
-        Con *ws = con_get_workspace(con);
-        if (ws == NULL) {
+        if (workspace == NULL) {
             DLOG("Window is not being managed, ignoring ConfigureRequest\n");
             goto out;
         }
 
-        if (strcmp(ws->name, "__i3_scratch") == 0) {
-            DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
-            goto out;
-        }
-
-        if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
+        if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(workspace))) {
             DLOG("Focusing con = %p\n", con);
-            workspace_show(ws);
+            workspace_show(workspace);
             con_activate(con);
             tree_render();
-        } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
+        } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(workspace))) {
             DLOG("Marking con = %p urgent\n", con);
             con_set_urgency(con, true);
             tree_render();
@@ -474,8 +452,6 @@ static void handle_screen_change(xcb_generic_event_t *e) {
     scratchpad_fix_resolution();
 
     ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
-
-    return;
 }
 
 /*
@@ -518,7 +494,7 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) {
     xcb_delete_property(conn, event->window, A__NET_WM_DESKTOP);
     xcb_delete_property(conn, event->window, A__NET_WM_STATE);
 
-    tree_close_internal(con, DONT_KILL_WINDOW, false, false);
+    tree_close_internal(con, DONT_KILL_WINDOW, false);
     tree_render();
 
 ignore_end:
@@ -659,7 +635,6 @@ static void handle_expose_event(xcb_expose_event_t *event) {
     draw_util_copy_surface(&(parent->frame_buffer), &(parent->frame),
                            0, 0, 0, 0, parent->rect.width, parent->rect.height);
     xcb_flush(conn);
-    return;
 }
 
 #define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0
@@ -800,21 +775,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
     } else if (event->type == A_I3_SYNC) {
         xcb_window_t window = event->data.data32[0];
         uint32_t rnd = event->data.data32[1];
-        DLOG("[i3 sync protocol] Sending random value %d back to X11 window 0x%08x\n", rnd, window);
-
-        void *reply = scalloc(32, 1);
-        xcb_client_message_event_t *ev = reply;
-
-        ev->response_type = XCB_CLIENT_MESSAGE;
-        ev->window = window;
-        ev->type = A_I3_SYNC;
-        ev->format = 32;
-        ev->data.data32[0] = window;
-        ev->data.data32[1] = rnd;
-
-        xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)ev);
-        xcb_flush(conn);
-        free(reply);
+        sync_respond(window, rnd);
     } else if (event->type == A__NET_REQUEST_FRAME_EXTENTS) {
         /*
          * A client can request an estimate for the frame size which the window
@@ -834,7 +795,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
         Rect r = {
             config.default_border_width, /* left */
             config.default_border_width, /* right */
-            config.font.height + 5,      /* top */
+            render_deco_height(),        /* top */
             config.default_border_width  /* bottom */
         };
         xcb_change_property(
@@ -845,6 +806,18 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             XCB_ATOM_CARDINAL, 32, 4,
             &r);
         xcb_flush(conn);
+    } else if (event->type == A_WM_CHANGE_STATE) {
+        /* http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4 */
+        if (event->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) {
+            /* For compatiblity reasons, Wine will request iconic state and cannot ensure that the WM has agreed on it;
+             * immediately revert to normal to avoid being stuck in a paused state. */
+            DLOG("Client has requested iconic state, rejecting. (window = %d)\n", event->window);
+            long data[] = {XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE};
+            xcb_change_property(conn, XCB_PROP_MODE_REPLACE, event->window,
+                                A_WM_STATE, A_WM_STATE, 32, 2, data);
+        } else {
+            DLOG("Not handling WM_CHANGE_STATE request. (window = %d, state = %d)\n", event->window, event->data.data32[0]);
+        }
     } else if (event->type == A__NET_CURRENT_DESKTOP) {
         /* This request is used by pagers and bars to change the current
          * desktop likely as a result of some user action. We interpret this as
@@ -906,7 +879,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             if (event->data.data32[0])
                 last_timestamp = event->data.data32[0];
 
-            tree_close_internal(con, KILL_WINDOW, false, false);
+            tree_close_internal(con, KILL_WINDOW, false);
             tree_render();
         } else {
             DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window);
@@ -935,7 +908,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             case _NET_WM_MOVERESIZE_MOVE:
                 floating_drag_window(con->parent, &fake);
                 break;
-            case _NET_WM_MOVERESIZE_SIZE_TOPLEFT... _NET_WM_MOVERESIZE_SIZE_LEFT:
+            case _NET_WM_MOVERESIZE_SIZE_TOPLEFT ... _NET_WM_MOVERESIZE_SIZE_LEFT:
                 floating_resize_window(con->parent, false, &fake);
                 break;
             default:
@@ -976,8 +949,8 @@ static void handle_client_message(xcb_client_message_event_t *event) {
     }
 }
 
-bool handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
-                        xcb_atom_t atom, xcb_get_property_reply_t *reply) {
+static bool handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
+                               xcb_atom_t atom, xcb_get_property_reply_t *reply) {
     Con *con;
     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
         return false;
@@ -1020,9 +993,18 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
         con->window->min_height = size_hints.min_height;
     }
 
+    if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) {
+        DLOG("Maximum size: %d (width) x %d (height)\n", size_hints.max_width, size_hints.max_height);
+
+        con->window->max_width = size_hints.max_width;
+        con->window->max_height = size_hints.max_height;
+    }
+
     if (con_is_floating(con)) {
         win_width = MAX(win_width, con->window->min_width);
         win_height = MAX(win_height, con->window->min_height);
+        win_width = MIN(win_width, con->window->max_width);
+        win_height = MIN(win_height, con->window->max_height);
     }
 
     bool changed = false;
@@ -1090,7 +1072,7 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
 
     /* Convert numerator/denominator to a double */
     double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
-    double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den;
+    double max_aspect = (double)size_hints.max_aspect_num / size_hints.max_aspect_den;
 
     DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
     DLOG("width = %f, height = %f\n", width, height);
@@ -1163,8 +1145,7 @@ static bool handle_transient_for(void *data, xcb_connection_t *conn, uint8_t sta
     }
 
     if (prop == NULL) {
-        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
-                                                                       false, window, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 0, 32),
+        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 0, 32),
                                       NULL);
         if (prop == NULL)
             return false;
@@ -1187,8 +1168,7 @@ static bool handle_clientleader_change(void *data, xcb_connection_t *conn, uint8
         return false;
 
     if (prop == NULL) {
-        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
-                                                                       false, window, A_WM_CLIENT_LEADER, XCB_ATOM_WINDOW, 0, 32),
+        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, A_WM_CLIENT_LEADER, XCB_ATOM_WINDOW, 0, 32),
                                       NULL);
         if (prop == NULL)
             return false;
@@ -1207,6 +1187,14 @@ static bool handle_clientleader_change(void *data, xcb_connection_t *conn, uint8
  */
 static void handle_focus_in(xcb_focus_in_event_t *event) {
     DLOG("focus change in, for window 0x%08x\n", event->event);
+
+    if (event->event == root) {
+        DLOG("Received focus in for root window, refocusing the focused window.\n");
+        con_focus(focused);
+        focused_id = XCB_NONE;
+        x_push_changes(croot);
+    }
+
     Con *con;
     if ((con = con_by_window_id(event->event)) == NULL || con->window == NULL)
         return;
@@ -1249,7 +1237,6 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
     /* We update focused_id because we don’t need to set focus again */
     focused_id = event->event;
     tree_render();
-    return;
 }
 
 /*
@@ -1281,8 +1268,7 @@ static bool handle_class_change(void *data, xcb_connection_t *conn, uint8_t stat
         return false;
 
     if (prop == NULL) {
-        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
-                                                                       false, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 32),
+        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 32),
                                       NULL);
 
         if (prop == NULL)
@@ -1305,8 +1291,7 @@ static bool handle_motif_hints_change(void *data, xcb_connection_t *conn, uint8_
         return false;
 
     if (prop == NULL) {
-        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
-                                                                       false, window, A__MOTIF_WM_HINTS, XCB_GET_PROPERTY_TYPE_ANY, 0, 5 * sizeof(uint64_t)),
+        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, A__MOTIF_WM_HINTS, XCB_GET_PROPERTY_TYPE_ANY, 0, 5 * sizeof(uint64_t)),
                                       NULL);
 
         if (prop == NULL)
@@ -1456,7 +1441,7 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom)
     struct property_handler_t *handler = NULL;
     xcb_get_property_reply_t *propr = NULL;
 
-    for (size_t c = 0; c < sizeof(property_handlers) / sizeof(struct property_handler_t); c++) {
+    for (size_t c = 0; c < NUM_HANDLERS; c++) {
         if (property_handlers[c].atom != atom)
             continue;
 
index a1a72b1ac8ba620136fd9f8d3cced898465d69f0..d0fb965c3d8e55dafbd5fdbd8c97f63e1794d913 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -38,6 +38,108 @@ static void set_nonblock(int sockfd) {
         err(-1, "Could not set O_NONBLOCK");
 }
 
+/*
+ * Given a message and a message type, create the corresponding header, merge it
+ * with the message and append it to the given client's output buffer.
+ *
+ */
+static void append_payload(ipc_client *client, uint32_t message_type, const char *payload) {
+    const size_t size = strlen(payload);
+    const i3_ipc_header_t header = {
+        .magic = {'i', '3', '-', 'i', 'p', 'c'},
+        .size = size,
+        .type = message_type};
+    const size_t header_size = sizeof(i3_ipc_header_t);
+    const size_t message_size = header_size + size;
+
+    client->buffer = srealloc(client->buffer, client->buffer_size + message_size);
+    memcpy(client->buffer + client->buffer_size, ((void *)&header), header_size);
+    memcpy(client->buffer + client->buffer_size + header_size, payload, size);
+    client->buffer_size += message_size;
+}
+
+static void free_ipc_client(ipc_client *client) {
+    close(client->fd);
+
+    ev_io_stop(main_loop, client->callback);
+    FREE(client->callback);
+    if (client->timeout) {
+        ev_timer_stop(main_loop, client->timeout);
+        FREE(client->timeout);
+    }
+
+    free(client->buffer);
+
+    for (int i = 0; i < client->num_events; i++) {
+        free(client->events[i]);
+    }
+    free(client->events);
+    TAILQ_REMOVE(&all_clients, client, clients);
+    free(client);
+}
+
+static void ipc_client_timeout(EV_P_ ev_timer *w, int revents);
+static void ipc_socket_writeable_cb(EV_P_ struct ev_io *w, int revents);
+
+static ev_tstamp kill_timeout = 10.0;
+
+void ipc_set_kill_timeout(ev_tstamp new) {
+    kill_timeout = new;
+}
+
+/*
+ * Try to write the contents of the pending buffer to the client's subscription
+ * socket. Will set, reset or clear the timeout and io callbacks depending on
+ * the result of the write operation.
+ *
+ */
+static void ipc_push_pending(ipc_client *client) {
+    const ssize_t result = writeall_nonblock(client->fd, client->buffer, client->buffer_size);
+    if (result < 0) {
+        return;
+    }
+
+    if ((size_t)result == client->buffer_size) {
+        /* Everything was written successfully: clear the timer and stop the io
+         * callback. */
+        FREE(client->buffer);
+        client->buffer_size = 0;
+        if (client->timeout) {
+            ev_timer_stop(main_loop, client->timeout);
+            FREE(client->timeout);
+        }
+        ev_io_stop(main_loop, client->callback);
+        return;
+    }
+
+    /* Otherwise, make sure that the io callback is enabled and create a new
+     * timer if needed. */
+    ev_io_start(main_loop, client->callback);
+
+    if (!client->timeout) {
+        struct ev_timer *timeout = scalloc(1, sizeof(struct ev_timer));
+        ev_timer_init(timeout, ipc_client_timeout, kill_timeout, 0.);
+        timeout->data = client;
+        client->timeout = timeout;
+        ev_set_priority(timeout, EV_MINPRI);
+        ev_timer_start(main_loop, client->timeout);
+    } else if (result > 0) {
+        /* Keep the old timeout when nothing is written. Otherwise, we would
+         * keep a dead connection by continuously renewing its timeouts. */
+        ev_timer_stop(main_loop, client->timeout);
+        ev_timer_set(client->timeout, kill_timeout, 0.0);
+        ev_timer_start(main_loop, client->timeout);
+    }
+    if (result == 0) {
+        return;
+    }
+
+    /* Shift the buffer to the left and reduce the allocated space. */
+    client->buffer_size -= (size_t)result;
+    memmove(client->buffer, client->buffer + result, client->buffer_size);
+    client->buffer = srealloc(client->buffer, client->buffer_size);
+}
+
 /*
  * Sends the specified event to all IPC clients which are currently connected
  * and subscribed to this kind of event.
@@ -57,7 +159,11 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa
         if (!interested)
             continue;
 
-        ipc_send_message(current->fd, strlen(payload), message_type, (const uint8_t *)payload);
+        const bool push_now = (current->buffer_size == 0);
+        append_payload(current, message_type, payload);
+        if (push_now) {
+            ipc_push_pending(current);
+        }
     }
 }
 
@@ -99,12 +205,7 @@ void ipc_shutdown(shutdown_reason_t reason) {
     while (!TAILQ_EMPTY(&all_clients)) {
         current = TAILQ_FIRST(&all_clients);
         shutdown(current->fd, SHUT_RDWR);
-        close(current->fd);
-        for (int i = 0; i < current->num_events; i++)
-            free(current->events[i]);
-        free(current->events);
-        TAILQ_REMOVE(&all_clients, current, clients);
-        free(current);
+        free_ipc_client(current);
     }
 }
 
@@ -268,10 +369,6 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
         case CT_DOCKAREA:
             ystr("dockarea");
             break;
-        default:
-            DLOG("About to dump unknown container type=%d. This is a bug.\n", con->type);
-            assert(false);
-            break;
     }
 
     /* provided for backwards compatibility only. */
@@ -660,32 +757,7 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
     }
 
     ystr("modifier");
-    switch (config->modifier) {
-        case M_NONE:
-            ystr("none");
-            break;
-        case M_CONTROL:
-            ystr("ctrl");
-            break;
-        case M_SHIFT:
-            ystr("shift");
-            break;
-        case M_MOD1:
-            ystr("Mod1");
-            break;
-        case M_MOD2:
-            ystr("Mod2");
-            break;
-        case M_MOD3:
-            ystr("Mod3");
-            break;
-        case M_MOD5:
-            ystr("Mod5");
-            break;
-        default:
-            ystr("Mod4");
-            break;
-    }
+    y(integer, config->modifier);
 
     dump_bar_bindings(gen, config);
 
@@ -709,6 +781,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
     ystr("strip_workspace_numbers");
     y(bool, config->strip_workspace_numbers);
 
+    ystr("strip_workspace_name");
+    y(bool, config->strip_workspace_name);
+
     ystr("binding_mode_indicator");
     y(bool, !config->hide_binding_mode_indicator);
 
@@ -1153,6 +1228,9 @@ IPC_HANDLER(send_tick) {
 
     y(map_open);
 
+    ystr("first");
+    y(bool, false);
+
     ystr("payload");
     yajl_gen_string(gen, (unsigned char *)message, message_size);
 
@@ -1170,9 +1248,68 @@ IPC_HANDLER(send_tick) {
     DLOG("Sent tick event\n");
 }
 
+struct sync_state {
+    char *last_key;
+    uint32_t rnd;
+    xcb_window_t window;
+};
+
+static int _sync_json_key(void *extra, const unsigned char *val, size_t len) {
+    struct sync_state *state = extra;
+    FREE(state->last_key);
+    state->last_key = scalloc(len + 1, 1);
+    memcpy(state->last_key, val, len);
+    return 1;
+}
+
+static int _sync_json_int(void *extra, long long val) {
+    struct sync_state *state = extra;
+    if (strcasecmp(state->last_key, "rnd") == 0) {
+        state->rnd = val;
+    } else if (strcasecmp(state->last_key, "window") == 0) {
+        state->window = (xcb_window_t)val;
+    }
+    return 1;
+}
+
+IPC_HANDLER(sync) {
+    yajl_handle p;
+    yajl_status stat;
+
+    /* Setup the JSON parser */
+    static yajl_callbacks callbacks = {
+        .yajl_map_key = _sync_json_key,
+        .yajl_integer = _sync_json_int,
+    };
+
+    struct sync_state state;
+    memset(&state, '\0', sizeof(struct sync_state));
+    p = yalloc(&callbacks, (void *)&state);
+    stat = yajl_parse(p, (const unsigned char *)message, message_size);
+    FREE(state.last_key);
+    if (stat != yajl_status_ok) {
+        unsigned char *err;
+        err = yajl_get_error(p, true, (const unsigned char *)message,
+                             message_size);
+        ELOG("YAJL parse error: %s\n", err);
+        yajl_free_error(p, err);
+
+        const char *reply = "{\"success\":false}";
+        ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SYNC, (const uint8_t *)reply);
+        yajl_free(p);
+        return;
+    }
+    yajl_free(p);
+
+    DLOG("received IPC sync request (rnd = %d, window = 0x%08x)\n", state.rnd, state.window);
+    sync_respond(state.window, state.rnd);
+    const char *reply = "{\"success\":true}";
+    ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SYNC, (const uint8_t *)reply);
+}
+
 /* The index of each callback function corresponds to the numeric
  * value of the message type (see include/i3/ipc.h) */
-handler_t handlers[11] = {
+handler_t handlers[12] = {
     handle_run_command,
     handle_get_workspaces,
     handle_subscribe,
@@ -1184,6 +1321,7 @@ handler_t handlers[11] = {
     handle_get_binding_modes,
     handle_get_config,
     handle_send_tick,
+    handle_sync,
 };
 
 /*
@@ -1210,25 +1348,21 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
             return;
         }
 
-        /* If not, there was some kind of error. We don’t bother
-         * and close the connection */
-        close(w->fd);
-
-        /* Delete the client from the list of clients */
+        /* If not, there was some kind of error. We don’t bother and close the
+         * connection. Delete the client from the list of clients. */
+        bool closed = false;
         ipc_client *current;
         TAILQ_FOREACH(current, &all_clients, clients) {
             if (current->fd != w->fd)
                 continue;
 
-            for (int i = 0; i < current->num_events; i++)
-                free(current->events[i]);
-            free(current->events);
-            /* We can call TAILQ_REMOVE because we break out of the
-             * TAILQ_FOREACH afterwards */
-            TAILQ_REMOVE(&all_clients, current, clients);
-            free(current);
+            free_ipc_client(current);
+            closed = true;
             break;
         }
+        if (!closed) {
+            close(w->fd);
+        }
 
         ev_io_stop(EV_A_ w);
         free(w);
@@ -1248,6 +1382,62 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
     FREE(message);
 }
 
+static void ipc_client_timeout(EV_P_ ev_timer *w, int revents) {
+    /* No need to be polite and check for writeability, the other callback would
+     * have been called by now. */
+    ipc_client *client = (ipc_client *)w->data;
+
+    char *cmdline = NULL;
+#if defined(__linux__) && defined(SO_PEERCRED)
+    struct ucred peercred;
+    socklen_t so_len = sizeof(peercred);
+    if (getsockopt(client->fd, SOL_SOCKET, SO_PEERCRED, &peercred, &so_len) != 0) {
+        goto end;
+    }
+    char *exepath;
+    sasprintf(&exepath, "/proc/%d/cmdline", peercred.pid);
+
+    int fd = open(exepath, O_RDONLY);
+    free(exepath);
+    if (fd == -1) {
+        goto end;
+    }
+    char buf[512] = {'\0'}; /* cut off cmdline for the error message. */
+    const ssize_t n = read(fd, buf, sizeof(buf));
+    close(fd);
+    if (n < 0) {
+        goto end;
+    }
+    for (char *walk = buf; walk < buf + n - 1; walk++) {
+        if (*walk == '\0') {
+            *walk = ' ';
+        }
+    }
+    cmdline = buf;
+
+    if (cmdline) {
+        ELOG("client %p with pid %d and cmdline '%s' on fd %d timed out, killing\n", client, peercred.pid, cmdline, client->fd);
+    }
+
+end:
+#endif
+    if (!cmdline) {
+        ELOG("client %p on fd %d timed out, killing\n", client, client->fd);
+    }
+
+    free_ipc_client(client);
+}
+
+static void ipc_socket_writeable_cb(EV_P_ ev_io *w, int revents) {
+    DLOG("fd %d writeable\n", w->fd);
+    ipc_client *client = (ipc_client *)w->data;
+
+    /* If this callback is called then there should be a corresponding active
+     * timer. */
+    assert(client->timeout != NULL);
+    ipc_push_pending(client);
+}
+
 /*
  * Handler for activity on the listening socket, meaning that a new client
  * has just connected and we should accept() him. Sets up the event handler
@@ -1276,10 +1466,16 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
     ev_io_init(package, ipc_receive_message, client, EV_READ);
     ev_io_start(EV_A_ package);
 
+    ipc_client *new = scalloc(1, sizeof(ipc_client));
+
+    package = scalloc(1, sizeof(struct ev_io));
+    package->data = new;
+    ev_io_init(package, ipc_socket_writeable_cb, client, EV_WRITE);
+
     DLOG("IPC: new client connected on fd %d\n", w->fd);
 
-    ipc_client *new = scalloc(1, sizeof(ipc_client));
     new->fd = client;
+    new->callback = package;
 
     TAILQ_INSERT_TAIL(&all_clients, new, clients);
 }
@@ -1384,7 +1580,7 @@ void ipc_send_workspace_event(const char *change, Con *current, Con *old) {
     y(free);
 }
 
-/**
+/*
  * For the window events we send, along the usual "change" field,
  * also the window container, in "container".
  */
@@ -1414,7 +1610,7 @@ void ipc_send_window_event(const char *property, Con *con) {
     setlocale(LC_NUMERIC, "");
 }
 
-/**
+/*
  * For the barconfig update events, we send the serialized barconfig.
  */
 void ipc_send_barconfig_update_event(Barconfig *barconfig) {
index aa7ac03c452f20c66505215602be441cc61ae3bd..5a340d2c6c28bfc1befb96829a9276172dcd3b74 100644 (file)
@@ -108,18 +108,11 @@ static int json_end_map(void *ctx) {
             /* Prevent name clashes when appending a workspace, e.g. when the
              * user tries to restore a workspace called “1” but already has a
              * workspace called “1”. */
-            Con *output;
-            Con *workspace = NULL;
-            TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
-            GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name));
             char *base = sstrdup(json_node->name);
             int cnt = 1;
-            while (workspace != NULL) {
+            while (get_existing_workspace_by_name(json_node->name) != NULL) {
                 FREE(json_node->name);
                 sasprintf(&(json_node->name), "%s_%d", base, cnt++);
-                workspace = NULL;
-                TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
-                GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name));
             }
             free(base);
 
@@ -160,8 +153,7 @@ static int json_end_map(void *ctx) {
                 free(marks[i]);
             }
 
-            free(marks);
-            marks = NULL;
+            FREE(marks);
             num_marks = 0;
         }
 
@@ -618,6 +610,17 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
     yajl_config(hand, yajl_allow_comments, true);
     /* Allow multiple values, i.e. multiple nodes to attach */
     yajl_config(hand, yajl_allow_multiple_values, true);
+    /* We don't need to validate that the input is valid UTF8 here.
+     * tree_append_json is called in two cases:
+     * 1. With the append_layout command. json_validate is called first and will
+     *    fail on invalid UTF8 characters so we don't need to recheck.
+     * 2. With an in-place restart. The rest of the codebase should be
+     *    responsible for producing valid UTF8 JSON output. If not,
+     *    tree_append_json will just preserve invalid UTF8 strings in the tree
+     *    instead of failing to parse the layout file which could lead to
+     *    problems like in #3156.
+     * Either way, disabling UTF8 validation slightly speeds up yajl. */
+    yajl_config(hand, yajl_dont_validate_strings, true);
     json_node = con;
     to_focus = NULL;
     incomplete = 0;
@@ -639,6 +642,9 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
         while (incomplete-- > 0) {
             Con *parent = json_node->parent;
             DLOG("freeing incomplete container %p\n", json_node);
+            if (json_node == to_focus) {
+                to_focus = NULL;
+            }
             con_free(json_node);
             json_node = parent;
         }
index 194ef05c337efc76898d780eee8148f5fc9c9ca0..7eb47c822fa83550b0d61df0f8d127a27cac1e8d 100644 (file)
  * RLIM_INFINITY for i3 debugging versions. */
 struct rlimit original_rlimit_core;
 
-/** The number of file descriptors passed via socket activation. */
+/* The number of file descriptors passed via socket activation. */
 int listen_fds;
 
 /* We keep the xcb_prepare watcher around to be able to enable and disable it
  * temporarily for drag_pointer(). */
 static struct ev_prepare *xcb_prepare;
 
-extern Con *focused;
-
 char **start_argv;
 
 xcb_connection_t *conn;
@@ -643,8 +641,16 @@ int main(int argc, char *argv[]) {
         /* Setting both, XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE and
          * XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED, will lead to the
          * X server sending us the full XKB state in KeyPress and KeyRelease:
-         * https://sources.debian.net/src/xorg-server/2:1.17.2-1.1/xkb/xkbEvents.c/?hl=927#L927
+         * https://cgit.freedesktop.org/xorg/xserver/tree/xkb/xkbEvents.c?h=xorg-server-1.20.0#n927
+         *
+         * XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT enable detectable autorepeat:
+         * https://www.x.org/releases/current/doc/kbproto/xkbproto.html#Detectable_Autorepeat
+         * This affects bindings using the --release flag: instead of getting multiple KeyRelease
+         * events we get only one event when the key is physically released by the user.
          */
+        const uint32_t mask = XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE |
+                              XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED |
+                              XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT;
         xcb_xkb_per_client_flags_reply_t *pcf_reply;
         /* The last three parameters are unset because they are only relevant
          * when using a feature called “automatic reset of boolean controls”:
@@ -655,20 +661,24 @@ int main(int argc, char *argv[]) {
             xcb_xkb_per_client_flags(
                 conn,
                 XCB_XKB_ID_USE_CORE_KBD,
-                XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED,
-                XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED,
+                mask,
+                mask,
                 0 /* uint32_t ctrlsToChange */,
                 0 /* uint32_t autoCtrls */,
                 0 /* uint32_t autoCtrlsValues */),
             NULL);
-        if (pcf_reply == NULL ||
-            !(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE)) {
-            ELOG("Could not set XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE\n");
-        }
-        if (pcf_reply == NULL ||
-            !(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED)) {
-            ELOG("Could not set XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED\n");
-        }
+
+#define PCF_REPLY_ERROR(_value)                                    \
+    do {                                                           \
+        if (pcf_reply == NULL || !(pcf_reply->value & (_value))) { \
+            ELOG("Could not set " #_value "\n");                   \
+        }                                                          \
+    } while (0)
+
+        PCF_REPLY_ERROR(XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE);
+        PCF_REPLY_ERROR(XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED);
+        PCF_REPLY_ERROR(XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT);
+
         free(pcf_reply);
         xkb_base = extreply->first_event;
     }
@@ -949,8 +959,9 @@ int main(int argc, char *argv[]) {
     Barconfig *barconfig;
     TAILQ_FOREACH(barconfig, &barconfigs, configs) {
         char *command = NULL;
-        sasprintf(&command, "%s --bar_id=%s --socket=\"%s\"",
+        sasprintf(&command, "%s %s --bar_id=%s --socket=\"%s\"",
                   barconfig->i3bar_command ? barconfig->i3bar_command : "i3bar",
+                  barconfig->verbose ? "-V" : "",
                   barconfig->id, current_socketpath);
         LOG("Starting bar process: %s\n", command);
         start_application(command, true);
index 8b306052c7ea77243e457ccf97bccbd25ac0f23c..c4706b0dd20892b4c2493f9344f34ce753e31dac 100644 (file)
@@ -246,17 +246,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
     DLOG("Initial geometry: (%d, %d, %d, %d)\n", geom->x, geom->y, geom->width, geom->height);
 
-    Con *nc = NULL;
-    Match *match = NULL;
-    Assignment *assignment;
-
-    /* TODO: two matches for one container */
-
     /* See if any container swallows this new window */
-    nc = con_for_window(search_at, cwindow, &match);
+    Match *match = NULL;
+    Con *nc = con_for_window(search_at, cwindow, &match);
     const bool match_from_restart_mode = (match && match->restart_mode);
     if (nc == NULL) {
         Con *wm_desktop_ws = NULL;
+        Assignment *assignment;
 
         /* If not, check if it is assigned to a specific workspace */
         if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE)) ||
@@ -265,13 +261,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
             Con *assigned_ws = NULL;
             if (assignment->type == A_TO_WORKSPACE_NUMBER) {
-                Con *output = NULL;
                 long parsed_num = ws_name_to_number(assignment->dest.workspace);
 
-                /* This will only work for workspaces that already exist. */
-                TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
-                    GREP_FIRST(assigned_ws, output_get_content(output), child->num == parsed_num);
-                }
+                assigned_ws = get_existing_workspace_by_num(parsed_num);
             }
             /* A_TO_WORKSPACE type assignment or fallback from A_TO_WORKSPACE_NUMBER
              * when the target workspace number does not exist yet. */
@@ -359,8 +351,16 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
             }
         }
     }
+    xcb_window_t old_frame = XCB_NONE;
     if (nc->window != cwindow && nc->window != NULL) {
         window_free(nc->window);
+        /* Match frame and window depth. This is needed because X will refuse to reparent a
+         * window whose background is ParentRelative under a window with a different depth. */
+        if (nc->depth != cwindow->depth) {
+            old_frame = nc->frame.id;
+            nc->depth = cwindow->depth;
+            x_con_reframe(nc);
+        }
     }
     nc->window = cwindow;
     x_reinit(nc);
@@ -374,9 +374,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
     /* handle fullscreen containers */
     Con *ws = con_get_workspace(nc);
-    Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
-    if (fs == NULL)
-        fs = con_get_fullscreen_con(croot, CF_GLOBAL);
+    Con *fs = con_get_fullscreen_covering_ws(ws);
 
     if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_FULLSCREEN)) {
         /* If this window is already fullscreen (after restarting!), skip
@@ -518,6 +516,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
         nc->window->min_height = wm_size_hints.min_height;
     }
 
+    if (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) {
+        DLOG("Window specifies maximum size %d x %d\n", wm_size_hints.max_width, wm_size_hints.max_height);
+        nc->window->max_width = wm_size_hints.max_width;
+        nc->window->max_height = wm_size_hints.max_height;
+    }
+
     /* Store the requested geometry. The width/height gets raised to at least
      * 75x50 when entering floating mode, which is the minimum size for a
      * window to be useful (smaller windows are usually overlays/toolbars/…
@@ -651,6 +655,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
     tree_render();
 
+    /* Destroy the old frame if we had to reframe the container. This needs to be done
+     * after rendering in order to prevent the background from flickering in its place. */
+    if (old_frame != XCB_NONE) {
+        xcb_destroy_window(conn, old_frame);
+    }
+
     /* Windows might get managed with the urgency hint already set (Pidgin is
      * known to do that), so check for that and handle the hint accordingly.
      * This code needs to be in this part of manage_window() because the window
@@ -668,5 +678,4 @@ geom_out:
     free(geom);
 out:
     free(attr);
-    return;
 }
index b3136ab9169bc269d028ec44032310560dcb98b9..83e3732759b02ba5889aee8c10dfc2dc443bd53b 100644 (file)
@@ -87,31 +87,30 @@ void match_copy(Match *dest, Match *src) {
 bool match_matches_window(Match *match, i3Window *window) {
     LOG("Checking window 0x%08x (class %s)\n", window->id, window->class_class);
 
-    if (match->class != NULL) {
-        if (window->class_class == NULL)
-            return false;
-        if (strcmp(match->class->pattern, "__focused__") == 0 &&
-            strcmp(window->class_class, focused->window->class_class) == 0) {
-            LOG("window class matches focused window\n");
-        } else if (regex_matches(match->class, window->class_class)) {
-            LOG("window class matches (%s)\n", window->class_class);
-        } else {
-            return false;
-        }
-    }
+#define GET_FIELD_str(field) (field)
+#define GET_FIELD_i3string(field) (i3string_as_utf8(field))
+#define CHECK_WINDOW_FIELD(match_field, window_field, type)                                       \
+    do {                                                                                          \
+        if (match->match_field != NULL) {                                                         \
+            if (window->window_field == NULL) {                                                   \
+                return false;                                                                     \
+            }                                                                                     \
+                                                                                                  \
+            const char *window_field_str = GET_FIELD_##type(window->window_field);                \
+            if (strcmp(match->match_field->pattern, "__focused__") == 0 &&                        \
+                focused && focused->window && focused->window->window_field &&                    \
+                strcmp(window_field_str, GET_FIELD_##type(focused->window->window_field)) == 0) { \
+                LOG("window " #match_field " matches focused window\n");                          \
+            } else if (regex_matches(match->match_field, window_field_str)) {                     \
+                LOG("window " #match_field " matches (%s)\n", window_field_str);                  \
+            } else {                                                                              \
+                return false;                                                                     \
+            }                                                                                     \
+        }                                                                                         \
+    } while (0)
 
-    if (match->instance != NULL) {
-        if (window->class_instance == NULL)
-            return false;
-        if (strcmp(match->instance->pattern, "__focused__") == 0 &&
-            strcmp(window->class_instance, focused->window->class_instance) == 0) {
-            LOG("window instance matches focused window\n");
-        } else if (regex_matches(match->instance, window->class_instance)) {
-            LOG("window instance matches (%s)\n", window->class_instance);
-        } else {
-            return false;
-        }
-    }
+    CHECK_WINDOW_FIELD(class, class_class, str);
+    CHECK_WINDOW_FIELD(instance, class_instance, str);
 
     if (match->id != XCB_NONE) {
         if (window->id == match->id) {
@@ -122,33 +121,8 @@ bool match_matches_window(Match *match, i3Window *window) {
         }
     }
 
-    if (match->title != NULL) {
-        if (window->name == NULL)
-            return false;
-
-        const char *title = i3string_as_utf8(window->name);
-        if (strcmp(match->title->pattern, "__focused__") == 0 &&
-            strcmp(title, i3string_as_utf8(focused->window->name)) == 0) {
-            LOG("window title matches focused window\n");
-        } else if (regex_matches(match->title, title)) {
-            LOG("title matches (%s)\n", title);
-        } else {
-            return false;
-        }
-    }
-
-    if (match->window_role != NULL) {
-        if (window->role == NULL)
-            return false;
-        if (strcmp(match->window_role->pattern, "__focused__") == 0 &&
-            strcmp(window->role, focused->window->role) == 0) {
-            LOG("window role matches focused window\n");
-        } else if (regex_matches(match->window_role, window->role)) {
-            LOG("window_role matches (%s)\n", window->role);
-        } else {
-            return false;
-        }
-    }
+    CHECK_WINDOW_FIELD(title, name, i3string);
+    CHECK_WINDOW_FIELD(window_role, role, str);
 
     if (match->window_type != UINT32_MAX) {
         if (window->window_type == match->window_type) {
index 97ca6d40b42423a07582d44f3ade0f9001d7ef7f..545a910af24b48188685ccaef23dda50eb0d2642 100644 (file)
  */
 #include "all.h"
 
-typedef enum { BEFORE,
-               AFTER } position_t;
+/*
+ * Returns the lowest container in the tree that has both a and b as descendants.
+ *
+ */
+static Con *lowest_common_ancestor(Con *a, Con *b) {
+    Con *parent_a = a;
+    while (parent_a) {
+        Con *parent_b = b;
+        while (parent_b) {
+            if (parent_a == parent_b) {
+                return parent_a;
+            }
+            parent_b = parent_b->parent;
+        }
+        parent_a = parent_a->parent;
+    }
+    assert(false);
+}
+
+/*
+ * Returns the direct child of ancestor that contains con.
+ *
+ */
+static Con *child_containing_con_recursively(Con *ancestor, Con *con) {
+    Con *child = con;
+    while (child && child->parent != ancestor) {
+        child = child->parent;
+        assert(child->parent);
+    }
+    return child;
+}
+
+/*
+ * Returns true if the given container is the focused descendant of ancestor, recursively.
+ *
+ */
+static bool is_focused_descendant(Con *con, Con *ancestor) {
+    Con *current = con;
+    while (current != ancestor) {
+        if (TAILQ_FIRST(&(current->parent->focus_head)) != current) {
+            return false;
+        }
+        current = current->parent;
+        assert(current->parent);
+    }
+    return true;
+}
 
 /*
  * This function detaches 'con' from its parent and inserts it either before or
  * after 'target'.
  *
  */
-static void insert_con_into(Con *con, Con *target, position_t position) {
+void insert_con_into(Con *con, Con *target, position_t position) {
     Con *parent = target->parent;
     /* We need to preserve the old con->parent. While it might still be used to
      * insert the entry before/after it, we call the on_remove_child callback
      * afterwards which might then close the con if it is empty. */
     Con *old_parent = con->parent;
 
+    /* We compare the focus order of the children of the lowest common ancestor. If con or
+     * its ancestor is before target's ancestor then con should be placed before the target
+     * in the focus stack. */
+    Con *lca = lowest_common_ancestor(con, parent);
+    if (lca == con) {
+        ELOG("Container is being inserted into one of its descendants.\n");
+        return;
+    }
+
+    Con *con_ancestor = child_containing_con_recursively(lca, con);
+    Con *target_ancestor = child_containing_con_recursively(lca, target);
+    bool moves_focus_from_ancestor = is_focused_descendant(con, con_ancestor);
+    bool focus_before;
+
+    /* Determine if con is going to be placed before or after target in the parent's focus stack. */
+    if (con_ancestor == target_ancestor) {
+        /* Happens when the target is con's old parent. Eg with layout V [ A H [ B C ] ],
+         * if we move C up. Target will be H. */
+        focus_before = moves_focus_from_ancestor;
+    } else {
+        /* Look at the focus stack order of the children of the lowest common ancestor. */
+        Con *current;
+        TAILQ_FOREACH(current, &(lca->focus_head), focused) {
+            if (current == con_ancestor || current == target_ancestor) {
+                break;
+            }
+        }
+        focus_before = (current == con_ancestor);
+    }
+
+    /* If con is the focused container in our old ancestor we place the new ancestor
+     * before the old ancestor in the focus stack. Example:
+     * Consider the layout [ H [ V1 [ A* B ] V2 [ C ] ] ] where A is focused. We move to
+     * a second workspace and from there we move A to the right and switch back to the
+     * original workspace. Without the change focus would move to B instead of staying
+     * with A. */
+    if (moves_focus_from_ancestor && focus_before) {
+        Con *place = TAILQ_PREV(con_ancestor, focus_head, focused);
+        TAILQ_REMOVE(&(lca->focus_head), target_ancestor, focused);
+        if (place) {
+            TAILQ_INSERT_AFTER(&(lca->focus_head), place, target_ancestor, focused);
+        } else {
+            TAILQ_INSERT_HEAD(&(lca->focus_head), target_ancestor, focused);
+        }
+    }
+
     con_detach(con);
     con_fix_percent(con->parent);
 
@@ -46,12 +137,28 @@ static void insert_con_into(Con *con, Con *target, position_t position) {
 
     con->parent = parent;
 
+    if (parent == lca) {
+        if (focus_before) {
+            /* Example layout: H [ A B* ], we move A up/down. 'target' will be H. */
+            TAILQ_INSERT_BEFORE(target, con, focused);
+        } else {
+            /* Example layout: H [ A B* ], we move A up/down. 'target' will be H. */
+            TAILQ_INSERT_AFTER(&(parent->focus_head), target, con, focused);
+        }
+    } else {
+        if (focus_before) {
+            /* Example layout: V [ H [ A B ] C* ], we move C up. 'target' will be A. */
+            TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
+        } else {
+            /* Example layout: V [ H [ A* B ] C ], we move C up. 'target' will be A. */
+            TAILQ_INSERT_TAIL(&(parent->focus_head), con, focused);
+        }
+    }
+
     if (position == BEFORE) {
         TAILQ_INSERT_BEFORE(target, con, nodes);
-        TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
     } else if (position == AFTER) {
         TAILQ_INSERT_AFTER(&(parent->nodes_head), target, con, nodes);
-        TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
     }
 
     /* Pretend the con was just opened with regards to size percent values.
@@ -72,18 +179,16 @@ static void insert_con_into(Con *con, Con *target, position_t position) {
 static void attach_to_workspace(Con *con, Con *ws, direction_t direction) {
     con_detach(con);
     con_fix_percent(con->parent);
-
     CALL(con->parent, on_remove_child);
 
     con->parent = ws;
 
     if (direction == D_RIGHT || direction == D_DOWN) {
         TAILQ_INSERT_HEAD(&(ws->nodes_head), con, nodes);
-        TAILQ_INSERT_HEAD(&(ws->focus_head), con, focused);
     } else {
         TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes);
-        TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
     }
+    TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
 
     /* Pretend the con was just opened with regards to size percent values.
      * Since the con is moved to a completely different con, the old value
@@ -98,7 +203,6 @@ static void attach_to_workspace(Con *con, Con *ws, direction_t direction) {
  *
  */
 static void move_to_output_directed(Con *con, direction_t direction) {
-    Con *old_ws = con_get_workspace(con);
     Output *current_output = get_output_for_con(con);
     Output *output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
 
@@ -115,17 +219,26 @@ static void move_to_output_directed(Con *con, direction_t direction) {
         return;
     }
 
+    Con *old_ws = con_get_workspace(con);
+    const bool moves_focus = (focused == con);
     attach_to_workspace(con, ws, direction);
-
-    /* fix the focus stack */
-    con_activate(con);
+    if (moves_focus) {
+        /* workspace_show will not correctly update the active workspace because
+         * the focused container, con, is now a child of ws. To work around this
+         * and still produce the correct workspace focus events (see
+         * 517-regress-move-direction-ipc.t) we need to temporarily set focused
+         * to the old workspace. */
+        focused = old_ws;
+        workspace_show(ws);
+        con_focus(con);
+    }
 
     /* force re-painting the indicators */
     FREE(con->deco_render_params);
 
     tree_flatten(croot);
-
-    ipc_send_workspace_event("focus", ws, old_ws);
+    ipc_send_window_event("move", con);
+    ewmh_update_wm_desktop();
 }
 
 /*
@@ -146,13 +259,19 @@ void tree_move(Con *con, int direction) {
         return;
     }
 
-    if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) {
+    if (con->fullscreen_mode == CF_GLOBAL) {
+        DLOG("Not moving fullscreen global container\n");
+        return;
+    }
+
+    if ((con->fullscreen_mode == CF_OUTPUT) ||
+        (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1)) {
         /* This is the only con on this workspace */
         move_to_output_directed(con, direction);
         return;
     }
 
-    orientation_t o = (direction == D_LEFT || direction == D_RIGHT ? HORIZ : VERT);
+    orientation_t o = orientation_from_direction(direction);
 
     Con *same_orientation = con_parent_with_orientation(con, o);
     /* The do {} while is used to 'restart' at this point with a different
@@ -179,9 +298,10 @@ void tree_move(Con *con, int direction) {
 
         /* easy case: the move is within this container */
         if (same_orientation == con->parent) {
-            DLOG("We are in the same container\n");
-            Con *swap;
-            if ((swap = (direction == D_LEFT || direction == D_UP ? TAILQ_PREV(con, nodes_head, nodes) : TAILQ_NEXT(con, nodes)))) {
+            Con *swap = (direction == D_LEFT || direction == D_UP)
+                            ? TAILQ_PREV(con, nodes_head, nodes)
+                            : TAILQ_NEXT(con, nodes);
+            if (swap) {
                 if (!con_is_leaf(swap)) {
                     DLOG("Moving into our bordering branch\n");
                     target = con_descend_direction(swap, direction);
@@ -193,26 +313,22 @@ void tree_move(Con *con, int direction) {
                     insert_con_into(con, target, position);
                     goto end;
                 }
-                if (direction == D_LEFT || direction == D_UP)
+
+                DLOG("Swapping with sibling.\n");
+                if (direction == D_LEFT || direction == D_UP) {
                     TAILQ_SWAP(swap, con, &(swap->parent->nodes_head), nodes);
-                else
+                } else {
                     TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes);
+                }
 
-                TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
-                TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused);
-
-                DLOG("Swapped.\n");
                 ipc_send_window_event("move", con);
-                ewmh_update_wm_desktop();
                 return;
             }
 
             if (con->parent == con_get_workspace(con)) {
-                /*  If we couldn't find a place to move it on this workspace,
-                 *  try to move it to a workspace on a different output */
+                /* If we couldn't find a place to move it on this workspace, try
+                 * to move it to a workspace on a different output */
                 move_to_output_directed(con, direction);
-                ipc_send_window_event("move", con);
-                ewmh_update_wm_desktop();
                 return;
             }
 
@@ -257,6 +373,7 @@ void tree_move(Con *con, int direction) {
          * and move it to the next output. */
         DLOG("Grandparent is workspace\n");
         move_to_output_directed(con, direction);
+        return;
     } else {
         DLOG("Moving into container above\n");
         position = (direction == D_UP || direction == D_LEFT ? BEFORE : AFTER);
@@ -264,11 +381,6 @@ void tree_move(Con *con, int direction) {
     }
 
 end:
-    /* We need to call con_focus() to fix the focus stack "above" the container
-     * we just inserted the focused container into (otherwise, the parent
-     * container(s) would still point to the old container(s)). */
-    con_focus(con);
-
     /* force re-painting the indicators */
     FREE(con->deco_render_params);
 
index c76dfd035d213360ef727663b3e98ff49fba6db4..ebba5c77d40670afefe7b83f92245a5afea3b71f 100644 (file)
@@ -10,7 +10,7 @@
 #include "all.h"
 
 /*
- * Returns the output container below the given output container.
+ * Returns the content container below the given output container.
  *
  */
 Con *output_get_content(Con *output) {
@@ -72,8 +72,15 @@ Output *get_output_for_con(Con *con) {
  * Iterates over all outputs and pushes sticky windows to the currently visible
  * workspace on that output.
  *
+ * old_focus is used to determine if a sticky window is going to be focused.
+ * old_focus might be different than the currently focused container because the
+ * caller might need to temporarily change the focus and then call
+ * output_push_sticky_windows. For example, workspace_show needs to set focus to
+ * one of its descendants first, then call output_push_sticky_windows that
+ * should focus a sticky window if it was the focused in the previous workspace.
+ *
  */
-void output_push_sticky_windows(Con *to_focus) {
+void output_push_sticky_windows(Con *old_focus) {
     Con *output;
     TAILQ_FOREACH(output, &(croot->focus_head), focused) {
         Con *workspace, *visible_ws = NULL;
@@ -95,12 +102,17 @@ void output_push_sticky_windows(Con *to_focus) {
                  child != TAILQ_END(&(current_ws->focus_head));) {
                 Con *current = child;
                 child = TAILQ_NEXT(child, focused);
-                if (current->type != CT_FLOATING_CON)
+                if (current->type != CT_FLOATING_CON || !con_is_sticky(current)) {
                     continue;
+                }
 
-                if (con_is_sticky(current)) {
-                    bool ignore_focus = (to_focus == NULL) || (current != to_focus->parent);
-                    con_move_to_workspace(current, visible_ws, true, false, ignore_focus);
+                bool ignore_focus = (old_focus == NULL) || (current != old_focus->parent);
+                con_move_to_workspace(current, visible_ws, true, false, ignore_focus);
+                if (!ignore_focus) {
+                    Con *current_ws = con_get_workspace(focused);
+                    con_activate(con_descend_focused(current));
+                    /* Pushing sticky windows shouldn't change the focused workspace. */
+                    con_activate(con_descend_focused(current_ws));
                 }
             }
         }
index 85add08fa41ea2cb64a60fc194a3fe65d02a1550..6bb8d9e649e5137f08da46c1db9e2c35747e4184 100644 (file)
@@ -114,6 +114,20 @@ Output *get_output_containing(unsigned int x, unsigned int y) {
     return NULL;
 }
 
+/*
+ * Returns the active output which contains the midpoint of the given rect. If
+ * such an output doesn't exist, returns the output which contains most of the
+ * rectangle or NULL if there is no output which intersects with it.
+ *
+ */
+Output *get_output_from_rect(Rect rect) {
+    unsigned int mid_x = rect.x + rect.width / 2;
+    unsigned int mid_y = rect.y + rect.height / 2;
+    Output *output = get_output_containing(mid_x, mid_y);
+
+    return output ? output : output_containing_rect(rect);
+}
+
 /*
  * Returns the active output which spans exactly the area specified by
  * rect or NULL if there is no output like this.
@@ -136,27 +150,37 @@ Output *get_output_with_dimensions(Rect rect) {
 }
 
 /*
- * In contained_by_output, we check if any active output contains part of the container.
+ * In output_containing_rect, we check if any active output contains part of the container.
  * We do this by checking if the output rect is intersected by the Rect.
  * This is the 2-dimensional counterpart of get_output_containing.
- * Since we don't actually need the outputs intersected by the given Rect (There could
- * be many), we just return true or false for convenience.
+ * Returns the output with the maximum intersecting area.
  *
  */
-bool contained_by_output(Rect rect) {
+Output *output_containing_rect(Rect rect) {
     Output *output;
     int lx = rect.x, uy = rect.y;
     int rx = rect.x + rect.width, by = rect.y + rect.height;
+    long max_area = 0;
+    Output *result = NULL;
     TAILQ_FOREACH(output, &outputs, outputs) {
         if (!output->active)
             continue;
+        int lx_o = (int)output->rect.x, uy_o = (int)output->rect.y;
+        int rx_o = (int)(output->rect.x + output->rect.width), by_o = (int)(output->rect.y + output->rect.height);
         DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
              rect.x, rect.y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
-        if (rx >= (int)output->rect.x && lx <= (int)(output->rect.x + output->rect.width) &&
-            by >= (int)output->rect.y && uy <= (int)(output->rect.y + output->rect.height))
-            return true;
+        int left = max(lx, lx_o);
+        int right = min(rx, rx_o);
+        int bottom = min(by, by_o);
+        int top = max(uy, uy_o);
+        if (left < right && bottom > top) {
+            long area = (right - left) * (bottom - top);
+            if (area > max_area) {
+                result = output;
+            }
+        }
     }
-    return false;
+    return result;
 }
 
 /*
@@ -400,14 +424,10 @@ void init_ws_for_output(Output *output, Con *content) {
     /* go through all assignments and move the existing workspaces to this output */
     struct Workspace_Assignment *assignment;
     TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-        if (strcmp(assignment->output, output_primary_name(output)) != 0)
+        if (!output_triggers_assignment(output, assignment)) {
             continue;
-
-        /* check if this workspace actually exists */
-        Con *workspace = NULL, *out;
-        TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
-        GREP_FIRST(workspace, output_get_content(out),
-                   !strcasecmp(child->name, assignment->name));
+        }
+        Con *workspace = get_existing_workspace_by_name(assignment->name);
         if (workspace == NULL)
             continue;
 
@@ -481,8 +501,9 @@ void init_ws_for_output(Output *output, Con *content) {
 
     /* otherwise, we create the first assigned ws for this output */
     TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-        if (strcmp(assignment->output, output_primary_name(output)) != 0)
+        if (!output_triggers_assignment(output, assignment)) {
             continue;
+        }
 
         LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
             assignment->name, assignment->output);
@@ -496,7 +517,7 @@ void init_ws_for_output(Output *output, Con *content) {
     Con *ws = create_workspace_on_output(output, content);
 
     /* TODO: Set focus in main.c */
-    con_activate(ws);
+    con_focus(ws);
 }
 
 /*
@@ -529,7 +550,7 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) {
     }
 
     /* If default_orientation is NO_ORIENTATION, we change the orientation of
-     * the workspaces and their childs depending on output resolution. This is
+     * the workspaces and their children depending on output resolution. This is
      * only done for workspaces with maximum one child. */
     if (config.default_orientation == NO_ORIENTATION) {
         TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) {
@@ -836,8 +857,9 @@ void randr_query_outputs(void) {
     /* If there's no randr output, enable the output covering the root window. */
     if (any_randr_output_active()) {
         DLOG("Active RandR output found. Disabling root output.\n");
-        if (root_output->active)
+        if (root_output && root_output->active) {
             root_output->to_be_disabled = true;
+        }
     } else {
         DLOG("No active RandR output found. Enabling root output.\n");
         root_output->active = true;
@@ -924,7 +946,9 @@ void randr_query_outputs(void) {
             continue;
 
         DLOG("Focusing primary output %s\n", output_primary_name(output));
-        con_activate(con_descend_focused(output->con));
+        Con *content = output_get_content(output->con);
+        Con *ws = TAILQ_FIRST(&(content)->focus_head);
+        workspace_show(ws);
     }
 
     /* render_layout flushes */
@@ -969,7 +993,7 @@ void randr_disable_output(Output *output) {
             if (current != next && TAILQ_EMPTY(&(current->focus_head))) {
                 /* the workspace is empty and not focused, get rid of it */
                 DLOG("Getting rid of current = %p / %s (empty, unfocused)\n", current, current->name);
-                tree_close_internal(current, DONT_KILL_WINDOW, false, false);
+                tree_close_internal(current, DONT_KILL_WINDOW, false);
                 continue;
             }
             DLOG("Detaching current = %p / %s\n", current, current->name);
@@ -1015,7 +1039,7 @@ void randr_disable_output(Output *output) {
         Con *con = output->con;
         /* clear the pointer before calling tree_close_internal in which the memory is freed */
         output->con = NULL;
-        tree_close_internal(con, DONT_KILL_WINDOW, true, false);
+        tree_close_internal(con, DONT_KILL_WINDOW, true);
         DLOG("Done. Should be fine now\n");
     }
 
index 0125b89d683a42346edaa179e902edba3affb447..d8bffc61f2b1a1232a16e10c248ac752db9d299e 100644 (file)
@@ -183,26 +183,28 @@ free_params:
 }
 
 static int *precalculate_sizes(Con *con, render_params *p) {
+    if ((con->layout != L_SPLITH && con->layout != L_SPLITV) || p->children <= 0) {
+        return NULL;
+    }
+
     int *sizes = smalloc(p->children * sizeof(int));
-    if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && p->children > 0) {
-        assert(!TAILQ_EMPTY(&con->nodes_head));
+    assert(!TAILQ_EMPTY(&con->nodes_head));
 
-        Con *child;
-        int i = 0, assigned = 0;
-        int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height;
-        TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
-            double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children;
-            assigned += sizes[i++] = percentage * total;
-        }
-        assert(assigned == total ||
-               (assigned > total && assigned - total <= p->children * 2) ||
-               (assigned < total && total - assigned <= p->children * 2));
-        int signal = assigned < total ? 1 : -1;
-        while (assigned != total) {
-            for (i = 0; i < p->children && assigned != total; ++i) {
-                sizes[i] += signal;
-                assigned += signal;
-            }
+    Con *child;
+    int i = 0, assigned = 0;
+    int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height;
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children;
+        assigned += sizes[i++] = lround(percentage * total);
+    }
+    assert(assigned == total ||
+           (assigned > total && assigned - total <= p->children * 2) ||
+           (assigned < total && total - assigned <= p->children * 2));
+    int signal = assigned < total ? 1 : -1;
+    while (assigned != total) {
+        for (i = 0; i < p->children && assigned != total; ++i) {
+            sizes[i] += signal;
+            assigned += signal;
         }
     }
 
@@ -232,25 +234,22 @@ static void render_root(Con *con, Con *fullscreen) {
             continue;
         }
         Con *workspace = TAILQ_FIRST(&(content->focus_head));
-        Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
+        Con *fullscreen = con_get_fullscreen_covering_ws(workspace);
         Con *child;
         TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) {
-            /* Don’t render floating windows when there is a fullscreen window
-             * on that workspace. Necessary to make floating fullscreen work
-             * correctly (ticket #564). */
-            /* If there is no fullscreen->window, this cannot be a
-             * transient window, so we _know_ we need to skip it. This
-             * happens during restarts where the container already exists,
-             * but the window was not yet associated. */
-            if (fullscreen != NULL && fullscreen->window == NULL)
-                continue;
-            if (fullscreen != NULL && fullscreen->window != NULL) {
+            if (fullscreen != NULL) {
+                /* Don’t render floating windows when there is a fullscreen
+                 * window on that workspace. Necessary to make floating
+                 * fullscreen work correctly (ticket #564). Exception to the
+                 * above rule: smart popup_during_fullscreen handling (popups
+                 * belonging to the fullscreen app will be rendered). */
+                if (config.popup_during_fullscreen != PDF_SMART) {
+                    continue;
+                }
+
                 Con *floating_child = con_descend_focused(child);
                 Con *transient_con = floating_child;
                 bool is_transient_for = false;
-                /* Exception to the above rule: smart
-                 * popup_during_fullscreen handling (popups belonging to
-                 * the fullscreen app will be rendered). */
                 while (transient_con != NULL &&
                        transient_con->window != NULL &&
                        transient_con->window->transient_for != XCB_NONE) {
index ee50bfbc694514abe8b43c51a5cffed65365fa69..d746ea227f9bb55b608d658586177a263fec776c 100644 (file)
@@ -57,7 +57,7 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
     }
 
     /* Go up in the tree and search for a container to resize */
-    const orientation_t search_orientation = ((direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT);
+    const orientation_t search_orientation = orientation_from_direction(direction);
     const bool dir_backwards = (direction == D_UP || direction == D_LEFT);
     while (first->type != CT_WORKSPACE &&
            first->type != CT_FLOATING_CON &&
@@ -101,10 +101,66 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
     return true;
 }
 
-int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
-    DLOG("resize handler\n");
+/*
+ * Calculate the given container's new percent given a change in pixels.
+ *
+ */
+double px_resize_to_percent(Con *con, int px_diff) {
+    Con *parent = con->parent;
+    const orientation_t o = con_orientation(parent);
+    const int total = (o == HORIZ ? parent->rect.width : parent->rect.height);
+    /* deco_rect.height is subtracted from each child in render_con_split */
+    const int target = px_diff + (o == HORIZ ? con->rect.width : con->rect.height + con->deco_rect.height);
+    return ((double)target / (double)total);
+}
+
+/*
+ * Calculate the minimum percent needed for the given container to be at least 1
+ * pixel.
+ *
+ */
+double percent_for_1px(Con *con) {
+    Con *parent = con->parent;
+    const orientation_t o = con_orientation(parent);
+    const int total = (o == HORIZ ? parent->rect.width : parent->rect.height);
+    const int target = (o == HORIZ ? 1 : 1 + con->deco_rect.height);
+    return ((double)target / (double)total);
+}
 
-    /* TODO: previously, we were getting a rect containing all screens. why? */
+/*
+ * Resize the two given containers using the given amount of pixels or
+ * percentage points. One of the two needs to be 0. A positive amount means
+ * growing the first container while a negative means shrinking it.
+ * Returns false when the resize would result in one of the two containers
+ * having less than 1 pixel of size.
+ *
+ */
+bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt) {
+    assert(px * ppt == 0);
+
+    Con *parent = first->parent;
+    double new_first_percent;
+    double new_second_percent;
+    if (ppt) {
+        new_first_percent = first->percent + ((double)ppt / 100.0);
+        new_second_percent = second->percent - ((double)ppt / 100.0);
+    } else {
+        new_first_percent = px_resize_to_percent(first, px);
+        new_second_percent = second->percent + first->percent - new_first_percent;
+    }
+    /* Ensure that no container will be less than 1 pixel in the resizing
+     * direction. */
+    if (new_first_percent < percent_for_1px(first) || new_second_percent < percent_for_1px(second)) {
+        return false;
+    }
+
+    first->percent = new_first_percent;
+    second->percent = new_second_percent;
+    con_fix_percent(parent);
+    return true;
+}
+
+void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
     Con *output = con_get_output(first);
     DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width);
 
@@ -117,29 +173,27 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
     mask = XCB_CW_OVERRIDE_REDIRECT;
     values[0] = 1;
 
-    /* Open a new window, the resizebar. Grab the pointer and move the window around
-       as the user moves the pointer. */
+    /* Open a new window, the resizebar. Grab the pointer and move the window
+     * around as the user moves the pointer. */
     xcb_window_t grabwin = create_window(conn, output->rect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
                                          XCB_WINDOW_CLASS_INPUT_ONLY, XCURSOR_CURSOR_POINTER, true, mask, values);
 
-    /* Keep track of the coordinate orthogonal to motion so we can determine
-     * the length of the resize afterward. */
+    /* Keep track of the coordinate orthogonal to motion so we can determine the
+     * length of the resize afterward. */
     uint32_t initial_position, new_position;
 
     /* Configure the resizebar and snap the pointer. The resizebar runs along
      * the rect of the second con and follows the motion of the pointer. */
     Rect helprect;
+    helprect.x = second->rect.x;
+    helprect.y = second->rect.y;
     if (orientation == HORIZ) {
-        helprect.x = second->rect.x;
-        helprect.y = second->rect.y;
         helprect.width = logical_px(2);
         helprect.height = second->rect.height;
         initial_position = second->rect.x;
         xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
                          second->rect.x, event->root_y);
     } else {
-        helprect.x = second->rect.x;
-        helprect.y = second->rect.y;
         helprect.width = second->rect.width;
         helprect.height = logical_px(2);
         initial_position = second->rect.y;
@@ -173,37 +227,17 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
     xcb_flush(conn);
 
     /* User cancelled the drag so no action should be taken. */
-    if (drag_result == DRAG_REVERT)
-        return 0;
+    if (drag_result == DRAG_REVERT) {
+        return;
+    }
 
     int pixels = (new_position - initial_position);
-
     DLOG("Done, pixels = %d\n", pixels);
 
-    // if we got thus far, the containers must have
-    // percentages associated with them
+    /* if we got thus far, the containers must have valid percentages. */
     assert(first->percent > 0.0);
     assert(second->percent > 0.0);
-
-    // calculate the new percentage for the first container
-    double new_percent, difference;
-    double percent = first->percent;
-    DLOG("percent = %f\n", percent);
-    int original = (orientation == HORIZ ? first->rect.width : first->rect.height);
-    DLOG("original = %d\n", original);
-    new_percent = (original + pixels) * (percent / original);
-    difference = percent - new_percent;
-    DLOG("difference = %f\n", difference);
-    DLOG("new percent = %f\n", new_percent);
-    first->percent = new_percent;
-
-    // calculate the new percentage for the second container
-    double s_percent = second->percent;
-    second->percent = s_percent + difference;
-    DLOG("second->percent = %f\n", second->percent);
-
-    // now we must make sure that the sum of the percentages remain 1.0
-    con_fix_percent(first->parent);
-
-    return 0;
+    const bool result = resize_neighboring_cons(first, second, pixels, 0);
+    DLOG("Graphical resize %s: first->percent = %f, second->percent = %f.\n",
+         result ? "successful" : "failed", first->percent, second->percent);
 }
index 95154014c729a655a40b4ab9be49dd42f815eedf..d564bf3208e1ac7768522db2c9ca9a09295a0a44 100644 (file)
@@ -84,7 +84,7 @@ void scratchpad_move(Con *con) {
  * can press the same key to quickly look something up).
  *
  */
-void scratchpad_show(Con *con) {
+bool scratchpad_show(Con *con) {
     DLOG("should show scratchpad window %p\n", con);
     Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
     Con *floating;
@@ -97,7 +97,7 @@ void scratchpad_show(Con *con) {
         floating->scratchpad_state != SCRATCHPAD_NONE) {
         DLOG("Focused window is a scratchpad window, hiding it.\n");
         scratchpad_move(focused);
-        return;
+        return true;
     }
 
     /* If the current con or any of its parents are in fullscreen mode, we
@@ -124,7 +124,7 @@ void scratchpad_show(Con *con) {
                  * window inside this scratch container in order to
                  * keep the focus the same within this container */
             con_activate(con_descend_tiling_focused(walk_con));
-            return;
+            return true;
         }
     }
 
@@ -141,7 +141,8 @@ void scratchpad_show(Con *con) {
             DLOG("Found a visible scratchpad window on another workspace,\n");
             DLOG("moving it to this workspace: con = %p\n", walk_con);
             con_move_to_workspace(walk_con, focused_ws, true, false, false);
-            return;
+            con_activate(con_descend_focused(walk_con));
+            return true;
         }
     }
 
@@ -149,7 +150,7 @@ void scratchpad_show(Con *con) {
      * is actually in the scratchpad */
     if (con && con->parent->scratchpad_state == SCRATCHPAD_NONE) {
         DLOG("Window is not in the scratchpad, doing nothing.\n");
-        return;
+        return false;
     }
 
     /* If this was 'scratchpad show' with criteria, we check if it matches a
@@ -165,7 +166,7 @@ void scratchpad_show(Con *con) {
         if (current == active) {
             DLOG("Window is a scratchpad window, hiding it.\n");
             scratchpad_move(con);
-            return;
+            return true;
         }
     }
 
@@ -178,7 +179,7 @@ void scratchpad_show(Con *con) {
         if (!con) {
             LOG("You don't have any scratchpad windows yet.\n");
             LOG("Use 'move scratchpad' to move a window to the scratchpad.\n");
-            return;
+            return false;
         }
     } else {
         /* We used a criterion, so we need to do what follows (moving,
@@ -206,6 +207,8 @@ void scratchpad_show(Con *con) {
     }
 
     con_activate(con_descend_focused(con));
+
+    return true;
 }
 
 /*
index 12ed0ef990dc7fc25bf15bb84f374d071d0c869a..e49c30ba668efa3883ca048682481e16fb638e5b 100644 (file)
@@ -62,14 +62,13 @@ static int sighandler_backtrace(void) {
 
     char *filename = NULL;
     int suffix = 0;
-    struct stat bt;
     /* Find a unique filename for the backtrace (since the PID of i3 stays the
      * same), so that we don’t overwrite earlier backtraces. */
     do {
         FREE(filename);
         sasprintf(&filename, "%s/i3-backtrace.%d.%d.txt", tmpdir, pid_parent, suffix);
         suffix++;
-    } while (stat(filename, &bt) == 0);
+    } while (path_exists(filename));
 
     pid_t pid_gdb = fork();
     if (pid_gdb < 0) {
@@ -130,7 +129,7 @@ static int sighandler_backtrace(void) {
     if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
         DLOG("GDB did not run properly\n");
         return -1;
-    } else if (stat(filename, &bt) == -1) {
+    } else if (!path_exists(filename)) {
         DLOG("GDB executed successfully, but no backtrace was generated\n");
         return -1;
     }
@@ -300,7 +299,7 @@ static void sighandler_handle_key_press(xcb_key_press_event_t *event) {
     }
 }
 
-void handle_signal(int sig, siginfo_t *info, void *data) {
+static void handle_signal(int sig, siginfo_t *info, void *data) {
     DLOG("i3 crashed. SIG: %d\n", sig);
 
     struct sigaction action;
index 166842e07c04e8471123ec331012837ffe189220..6302d811e3b4b3688ff59cc8bbfe7108edcfc390 100644 (file)
@@ -49,6 +49,7 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) {
 
     if (!sequence) {
         DLOG("Sequence already deleted, nevermind.\n");
+        free(w);
         return;
     }
 
@@ -94,7 +95,7 @@ static int _prune_startup_sequences(void) {
     return active_sequences;
 }
 
-/**
+/*
  * Deletes a startup sequence, ignoring whether its timeout has elapsed.
  * Useful when e.g. a window is moved between workspaces and its children
  * shouldn't spawn on the original workspace.
@@ -117,10 +118,10 @@ void startup_sequence_delete(struct Startup_Sequence *sequence) {
 }
 
 /*
- * Starts the given application by passing it through a shell. We use double fork
- * to avoid zombie processes. As the started application’s parent exits (immediately),
- * the application is reparented to init (process-id 1), which correctly handles
- * childs, so we don’t have to do it :-).
+ * Starts the given application by passing it through a shell. We use double
+ * fork to avoid zombie processes. As the started application’s parent exits
+ * (immediately), the application is reparented to init (process-id 1), which
+ * correctly handles children, so we don’t have to do it :-).
  *
  * The shell used to start applications is the system's bourne shell (i.e.,
  * /bin/sh).
@@ -190,7 +191,7 @@ void start_application(const char *command, bool no_startup_id) {
             if (!no_startup_id)
                 sn_launcher_context_setup_child_process(context);
 
-            execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void *)NULL);
+            execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL);
             /* not reached */
         }
         _exit(0);
@@ -256,7 +257,7 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) {
     }
 }
 
-/**
+/*
  * Renames workspaces that are mentioned in the startup sequences.
  *
  */
@@ -272,7 +273,7 @@ void startup_sequence_rename_workspace(const char *old_name, const char *new_nam
     }
 }
 
-/**
+/*
  * Gets the stored startup sequence for the _NET_STARTUP_ID of a given window.
  *
  */
diff --git a/src/sync.c b/src/sync.c
new file mode 100644 (file)
index 0000000..dafbc35
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * sync.c: i3 sync protocol: https://i3wm.org/docs/testsuite.html#i3_sync
+ *
+ */
+#include "all.h"
+
+void sync_respond(xcb_window_t window, uint32_t rnd) {
+    DLOG("[i3 sync protocol] Sending random value %d back to X11 window 0x%08x\n", rnd, window);
+
+    void *reply = scalloc(32, 1);
+    xcb_client_message_event_t *ev = reply;
+
+    ev->response_type = XCB_CLIENT_MESSAGE;
+    ev->window = window;
+    ev->type = A_I3_SYNC;
+    ev->format = 32;
+    ev->data.data32[0] = window;
+    ev->data.data32[1] = rnd;
+
+    xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)ev);
+    xcb_flush(conn);
+    free(reply);
+}
index 6c6a614e22d46479381e74447fd12f55dd378bb1..e384987366bd5982f4a430a80b48ed349995e40f 100644 (file)
@@ -92,6 +92,10 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) {
 
     DLOG("appended tree, using new root\n");
     croot = TAILQ_FIRST(&(croot->nodes_head));
+    if (!croot) {
+        /* tree_append_json failed. Continuing here would segfault. */
+        goto out;
+    }
     DLOG("new root = %p\n", croot);
     Con *out = TAILQ_FIRST(&(croot->nodes_head));
     DLOG("out = %p\n", out);
@@ -175,16 +179,6 @@ Con *tree_open_con(Con *con, i3Window *window) {
     return new;
 }
 
-static bool _is_con_mapped(Con *con) {
-    Con *child;
-
-    TAILQ_FOREACH(child, &(con->nodes_head), nodes)
-    if (_is_con_mapped(child))
-        return true;
-
-    return con->mapped;
-}
-
 /*
  * Closes the given container including all children.
  * Returns true if the container was killed or false if just WM_DELETE was sent
@@ -193,22 +187,10 @@ static bool _is_con_mapped(Con *con) {
  * The dont_kill_parent flag is specified when the function calls itself
  * recursively while deleting a containers children.
  *
- * The force_set_focus flag is specified in the case of killing a floating
- * window: tree_close_internal() will be invoked for the CT_FLOATINGCON (the parent
- * container) and focus should be set there.
- *
  */
-bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus) {
-    bool was_mapped = con->mapped;
+bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent) {
     Con *parent = con->parent;
 
-    if (!was_mapped) {
-        /* Even if the container itself is not mapped, its children may be
-         * mapped (for example split containers don't have a mapped window on
-         * their own but usually contain mapped children). */
-        was_mapped = _is_con_mapped(con);
-    }
-
     /* remove the urgency hint of the workspace (if set) */
     if (con->urgent) {
         con_set_urgency(con, false);
@@ -216,10 +198,6 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
         workspace_update_urgent_flag(con_get_workspace(con));
     }
 
-    /* Get the container which is next focused */
-    Con *next = con_next_focused(con);
-    DLOG("next = %p, focused = %p\n", next, focused);
-
     DLOG("closing %p, kill_window = %d\n", con, kill_window);
     Con *child, *nextchild;
     bool abort_kill = false;
@@ -228,8 +206,9 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
     for (child = TAILQ_FIRST(&(con->nodes_head)); child;) {
         nextchild = TAILQ_NEXT(child, nodes);
         DLOG("killing child=%p\n", child);
-        if (!tree_close_internal(child, kill_window, true, false))
+        if (!tree_close_internal(child, kill_window, true)) {
             abort_kill = true;
+        }
         child = nextchild;
     }
 
@@ -281,17 +260,8 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
     Con *ws = con_get_workspace(con);
 
     /* Figure out which container to focus next before detaching 'con'. */
-    if (con_is_floating(con)) {
-        if (con == focused) {
-            DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws);
-            next = con_next_focused(parent);
-
-            dont_kill_parent = true;
-            DLOG("Alright, focusing %p\n", next);
-        } else {
-            next = NULL;
-        }
-    }
+    Con *next = (con == focused) ? con_next_focused(con) : NULL;
+    DLOG("next = %p, focused = %p\n", next, focused);
 
     /* Detach the container so that it will not be rendered anymore. */
     con_detach(con);
@@ -324,12 +294,6 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
     /* kill the X11 part of this container */
     x_con_kill(con);
 
-    if (con_is_floating(con)) {
-        DLOG("Container was floating, killing floating container\n");
-        tree_close_internal(parent, DONT_KILL_WINDOW, false, (con == focused));
-        DLOG("parent container killed\n");
-    }
-
     if (ws == con) {
         DLOG("Closing a workspace container, updating EWMH atoms\n");
         ewmh_update_number_of_desktops();
@@ -339,30 +303,10 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
 
     con_free(con);
 
-    /* in the case of floating windows, we already focused another container
-     * when closing the parent, so we can exit now. */
-    if (!next) {
-        DLOG("No next container, i will just exit now\n");
-        return true;
-    }
-
-    if (was_mapped || con == focused) {
-        if ((kill_window != DONT_KILL_WINDOW) || !dont_kill_parent || con == focused) {
-            DLOG("focusing %p / %s\n", next, next->name);
-            if (next->type == CT_DOCKAREA) {
-                /* Instead of focusing the dockarea, we need to restore focus to the workspace */
-                con_activate(con_descend_focused(output_get_content(next->parent)));
-            } else {
-                if (!force_set_focus && con != focused)
-                    DLOG("not changing focus, the container was not focused before\n");
-                else
-                    con_activate(next);
-            }
-        } else {
-            DLOG("not focusing because we're not killing anybody\n");
-        }
+    if (next) {
+        con_activate(next);
     } else {
-        DLOG("not focusing, was not mapped\n");
+        DLOG("not changing focus, the container was not focused before\n");
     }
 
     /* check if the parent container is empty now and close it */
@@ -567,9 +511,16 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
         if (!workspace)
             return false;
 
-        Con *focus = con_descend_tiling_focused(workspace);
-        if (focus == workspace) {
-            focus = con_descend_focused(workspace);
+        /* Use descend_focused first to give higher priority to floating or
+         * tiling fullscreen containers. */
+        Con *focus = con_descend_focused(workspace);
+        if (focus->fullscreen_mode == CF_NONE) {
+            Con *focus_tiling = con_descend_tiling_focused(workspace);
+            /* If descend_tiling returned a workspace then focus is either a
+             * floating container or the same workspace. */
+            if (focus_tiling != workspace) {
+                focus = focus_tiling;
+            }
         }
 
         workspace_show(workspace);
@@ -748,7 +699,7 @@ void tree_flatten(Con *con) {
 
     /* 4: close the redundant cons */
     DLOG("closing redundant cons\n");
-    tree_close_internal(con, DONT_KILL_WINDOW, true, false);
+    tree_close_internal(con, DONT_KILL_WINDOW, true);
 
     /* Well, we got to abort the recursion here because we destroyed the
      * container. However, if tree_flatten() is called sufficiently often,
index dc3444f7b6aa950be8b4696896ac55b1cc4033fc..85f359c0fa801b45e20a9ee9515253cd0316d934 100644 (file)
@@ -217,7 +217,7 @@ static char **add_argument(char **original, char *opt_char, char *opt_arg, char
 #define y(x, ...) yajl_gen_##x(gen, ##__VA_ARGS__)
 #define ystr(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str))
 
-char *store_restart_layout(void) {
+static char *store_restart_layout(void) {
     setlocale(LC_NUMERIC, "C");
     yajl_gen gen = yajl_gen_alloc(NULL);
 
@@ -501,9 +501,16 @@ ssize_t slurp(const char *path, char **buf) {
     fclose(f);
     if ((ssize_t)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;
+        FREE(*buf);
         return -1;
     }
     return (ssize_t)n;
 }
+
+/*
+ * Convert a direction to its corresponding orientation.
+ *
+ */
+orientation_t orientation_from_direction(direction_t direction) {
+    return (direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT;
+}
index 8c46a94917b9f2f3dbc18848e8154d157a99fa06..a2d9b0e8ae469068d8a92bffc20708cb81ce51e8 100644 (file)
@@ -19,6 +19,34 @@ static char *previous_workspace_name = NULL;
  * keybindings. */
 static char **binding_workspace_names = NULL;
 
+/*
+ * Returns the workspace with the given name or NULL if such a workspace does
+ * not exist.
+ *
+ */
+Con *get_existing_workspace_by_name(const char *name) {
+    Con *output, *workspace = NULL;
+    TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+        GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, name));
+    }
+
+    return workspace;
+}
+
+/*
+ * Returns the workspace with the given number or NULL if such a workspace does
+ * not exist.
+ *
+ */
+Con *get_existing_workspace_by_num(int num) {
+    Con *output, *workspace = NULL;
+    TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+        GREP_FIRST(workspace, output_get_content(output), child->num == num);
+    }
+
+    return workspace;
+}
+
 /*
  * Sets ws->layout to splith/splitv if default_orientation was specified in the
  * configfile. Otherwise, it uses splith/splitv depending on whether the output
@@ -39,6 +67,52 @@ static void _workspace_apply_default_orientation(Con *ws) {
     }
 }
 
+/*
+ * Returns the first output that is assigned to a workspace specified by the
+ * given name or number or NULL if no such output exists. If there is a
+ * workspace with a matching name and another workspace with a matching number,
+ * the output assigned to the first one is returned.
+ * The order of the 'ws_assignments' queue is respected: if multiple assignments
+ * match the specified workspace, the first one is returned.
+ * If 'name' is NULL it will be ignored.
+ * If 'parsed_num' is -1 it will be ignored.
+ *
+ */
+static Con *get_assigned_output(const char *name, long parsed_num) {
+    Con *output = NULL;
+    struct Workspace_Assignment *assignment;
+    TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
+        if (name && strcmp(assignment->name, name) == 0) {
+            DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output);
+            Output *assigned_by_name = get_output_by_name(assignment->output, true);
+            if (assigned_by_name) {
+                /* When the name matches exactly, skip numbered assignments. */
+                return assigned_by_name->con;
+            }
+        } else if (!output && /* Only keep the first numbered assignment. */
+                   parsed_num != -1 &&
+                   name_is_digits(assignment->name) &&
+                   ws_name_to_number(assignment->name) == parsed_num) {
+            DLOG("Found workspace number assignment to output \"%s\"\n", assignment->output);
+            Output *assigned_by_num = get_output_by_name(assignment->output, true);
+            if (assigned_by_num) {
+                output = assigned_by_num->con;
+            }
+        }
+    }
+
+    return output;
+}
+
+/*
+ * Returns true if the first output assigned to a workspace with the given
+ * workspace assignment is the same as the given output.
+ */
+bool output_triggers_assignment(Output *output, struct Workspace_Assignment *assignment) {
+    Con *assigned = get_assigned_output(assignment->name, -1);
+    return assigned && assigned == output->con;
+}
+
 /*
  * Returns a pointer to the workspace with the given number (starting at 0),
  * creating the workspace if necessary (by allocating the necessary amount of
@@ -46,32 +120,20 @@ static void _workspace_apply_default_orientation(Con *ws) {
  *
  */
 Con *workspace_get(const char *num, bool *created) {
-    Con *output, *workspace = NULL;
-
-    TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
-    GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num));
+    Con *workspace = get_existing_workspace_by_name(num);
 
     if (workspace == NULL) {
         LOG("Creating new workspace \"%s\"\n", num);
-        /* unless an assignment is found, we will create this workspace on the current output */
-        output = con_get_output(focused);
-        /* look for assignments */
-        struct Workspace_Assignment *assignment;
 
         /* We set workspace->num to the number if this workspace’s name begins
          * with a positive number. Otherwise it’s a named ws and num will be
          * -1. */
         long parsed_num = ws_name_to_number(num);
 
-        TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-            if (strcmp(assignment->name, num) == 0) {
-                DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output);
-                GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
-                break;
-            } else if (parsed_num != -1 && name_is_digits(assignment->name) && ws_name_to_number(assignment->name) == parsed_num) {
-                DLOG("Found workspace number assignment to output \"%s\"\n", assignment->output);
-                GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
-            }
+        Con *output = get_assigned_output(num, parsed_num);
+        /* if an assignment is not found, we create this workspace on the current output */
+        if (!output) {
+            output = con_get_output(focused);
         }
 
         Con *content = output_get_content(output);
@@ -172,7 +234,6 @@ void extract_workspace_names_from_bindings(void) {
  */
 Con *create_workspace_on_output(Output *output, Con *content) {
     /* add a workspace to this output */
-    Con *out, *current;
     char *name;
     bool exists = true;
     Con *ws = con_new(NULL, NULL);
@@ -184,24 +245,12 @@ Con *create_workspace_on_output(Output *output, Con *content) {
         /* Ensure that this workspace is not assigned to a different output —
          * otherwise we would create it, then move it over to its output, then
          * find a new workspace, etc… */
-        bool assigned = false;
-        struct Workspace_Assignment *assignment;
-        TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-            if (strcmp(assignment->name, target_name) != 0 ||
-                strcmp(assignment->output, output_primary_name(output)) == 0)
-                continue;
-
-            assigned = true;
-            break;
-        }
-
-        if (assigned)
+        Con *assigned = get_assigned_output(target_name, -1);
+        if (assigned && assigned != output->con) {
             continue;
+        }
 
-        current = NULL;
-        TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
-        GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, target_name));
-        exists = (current != NULL);
+        exists = (get_existing_workspace_by_name(target_name) != NULL);
         if (!exists) {
             ws->name = sstrdup(target_name);
             /* Set ->num to the number of the workspace, if the name actually
@@ -219,16 +268,11 @@ Con *create_workspace_on_output(Output *output, Con *content) {
         int c = 0;
         while (exists) {
             c++;
-
-            ws->num = c;
-
-            current = NULL;
-            TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
-            GREP_FIRST(current, output_get_content(out), child->num == ws->num);
-            exists = (current != NULL);
-
+            Con *assigned = get_assigned_output(NULL, c);
+            exists = (get_existing_workspace_by_num(c) || (assigned && assigned != output->con));
             DLOG("result for ws %d: exists = %d\n", c, exists);
         }
+        ws->num = c;
         sasprintf(&(ws->name), "%d", c);
     }
     con_attach(ws, content, false);
@@ -264,7 +308,7 @@ bool workspace_is_visible(Con *ws) {
  * XXX: we need to clean up all this recursive walking code.
  *
  */
-Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
+static Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
     Con *current;
 
     TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
@@ -340,7 +384,7 @@ static void workspace_reassign_sticky(Con *con) {
 
 /*
  * Callback to reset the urgent flag of the given con to false. May be started by
- * _workspace_show to avoid urgency hints being lost by switching to a workspace
+ * workspace_show to avoid urgency hints being lost by switching to a workspace
  * focusing the con.
  *
  */
@@ -360,9 +404,12 @@ static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents
     }
 }
 
-static void _workspace_show(Con *workspace) {
+/*
+ * Switches to the given workspace
+ *
+ */
+void workspace_show(Con *workspace) {
     Con *current, *old = NULL;
-    Con *old_focus = focused;
 
     /* safe-guard against showing i3-internal workspaces like __i3_scratch */
     if (con_is_internal(workspace))
@@ -385,6 +432,13 @@ static void _workspace_show(Con *workspace) {
         return;
     }
 
+    /* Used to correctly update focus when pushing sticky windows. Holds the
+     * previously focused container in the same output as workspace. For
+     * example, if a sticky window is focused and then we switch focus to a
+     * workspace in another output and then switch to a third workspace in the
+     * first output, the sticky window needs to be refocused. */
+    Con *old_focus = old ? con_descend_focused(old) : NULL;
+
     /* Remember currently focused workspace for switching back to it later with
      * the 'workspace back_and_forth' command.
      * NOTE: We have to duplicate the name as the original will be freed when
@@ -393,10 +447,8 @@ static void _workspace_show(Con *workspace) {
      * focused) are skipped, see bug #868. */
     if (current && !con_is_internal(current)) {
         FREE(previous_workspace_name);
-        if (current) {
-            previous_workspace_name = sstrdup(current->name);
-            DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name);
-        }
+        previous_workspace_name = sstrdup(current->name);
+        DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name);
     }
 
     workspace_reassign_sticky(workspace);
@@ -450,7 +502,7 @@ static void _workspace_show(Con *workspace) {
         if (!workspace_is_visible(old)) {
             LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
             yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL);
-            tree_close_internal(old, DONT_KILL_WINDOW, false, false);
+            tree_close_internal(old, DONT_KILL_WINDOW, false);
 
             const unsigned char *payload;
             ylength length;
@@ -487,14 +539,6 @@ static void _workspace_show(Con *workspace) {
     output_push_sticky_windows(old_focus);
 }
 
-/*
- * Switches to the given workspace
- *
- */
-void workspace_show(Con *workspace) {
-    _workspace_show(workspace);
-}
-
 /*
  * Looks up the workspace by name and switches to it.
  *
@@ -502,7 +546,7 @@ void workspace_show(Con *workspace) {
 void workspace_show_by_name(const char *num) {
     Con *workspace;
     workspace = workspace_get(num, NULL);
-    _workspace_show(workspace);
+    workspace_show(workspace);
 }
 
 /*
@@ -876,7 +920,7 @@ Con *workspace_attach_to(Con *ws) {
     return new;
 }
 
-/**
+/*
  * Creates a new container and re-parents all of children from the given
  * workspace into it.
  *
@@ -911,12 +955,12 @@ Con *workspace_encapsulate(Con *ws) {
     return new;
 }
 
-/**
+/*
  * Move the given workspace to the specified output.
  * This returns true if and only if moving the workspace was successful.
  */
-bool workspace_move_to_output(Con *ws, const char *name) {
-    LOG("Trying to move workspace %p / %s to output \"%s\".\n", ws, ws->name, name);
+bool workspace_move_to_output(Con *ws, Output *output) {
+    LOG("Trying to move workspace %p / %s to output %p / \"%s\".\n", ws, ws->name, output, output_primary_name(output));
 
     Output *current_output = get_output_for_con(ws);
     if (current_output == NULL) {
@@ -924,12 +968,6 @@ bool workspace_move_to_output(Con *ws, const char *name) {
         return false;
     }
 
-    Output *output = get_output_from_string(current_output, name);
-    if (!output) {
-        ELOG("Could not get output from string \"%s\"\n", name);
-        return false;
-    }
-
     Con *content = output_get_content(output->con);
     LOG("got output %p with content %p\n", output, content);
 
@@ -944,16 +982,13 @@ bool workspace_move_to_output(Con *ws, const char *name) {
         bool used_assignment = false;
         struct Workspace_Assignment *assignment;
         TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-            if (assignment->output == NULL || strcmp(assignment->output, output_primary_name(current_output)) != 0)
+            if (!output_triggers_assignment(current_output, assignment)) {
                 continue;
-
+            }
             /* check if this workspace is already attached to the tree */
-            Con *workspace = NULL, *out;
-            TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
-            GREP_FIRST(workspace, output_get_content(out),
-                       !strcasecmp(child->name, assignment->name));
-            if (workspace != NULL)
+            if (get_existing_workspace_by_name(assignment->name) != NULL) {
                 continue;
+            }
 
             /* so create the workspace referenced to by this assignment */
             LOG("Creating workspace from assignment %s.\n", assignment->name);
diff --git a/src/x.c b/src/x.c
index 7829079bb0363f8514e5b1abe6513434c0db809a..5b54d1457de1829590e8a16f16db8a7dd822aeac 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -42,7 +42,7 @@ typedef struct con_state {
     bool child_mapped;
     bool is_hidden;
 
-    /** The con for which this state is. */
+    /* The con for which this state is. */
     Con *con;
 
     /* For reparenting, we have a flag (need_reparent) and the X ID of the old
@@ -99,6 +99,27 @@ static con_state *state_for_frame(xcb_window_t window) {
     return NULL;
 }
 
+/*
+ * Changes the atoms on the root window and the windows themselves to properly
+ * reflect the current focus for ewmh compliance.
+ *
+ */
+static void change_ewmh_focus(xcb_window_t new_focus, xcb_window_t old_focus) {
+    if (new_focus == old_focus) {
+        return;
+    }
+
+    ewmh_update_active_window(new_focus);
+
+    if (new_focus != XCB_WINDOW_NONE) {
+        ewmh_update_focused(new_focus, true);
+    }
+
+    if (old_focus != XCB_WINDOW_NONE) {
+        ewmh_update_focused(old_focus, false);
+    }
+}
+
 /*
  * Initializes the X11 part for the given container. Called exactly once for
  * every container from con_new().
@@ -232,11 +253,7 @@ void x_move_win(Con *src, Con *dest) {
     }
 }
 
-/*
- * Kills the window decoration associated with the given container.
- *
- */
-void x_con_kill(Con *con) {
+static void _x_con_kill(Con *con) {
     con_state *state;
 
     if (con->colormap != XCB_NONE) {
@@ -245,7 +262,6 @@ void x_con_kill(Con *con) {
 
     draw_util_surface_free(conn, &(con->frame));
     draw_util_surface_free(conn, &(con->frame_buffer));
-    xcb_destroy_window(conn, con->frame.id);
     xcb_free_pixmap(conn, con->frame_buffer.id);
     state = state_for_frame(con->frame.id);
     CIRCLEQ_REMOVE(&state_head, state, state);
@@ -258,6 +274,24 @@ void x_con_kill(Con *con) {
     focused_id = last_focused = XCB_NONE;
 }
 
+/*
+ * Kills the window decoration associated with the given container.
+ *
+ */
+void x_con_kill(Con *con) {
+    _x_con_kill(con);
+    xcb_destroy_window(conn, con->frame.id);
+}
+
+/*
+ * Completely reinitializes the container's frame, without destroying the old window.
+ *
+ */
+void x_con_reframe(Con *con) {
+    _x_con_kill(con);
+    x_con_init(con);
+}
+
 /*
  * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
  *
@@ -474,14 +508,12 @@ void x_draw_decoration(Con *con) {
     /* 3: draw a rectangle in border color around the client */
     if (p->border_style != BS_NONE && p->con_is_leaf) {
         /* We might hide some borders adjacent to the screen-edge */
-        adjacent_t borders_to_hide = ADJ_NONE;
-        borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
-
+        adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
         Rect br = con_border_style_rect(con);
 
         /* These rectangles represent the border around the child window
          * (left, bottom and right part). We don’t just fill the whole
-         * rectangle because some childs are not freely resizable and we want
+         * rectangle because some children are not freely resizable and we want
          * their background color to "shine through". */
         if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) {
             draw_util_rectangle(&(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height);
@@ -572,6 +604,8 @@ void x_draw_decoration(Con *con) {
         goto after_title;
     }
 
+    const int title_padding = logical_px(2);
+    const int deco_width = (int)con->deco_rect.width;
     int mark_width = 0;
     if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
         char *formatted_mark = sstrdup("");
@@ -593,12 +627,17 @@ void x_draw_decoration(Con *con) {
             i3String *mark = i3string_from_utf8(formatted_mark);
             mark_width = predict_text_width(mark);
 
+            int mark_offset_x = (config.title_align == ALIGN_RIGHT)
+                                    ? title_padding
+                                    : deco_width - mark_width - title_padding;
+
             draw_util_text(mark, &(parent->frame_buffer),
                            p->color->text, p->color->background,
-                           con->deco_rect.x + con->deco_rect.width - mark_width - logical_px(2),
+                           con->deco_rect.x + mark_offset_x,
                            con->deco_rect.y + text_offset_y, mark_width);
-
             I3STRING_FREE(mark);
+
+            mark_width += title_padding;
         }
 
         FREE(formatted_mark);
@@ -609,11 +648,33 @@ void x_draw_decoration(Con *con) {
         goto copy_pixmaps;
     }
 
+    int title_offset_x;
+    switch (config.title_align) {
+        case ALIGN_LEFT:
+            /* (pad)[text    ](pad)[mark + its pad) */
+            title_offset_x = title_padding;
+            break;
+        case ALIGN_CENTER:
+            /* (pad)[  text  ](pad)[mark + its pad)
+             * To center the text inside its allocated space, the surface
+             * between the brackets, we use the formula
+             * (surface_width - predict_text_width) / 2
+             * where surface_width = deco_width - 2 * pad - mark_width
+             * so, offset = pad + (surface_width - predict_text_width) / 2 =
+             * = … = (deco_width - mark_width - predict_text_width) / 2 */
+            title_offset_x = max(title_padding, (deco_width - mark_width - predict_text_width(title)) / 2);
+            break;
+        case ALIGN_RIGHT:
+            /* [mark + its pad](pad)[    text](pad) */
+            title_offset_x = max(title_padding + mark_width, deco_width - title_padding - predict_text_width(title));
+            break;
+    }
+
     draw_util_text(title, &(parent->frame_buffer),
                    p->color->text, p->color->background,
-                   con->deco_rect.x + logical_px(2),
+                   con->deco_rect.x + title_offset_x,
                    con->deco_rect.y + text_offset_y,
-                   con->deco_rect.width - mark_width - 2 * logical_px(2));
+                   deco_width - mark_width - 2 * title_padding);
 
     if (con->title_format != NULL) {
         I3STRING_FREE(title);
@@ -766,11 +827,8 @@ void x_push_node(Con *con) {
          * background and only afterwards change the window size. This reduces
          * flickering. */
 
-        /* As the pixmap only depends on the size and not on the position, it
-         * is enough to check if width/height have changed. Also, we don’t
-         * create a pixmap at all when the window is actually not visible
-         * (height == 0) or when it is not needed. */
-        bool has_rect_changed = (state->rect.width != rect.width || state->rect.height != rect.height);
+        bool has_rect_changed = (state->rect.x != rect.x || state->rect.y != rect.y ||
+                                 state->rect.width != rect.width || state->rect.height != rect.height);
 
         /* Check if the container has an unneeded pixmap left over from
          * previously having a border or titlebar. */
@@ -1120,7 +1178,7 @@ void x_push_changes(Con *con) {
                      to_focus, focused, focused->name);
                 send_take_focus(to_focus, last_timestamp);
 
-                ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE));
+                change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused);
 
                 if (to_focus != last_focused && is_con_attached(focused))
                     ipc_send_window_event("focus", focused);
@@ -1139,7 +1197,7 @@ void x_push_changes(Con *con) {
                     xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
                 }
 
-                ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE));
+                change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused);
 
                 if (to_focus != XCB_NONE && to_focus != last_focused && focused->window != NULL && is_con_attached(focused))
                     ipc_send_window_event("focus", focused);
@@ -1154,7 +1212,8 @@ void x_push_changes(Con *con) {
          * root window in order to avoid an X11 fallback mechanism causing a ghosting effect (see #1378). */
         DLOG("Still no window focused, better set focus to the EWMH support window (%d)\n", ewmh_window);
         xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, ewmh_window, last_timestamp);
-        ewmh_update_active_window(XCB_WINDOW_NONE);
+        change_ewmh_focus(XCB_WINDOW_NONE, last_focused);
+
         focused_id = ewmh_window;
     }
 
@@ -1226,7 +1285,7 @@ void x_set_name(Con *con, const char *name) {
  * Set up the I3_SHMLOG_PATH atom.
  *
  */
-void update_shmlog_atom() {
+void update_shmlog_atom(void) {
     if (*shmlogname == '\0') {
         xcb_delete_property(conn, root, A_I3_SHMLOG_PATH);
     } else {
index 5506d67e78b1bf748edd692a9a2f7e7f5450a4fe..520b021315a61013a071a5d5a34197d575d37054 100644 (file)
@@ -34,7 +34,7 @@ static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents);
 
 static char *sun_path = NULL;
 
-void cleanup_socket(void) {
+static void cleanup_socket(void) {
     if (sun_path != NULL) {
         unlink(sun_path);
         free(sun_path);
index e754c0c17f6bf3b8b5b92e72233ebbe05ab8198a..5734eca70206cc15196f3c0ac78c9075186166aa 100644 (file)
@@ -26,6 +26,7 @@ use Data::Dumper ();
 use Exporter ();
 our @EXPORT = qw(
     get_workspace_names
+    get_output_for_workspace
     get_unused_workspace
     fresh_workspace
     get_ws_content
@@ -51,6 +52,7 @@ our @EXPORT = qw(
     kill_all_windows
     events_for
     listen_for_binding
+    is_net_wm_state_focused
 );
 
 =head1 NAME
@@ -401,6 +403,29 @@ sub get_workspace_names {
     [ map { $_->{name} } @cons ]
 }
 
+=head2 get_output_for_workspace()
+
+Returns the name of the output on which this workspace resides
+
+  cmd 'focus output fake-1';
+  cmd 'workspace 1';
+  is(get_output_for_workspace('1', 'fake-0', 'Workspace 1 in output fake-0');
+
+=cut
+sub get_output_for_workspace {
+    my $ws_name = shift @_;
+    my $i3 = i3(get_socket_path());
+    my $tree = $i3->get_tree->recv;
+    my @outputs = @{$tree->{nodes}};
+
+    foreach (grep { not $_->{name} =~ /^__/ } @outputs) {
+        my $output = $_->{name};
+        foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) {
+            return $output if $_->{nodes}[0]->{name} =~ $ws_name;
+        }
+    }
+}
+
 =head2 get_unused_workspace
 
 Returns a workspace name which has not yet been used. See also
@@ -1026,6 +1051,40 @@ sub listen_for_binding {
     return $command;
 }
 
+=head2 is_net_wm_state_focused
+
+Returns true if the given window has the _NET_WM_STATE_FOCUSED atom.
+
+    ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
+
+=cut
+sub is_net_wm_state_focused {
+    my ($window) = @_;
+
+    sync_with_i3;
+    my $atom = $x->atom(name => '_NET_WM_STATE_FOCUSED');
+    my $cookie = $x->get_property(
+        0,
+        $window->{id},
+        $x->atom(name => '_NET_WM_STATE')->id,
+        GET_PROPERTY_TYPE_ANY,
+        0,
+        4096
+    );
+
+    my $reply = $x->get_property_reply($cookie->{sequence});
+    my $len = $reply->{length};
+    return 0 if $len == 0;
+
+    my @atoms = unpack("L$len", $reply->{value});
+    for (my $i = 0; $i < $len; $i++) {
+        return 1 if $atoms[$i] == $atom->id;
+    }
+
+    return 0;
+}
+
+
 =head1 AUTHOR
 
 Michael Stapelberg <michael@i3wm.org>
index 1e2644ad96ed334437042825293b50df54660753..0de90193c60cb0ae1935a342e940b88bc172d456 100644 (file)
@@ -332,6 +332,34 @@ for ($type = 1; $type <= 2; $type++) {
     ok(!$source_ws->{urgent}, 'Source workspace is no longer marked urgent');
     is($target_ws->{urgent}, 1, 'Target workspace is now marked urgent');
 
+##############################################################################
+# Test that moving an unfocused container doesn't reset its urgency hint.
+##############################################################################
+    $tmp = fresh_workspace;
+    $win1 = open_window;
+    $win2 = open_window;
+    cmd 'split v';
+    $win3 = open_window;
+    set_urgency($win1, 1, $type);
+    sync_with_i3;
+
+    my $win1_info;
+
+    @content = @{get_ws_content($tmp)};
+    $win1_info = first { $_->{window} == $win1->id } @content;
+    ok($win1_info->{urgent}, 'win1 window is marked urgent');
+
+    cmd '[id="' . $win1->id . '"] move right';
+    cmd '[id="' . $win1->id . '"] move right';
+    @content = @{get_ws_content($tmp)};
+    $win1_info = first { $_->{window} == $win1->id } @content;
+    ok($win1_info->{urgent}, 'win1 window is still marked urgent after moving');
+
+    cmd '[id="' . $win1->id . '"] focus';
+    @content = @{get_ws_content($tmp)};
+    $win1_info = first { $_->{window} == $win1->id } @content;
+    ok(!$win1_info->{urgent}, 'win1 window is not marked urgent after focusing');
+
 ##############################################################################
 
     exit_gracefully($pid);
index 1a4beacc6cc177ef30f1c11b1193467b30e28871..60705f96d4de586998724d1270f77d6c3721e4ca 100644 (file)
@@ -339,4 +339,15 @@ $ws = get_ws($tmp2);
 is_num_children($tmp2, 0, 'no regular nodes on second workspace');
 is(@{$ws->{floating_nodes}}, 1, 'one floating node on second workspace');
 
+###################################################################
+# Check that moving an empty workspace using criteria doesn't
+# create unfocused empty workspace.
+###################################################################
+$tmp2 = get_unused_workspace();
+$tmp = fresh_workspace();
+cmd 'mark a';
+cmd "[con_mark=a] move to workspace $tmp2";
+
+is (get_ws($tmp2), undef, 'No empty workspace created');
+
 done_testing;
index 4557bbda6a9155b91fe423a011590e7e0b180f21..c10771c802fedf8228c98c39cd867ccaf3f4e4e3 100644 (file)
@@ -14,7 +14,7 @@
 #
 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
 #   (unless you are already familiar with Perl)
-# 
+#
 #
 use i3test;
 
index 97c4e4cd3d2429fd0ad1a756c4b30866fe94ce27..168151f4c0191018883f4e741248116614f6efb6 100644 (file)
@@ -105,18 +105,14 @@ cmd 'split v';
 cmd 'layout stacked';
 $second = open_window({ background_color => '#00ff00' });   # window 6
 $third = open_window({ background_color => '#0000ff' }); # window 7
-
 is($x->input_focus, $third->id, 'last container focused');
 
-cmd 'floating enable';
-
 cmd '[id="' . $second->id . '"] focus';
-
-is($x->input_focus, $second->id, 'second con focused');
-
 cmd 'floating enable';
+cmd '[id="' . $third->id . '"] floating enable';
 
 sync_with_i3;
+is($x->input_focus, $second->id, 'second con focused');
 
 # now kill the second one. focus should fall back to the third one, which is
 # also floating
@@ -240,4 +236,184 @@ $ws = get_ws($tmp);
 is($ws->{floating_nodes}->[1]->{nodes}->[0]->{window}, $first->id, 'first on top');
 is($ws->{floating_nodes}->[0]->{nodes}->[0]->{window}, $second->id, 'second behind');
 
+#############################################################################
+# 9: verify that disabling / enabling floating for a window from a different
+# workspace maintains the correct focus order.
+#############################################################################
+
+sub open_window_helper {
+    my $floating = shift if @_;
+    if ($floating){
+        return open_floating_window;
+    }
+    else {
+        return open_window;
+    }
+}
+
+for my $floating (0, 1){
+    $tmp = fresh_workspace;
+    $first = open_window;
+    $second = open_window_helper($floating);
+    is($x->input_focus, $second->id, "second window focused");
+
+    fresh_workspace;
+    cmd "[id=" . $second->id . "] floating toggle";
+    cmd "workspace $tmp";
+    sync_with_i3;
+
+    my $workspace = get_ws($tmp);
+    is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $second->id, 'second window on first workspace, floating') unless $floating;
+    is($workspace->{nodes}->[1]->{window}, $second->id, 'second window on first workspace, right') unless !$floating;
+    is($x->input_focus, $second->id, 'second window still focused');
+}
+
+#############################################################################
+# 10: verify that toggling floating for an unfocused window on another
+# workspace doesn't make it focused.
+#############################################################################
+
+for my $floating (0, 1){
+    $tmp = fresh_workspace;
+    $first = open_window_helper($floating);
+    $second = open_window;
+    is($x->input_focus, $second->id, 'second (tiling) window focused');
+
+    fresh_workspace;
+    cmd "[id=" . $first->id . "] floating toggle";
+    cmd "workspace $tmp";
+    sync_with_i3;
+
+    my $workspace = get_ws($tmp);
+    is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $first->id, 'first window on first workspace, floating') unless $floating;
+    is($workspace->{nodes}->[1]->{window}, $first->id, 'first window on first workspace, right') unless !$floating;
+    is($x->input_focus, $second->id, 'second window still focused');
+}
+
+#############################################################################
+# 11: verify that toggling floating for a focused window on another workspace
+# which has another, unfocused floating window maintains the focus of the
+# first window.
+#############################################################################
+for my $floating (0, 1){
+    $tmp = fresh_workspace;
+    $first = open_window;
+    $second = open_floating_window;
+    is($x->input_focus, $second->id, 'second (floating) window focused');
+    $third = open_window_helper($floating);
+    is($x->input_focus, $third->id, "third (floating = $floating) window focused");
+
+    fresh_workspace;
+    cmd "[id=" . $third->id . "] floating toggle";
+    cmd "workspace $tmp";
+    sync_with_i3;
+
+    my $workspace = get_ws($tmp);
+    is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $third->id, 'third window on first workspace, floating') unless $floating;
+    is($workspace->{nodes}->[1]->{window}, $third->id, 'third window on first workspace, right') unless !$floating;
+    is($x->input_focus, $third->id, 'third window still focused');
+}
+
+#############################################################################
+# 12: verify that toggling floating for an unfocused window on another
+# workspace which has another, focused floating window doesn't change focus.
+#############################################################################
+
+for my $floating (0, 1){
+    $tmp = fresh_workspace;
+    $first = open_window;
+    $second = open_window_helper($floating);
+    is($x->input_focus, $second->id, "second (floating = $floating) window focused");
+    $third = open_floating_window;
+    is($x->input_focus, $third->id, 'third (floating) window focused');
+
+    fresh_workspace;
+    cmd "[id=" . $second->id . "] floating toggle";
+    cmd "workspace $tmp";
+    sync_with_i3;
+
+    my $workspace = get_ws($tmp);
+    is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $second->id, 'second window on first workspace, floating') unless $floating;
+    is($workspace->{nodes}->[1]->{window}, $second->id, 'second window on first workspace, right') unless !$floating;
+    is($x->input_focus, $third->id, 'third window still focused');
+}
+
+#############################################################################
+# 13: For layout [H1 [A V1[ B F ] ] ] verify that toggling F's floating
+# mode maintains its focus.
+#############################################################################
+
+for my $floating (0, 1){
+    $tmp = fresh_workspace;
+    $first = open_window;
+    $second = open_window;
+    cmd "split v";
+    sync_with_i3;
+    is($x->input_focus, $second->id, "second (floating = $floating) window focused");
+    $third = open_window_helper($floating);
+    is($x->input_focus, $third->id, 'third (floating) window focused');
+
+    fresh_workspace;
+    cmd "[id=" . $third->id . "] floating toggle";
+    cmd "workspace $tmp";
+    sync_with_i3;
+
+    my $workspace = get_ws($tmp);
+    is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $third->id, 'third window on first workspace, floating') unless $floating;
+    is($workspace->{nodes}->[1]->{nodes}->[1]->{window}, $third->id, 'third window on first workspace') unless !$floating;
+    is($x->input_focus, $third->id, 'third window still focused');
+}
+
+#############################################################################
+# 14: For layout [H1 [A V1[ H2 [B H2 [ C V2 [ F D ] ] ] ] ] ] verify that
+# toggling F's floating mode maintains its focus.
+#############################################################################
+
+sub kill_and_confirm_focus {
+    my $focus = shift;
+    my $msg = shift;
+    cmd "kill";
+    sync_with_i3;
+    is($x->input_focus, $focus, $msg);
+}
+
+$tmp = fresh_workspace;
+my $A = open_window;
+my $B = open_window;
+cmd "split v";
+my $C = open_window;
+cmd "split h";
+my $F = open_window;
+cmd "split v";
+my $D = open_window;
+is($x->input_focus, $D->id, "D is focused");
+
+sync_with_i3;
+my $workspace = get_ws($tmp);
+is($workspace->{nodes}->[1]->{nodes}->[1]->{nodes}->[1]->{nodes}->[0]->{window}, $F->id, 'F opened in its expected position');
+
+fresh_workspace;
+cmd "[id=" . $F->id . "] floating enable";
+cmd "workspace $tmp";
+sync_with_i3;
+
+$workspace = get_ws($tmp);
+is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $F->id, 'F on first workspace, floating');
+is($workspace->{nodes}->[1]->{nodes}->[1]->{nodes}->[1]->{nodes}->[0]->{window}, $D->id, 'D where F used to be');
+is($x->input_focus, $D->id, 'D still focused');
+
+fresh_workspace;
+cmd "[id=" . $F->id . "] floating disable";
+cmd "workspace $tmp";
+sync_with_i3;
+
+$workspace = get_ws($tmp);
+is($workspace->{nodes}->[1]->{nodes}->[1]->{nodes}->[1]->{nodes}->[1]->{window}, $F->id, 'F where D used to be');
+is($x->input_focus, $D->id, 'D still focused');
+
+kill_and_confirm_focus($F->id, 'F focused after D is killed');
+kill_and_confirm_focus($C->id, 'C focused after F is killed');
+kill_and_confirm_focus($B->id, 'B focused after C is killed');
+kill_and_confirm_focus($A->id, 'A focused after B is killed');
+
 done_testing;
index 0e7fd526b75e9d450a532c92cfad26268d621d3d..5a264169c4367b9471cc34082592a62473a42465 100644 (file)
@@ -141,6 +141,147 @@ cmp_float($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%');
 cmp_float($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%');
 cmp_float($nodes->[3]->{percent}, 0.50, 'fourth window got 50%');
 
+################################################################################
+# Same but using pixels instead of ppt.
+################################################################################
+
+# Use two windows
+$tmp = fresh_workspace;
+
+$left = open_window;
+$right = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+my @widths = ($nodes->[0]->{rect}->{width}, $nodes->[1]->{rect}->{width});
+
+cmd 'resize grow width 10 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, $widths[0] - 10, 'left window is 10px smaller');
+cmp_float($nodes->[1]->{rect}->{width}, $widths[1] + 10, 'right window is 10px larger');
+
+# Now test it with four windows
+$tmp = fresh_workspace;
+
+open_window for (1..4);
+
+($nodes, $focus) = get_ws_content($tmp);
+my $width = $nodes->[0]->{rect}->{width};
+
+cmd 'resize grow width 10 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[3]->{rect}->{width}, $width + 10, 'last window is 10px larger');
+
+################################################################################
+# Same but for height
+################################################################################
+
+# Use two windows
+$tmp = fresh_workspace;
+cmd 'split v';
+
+$left = open_window;
+$right = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+my @heights = ($nodes->[0]->{rect}->{height}, $nodes->[1]->{rect}->{height});
+
+cmd 'resize grow height 10 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{height}, $heights[0] - 10, 'left window is 10px smaller');
+cmp_float($nodes->[1]->{rect}->{height}, $heights[1] + 10, 'right window is 10px larger');
+
+# Now test it with four windows
+$tmp = fresh_workspace;
+cmd 'split v';
+
+open_window for (1..4);
+
+($nodes, $focus) = get_ws_content($tmp);
+my $height = $nodes->[0]->{rect}->{height};
+
+cmd 'resize grow height 10 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[3]->{rect}->{height}, $height + 10, 'last window is 10px larger');
+
+################################################################################
+# Check that we can grow tiled windows by pixels
+################################################################################
+
+$tmp = fresh_workspace;
+
+$left = open_window;
+$right = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, 640, 'left window is 640px');
+cmp_float($nodes->[1]->{rect}->{width}, 640, 'right window is 640px');
+
+cmd 'resize grow left 10px';
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, 630, 'left window is 630px');
+cmp_float($nodes->[1]->{rect}->{width}, 650, 'right window is 650px');
+
+################################################################################
+# Check that we can shrink tiled windows by pixels
+################################################################################
+
+$tmp = fresh_workspace;
+
+$left = open_window;
+$right = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, 640, 'left window is 640px');
+cmp_float($nodes->[1]->{rect}->{width}, 640, 'right window is 640px');
+
+cmd 'resize shrink left 10px';
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, 650, 'left window is 650px');
+cmp_float($nodes->[1]->{rect}->{width}, 630, 'right window is 630px');
+
+
+################################################################################
+# Check that we can shrink vertical tiled windows by pixels
+################################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'split v';
+
+$top = open_window;
+$bottom = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+my @heights = ($nodes->[0]->{rect}->{height}, $nodes->[1]->{rect}->{height});
+
+cmd 'resize grow up 10px';
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{height}, $heights[0] - 10, 'top window is 10px larger');
+cmp_float($nodes->[1]->{rect}->{height}, $heights[1] + 10, 'bottom window is 10px smaller');
+
+################################################################################
+# Check that we can shrink vertical tiled windows by pixels
+################################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'split v';
+
+$top = open_window;
+$bottom = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+my @heights = ($nodes->[0]->{rect}->{height}, $nodes->[1]->{rect}->{height});
+
+cmd 'resize shrink up 10px';
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{height}, $heights[0] + 10, 'top window is 10px smaller');
+cmp_float($nodes->[1]->{rect}->{height}, $heights[1] - 10, 'bottom window is 10px larger');
+
 ################################################################################
 # Check that the resize grow/shrink width/height syntax works if a nested split
 # was set on the container, but no sibling has been opened yet. See #2015.
index 9c396f40fadfc0a438e5a9c13f3978bb77d0bb8f..62711915433ef76185a90d5560d864e392c36c9a 100644 (file)
@@ -29,70 +29,55 @@ EOT
 # | S1 | S2 |
 # +----+----+
 
-my $tmp = fresh_workspace;
-
-################################################################################
-# Open the left window.
-################################################################################
+sub verify_focus {
+    # Report original line
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
 
-my $left = open_window({ background_color => '#ff0000' });
+    my ($expected, $msg) = @_;
+    $expected = $expected->id;
 
-is($x->input_focus, $left->id, 'left window focused');
-
-diag("left = " . $left->id);
+    # Ensure the expected focus if the test fails.
+    cmd "[id=$expected] focus" unless is($x->input_focus, $expected, $msg);
+}
 
 ################################################################################
-# Open the right window.
+# Verify that window opened behind fullscreen window will get focus after the
+# fullscreen window gets moved to a different workspace.
 ################################################################################
 
-my $right = open_window({ background_color => '#00ff00' });
-
-diag("right = " . $right->id);
-
-################################################################################
-# Set the right window to fullscreen.
-################################################################################
+fresh_workspace;
+my $left = open_window;
+verify_focus($left, 'left window focused');
+diag("left = " . $left->id);
 
-cmd 'nop setting fullscreen';
+my $right = open_window;
 cmd 'fullscreen';
+diag("right = " . $right->id);
 
-################################################################################
-# Open a third window. Since we're fullscreen, the window won't be # mapped, so
+# Open a third window. Since we're fullscreen, the window won't be mapped, so
 # don't wait for it to be mapped. Instead, just send the map request and sync
 # with i3 to make sure i3 recognizes it.
-################################################################################
-
-my $third = open_window({
-        background_color => '#0000ff',
-        name => 'Third window',
-        dont_map => 1,
-    });
-
+my $third = open_window({dont_map => 1});
 $third->map;
-
 sync_with_i3;
-
 diag("third = " . $third->id);
 
-################################################################################
 # Move the window to a different workspace, and verify that the third window now
 # gets focused in the current workspace.
-################################################################################
-
 my $tmp2 = get_unused_workspace;
-
 cmd "move workspace $tmp2";
+verify_focus($third, 'third window focused');
 
-is($x->input_focus, $third->id, 'third window focused');
+kill_all_windows;
 
 ################################################################################
 # Ensure that moving a window to a workspace which has a fullscreen window does
 # not focus it (otherwise the user cannot get out of fullscreen mode anymore).
 ################################################################################
 
-$tmp = fresh_workspace;
+my $tmp = fresh_workspace;
 
-my $fullscreen_window = open_window;
+open_window;
 cmd 'fullscreen';
 
 my $nodes = get_ws_content($tmp);
@@ -111,32 +96,18 @@ is(scalar @$nodes, 2, 'precisely two windows');
 is($nodes->[0]->{id}, $old_id, 'id unchanged');
 is($nodes->[0]->{focused}, 1, 'fullscreen window focused');
 
+kill_all_windows;
+
 ################################################################################
 # Ensure it's possible to change focus if it doesn't escape the fullscreen
 # container with fullscreen global. We can't even focus a container in a
 # different workspace.
 ################################################################################
 
-cmd 'fullscreen';
-
-# Focus screen 1
-sync_with_i3;
-$x->root->warp_pointer(1025, 0);
-sync_with_i3;
-
-$tmp = fresh_workspace;
-cmd "workspace $tmp";
+$tmp = fresh_workspace(output => 1);
 my $diff_ws = open_window;
 
-# Focus screen 0
-sync_with_i3;
-$x->root->warp_pointer(0, 0);
-sync_with_i3;
-
-$tmp2 = fresh_workspace;
-cmd "workspace $tmp2";
-cmd 'split h';
-
+$tmp2 = fresh_workspace(output => 0);
 $left = open_window;
 my $right1 = open_window;
 cmd 'split v';
@@ -146,97 +117,99 @@ cmd 'focus parent';
 cmd 'fullscreen global';
 
 cmd '[id="' . $right1->id . '"] focus';
-is($x->input_focus, $right1->id, 'upper right window focused');
+verify_focus($right1, 'upper right window focused');
 
 cmd '[id="' . $right2->id . '"] focus';
-is($x->input_focus, $right2->id, 'bottom right window focused');
+verify_focus($right2, 'bottom right window focused');
 
 cmd 'focus parent';
 isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
 
 cmd 'focus child';
-is($x->input_focus, $right2->id, 'bottom right window focused again');
+verify_focus($right2, 'bottom right window focused again');
 
 cmd 'focus up';
-is($x->input_focus, $right1->id, 'allowed focus up');
+verify_focus($right1, 'allowed focus up');
 
 cmd 'focus down';
-is($x->input_focus, $right2->id, 'allowed focus down');
+verify_focus($right2, 'allowed focus down');
 
 cmd 'focus left';
-is($x->input_focus, $right2->id, 'prevented focus left');
+verify_focus($right2, 'prevented focus left');
 
 cmd 'focus right';
-is($x->input_focus, $right2->id, 'prevented focus right');
+verify_focus($right2, 'prevented focus right');
 
 cmd 'focus down';
-is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
+verify_focus($right1, 'allowed focus wrap (down)');
 
 cmd 'focus up';
-is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
+verify_focus($right2, 'allowed focus wrap (up)');
 
 ################################################################################
+# (depends on previous layout)
 # Same tests when we're in non-global fullscreen mode. It should now be possible
 # to focus a container in a different workspace.
 ################################################################################
 
 cmd 'focus parent';
-cmd 'fullscreen global';
+cmd 'fullscreen disable';
 cmd 'fullscreen';
 
 cmd '[id="' . $right1->id . '"] focus';
-is($x->input_focus, $right1->id, 'upper right window focused');
+verify_focus($right1, 'upper right window focused');
 
 cmd '[id="' . $right2->id . '"] focus';
-is($x->input_focus, $right2->id, 'bottom right window focused');
+verify_focus($right2, 'bottom right window focused');
 
 cmd 'focus parent';
 isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
 
 cmd 'focus child';
-is($x->input_focus, $right2->id, 'bottom right window focused again');
+verify_focus($right2, 'bottom right window focused again');
 
 cmd 'focus up';
-is($x->input_focus, $right1->id, 'allowed focus up');
+verify_focus($right1, 'allowed focus up');
 
 cmd 'focus down';
-is($x->input_focus, $right2->id, 'allowed focus down');
+verify_focus($right2, 'allowed focus down');
 
 cmd 'focus down';
-is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
+verify_focus($right1, 'allowed focus wrap (down)');
 
 cmd 'focus up';
-is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
+verify_focus($right2, 'allowed focus wrap (up)');
 
 cmd 'focus left';
-is($x->input_focus, $right2->id, 'focus left wrapped (no-op)');
+verify_focus($right2, 'focus left wrapped (no-op)');
 
 cmd 'focus right';
-is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws');
+verify_focus($diff_ws, 'allowed focus change to different ws');
 
 cmd 'focus left';
-is($x->input_focus, $right2->id, 'focused back into fullscreen container');
+verify_focus($right2, 'focused back into fullscreen container');
 
 cmd '[id="' . $diff_ws->id . '"] focus';
-is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws by id');
+verify_focus($diff_ws, 'allowed focus change to different ws by id');
 
 ################################################################################
+# (depends on previous layout)
 # More testing of the interaction between wrapping and the fullscreen focus
 # restrictions.
 ################################################################################
 
 cmd '[id="' . $right1->id . '"] focus';
-is($x->input_focus, $right1->id, 'upper right window focused');
+verify_focus($right1, 'upper right window focused');
 
 cmd 'focus parent';
-cmd 'fullscreen';
+cmd 'fullscreen disable';
 cmd 'focus child';
 
 cmd 'split v';
 my $right12 = open_window;
 
 cmd 'focus down';
-is($x->input_focus, $right2->id, 'bottom right window focused');
+verify_focus($right2, 'bottom right window focused');
 
 cmd 'split v';
 my $right22 = open_window;
@@ -246,18 +219,19 @@ cmd 'fullscreen';
 cmd 'focus child';
 
 cmd 'focus down';
-is($x->input_focus, $right2->id, 'focus did not leave parent container (1)');
+verify_focus($right2, 'focus did not leave parent container (1)');
 
 cmd 'focus down';
-is($x->input_focus, $right22->id, 'focus did not leave parent container (2)');
+verify_focus($right22, 'focus did not leave parent container (2)');
 
 cmd 'focus up';
-is($x->input_focus, $right2->id, 'focus did not leave parent container (3)');
+verify_focus($right2, 'focus did not leave parent container (3)');
 
 cmd 'focus up';
-is($x->input_focus, $right22->id, 'focus did not leave parent container (4)');
+verify_focus($right22, 'focus did not leave parent container (4)');
 
 ################################################################################
+# (depends on previous layout)
 # Ensure that moving in a direction doesn't violate the focus restrictions.
 ################################################################################
 
@@ -281,6 +255,7 @@ cmd 'move up';
 verify_move(2, 'prevented move up');
 
 ################################################################################
+# (depends on previous layout)
 # Moving to a different workspace is allowed with per-output fullscreen
 # containers.
 ################################################################################
@@ -296,6 +271,7 @@ cmd "move to workspace prev";
 verify_move(1, 'did not prevent move to workspace by position');
 
 ################################################################################
+# (depends on previous layout)
 # Ensure that is not allowed with global fullscreen containers.
 ################################################################################
 
@@ -304,7 +280,7 @@ cmd "move to workspace $tmp2";
 cmd "workspace $tmp2";
 
 cmd 'focus parent';
-cmd 'fullscreen';
+cmd 'fullscreen disable';
 cmd 'fullscreen global';
 cmd 'focus child';
 
@@ -314,6 +290,8 @@ verify_move(2, 'prevented move to workspace by name');
 cmd "move to workspace prev";
 verify_move(2, 'prevented move to workspace by position');
 
+kill_all_windows;
+
 ################################################################################
 # Ensure it's possible to focus a window using the focus command despite
 # fullscreen window blocking it. Fullscreen window should lose its fullscreen
@@ -321,96 +299,94 @@ verify_move(2, 'prevented move to workspace by position');
 ################################################################################
 
 # first & second tiling, focus using id
-kill_all_windows;
-
 $tmp = fresh_workspace;
 my $first = open_window;
 my $second = open_window;
 cmd 'fullscreen';
-is($x->input_focus, $second->id, 'fullscreen window focused');
+verify_focus($second, 'fullscreen window focused');
 is_num_fullscreen($tmp, 1, '1 fullscreen window');
 
-cmd '[id="'. $first->id .'"] focus';
+cmd '[id="' . $first->id . '"] focus';
 sync_with_i3;
 
-is($x->input_focus, $first->id, 'correctly focused using id');
+verify_focus($first, 'correctly focused using id');
 is_num_fullscreen($tmp, 0, 'no fullscreen windows');
 
-# first floating, second tiling, focus using 'focus floating'
 kill_all_windows;
 
+# first floating, second tiling, focus using 'focus floating'
 $tmp = fresh_workspace;
 $first = open_floating_window;
 $second = open_window;
 cmd 'fullscreen';
-is($x->input_focus, $second->id, 'fullscreen window focused');
+verify_focus($second, 'fullscreen window focused');
 is_num_fullscreen($tmp, 1, '1 fullscreen window');
 
 cmd 'focus floating';
 sync_with_i3;
 
-is($x->input_focus, $first->id, 'correctly focused using focus floating');
+verify_focus($first, 'correctly focused using focus floating');
 is_num_fullscreen($tmp, 0, 'no fullscreen windows');
 
-# first tiling, second floating, focus using 'focus tiling'
 kill_all_windows;
 
+# first tiling, second floating, focus using 'focus tiling'
 $tmp = fresh_workspace;
 $first = open_window;
 $second = open_floating_window;
 cmd 'fullscreen';
-is($x->input_focus, $second->id, 'fullscreen window focused');
+verify_focus($second, 'fullscreen window focused');
 is_num_fullscreen($tmp, 1, '1 fullscreen window');
 
 cmd 'focus tiling';
 sync_with_i3;
 
-is($x->input_focus, $first->id, 'correctly focused using focus tiling');
+verify_focus($first, 'correctly focused using focus tiling');
 is_num_fullscreen($tmp, 0, 'no fullscreen windows');
 
+kill_all_windows;
+
 ################################################################################
 # When the fullscreen window is in an other workspace it should maintain its
 # fullscreen mode since it's not blocking the window to be focused.
 ################################################################################
 
-kill_all_windows;
-
 $tmp = fresh_workspace;
 $first = open_window;
 
 $tmp2 = fresh_workspace;
 $second = open_window;
 cmd 'fullscreen';
-is($x->input_focus, $second->id, 'fullscreen window focused');
+verify_focus($second, 'fullscreen window focused');
 is_num_fullscreen($tmp2, 1, '1 fullscreen window');
 
-cmd '[id="'. $first->id .'"] focus';
+cmd '[id="' . $first->id . '"] focus';
 sync_with_i3;
 
-is($x->input_focus, $first->id, 'correctly focused using focus id');
+verify_focus($first, 'correctly focused using focus id');
 is_num_fullscreen($tmp, 0, 'no fullscreen windows on first workspace');
 is_num_fullscreen($tmp2, 1, 'still one fullscreen window on second workspace');
 
+kill_all_windows;
+
 ################################################################################
 # But a global window in another workspace is blocking the window to be focused.
 # Ensure that it loses its fullscreen mode.
 ################################################################################
 
-kill_all_windows;
-
 $tmp = fresh_workspace;
 $first = open_window;
 
 $tmp2 = fresh_workspace;
 $second = open_window;
 cmd 'fullscreen global';
-is($x->input_focus, $second->id, 'global window focused');
+verify_focus($second, 'global window focused');
 is_num_fullscreen($tmp2, 1, '1 fullscreen window');
 
-cmd '[id="'. $first->id .'"] focus';
+cmd '[id="' . $first->id . '"] focus';
 sync_with_i3;
 
-is($x->input_focus, $first->id, 'correctly focused using focus id');
+verify_focus($first, 'correctly focused using focus id');
 is_num_fullscreen($tmp2, 0, 'no fullscreen windows');
 
 
index 41d400d5cd15cc34f91ff6822273b56f3ea810b0..c4b42964fc3875580b2dc40e6ee691785e13ce0a 100644 (file)
@@ -55,6 +55,7 @@ subtest 'Window without WM_TAKE_FOCUS', sub {
     my $window = open_window;
 
     ok(!recv_take_focus($window), 'did not receive ClientMessage');
+    ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
 
     my ($nodes) = get_ws_content($ws);
     my $con = shift @$nodes;
@@ -91,6 +92,7 @@ subtest 'Window with WM_TAKE_FOCUS and without InputHint', sub {
     $window->map;
 
     ok(!recv_take_focus($window), 'did not receive ClientMessage');
+    ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
 
     my ($nodes) = get_ws_content($ws);
     my $con = shift @$nodes;
@@ -112,6 +114,7 @@ subtest 'Window with WM_TAKE_FOCUS and unspecified InputHint', sub {
     my $window = open_window({ protocols => [ $take_focus ] });
 
     ok(!recv_take_focus($window), 'did not receive ClientMessage');
+    ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
 
     my ($nodes) = get_ws_content($ws);
     my $con = shift @$nodes;
index 51219ba6a63cecd217af61406bb85115b67d9cbb..4146fd7975c1a9e2d393dfd5e00024d4d4208bc2 100644 (file)
@@ -28,7 +28,7 @@ is($nodes[0]->{border}, 'normal', 'border style normal');
 
 cmd 'border 1pixel';
 @nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, 'pixel', 'border style 1pixel');
+is($nodes[0]->{border}, 'pixel', 'border style pixel');
 is($nodes[0]->{current_border_width}, 1, 'border width = 1px');
 
 cmd 'border none';
@@ -48,7 +48,7 @@ is($nodes[0]->{current_border_width}, 0, 'border width = 0px');
 
 cmd 'border toggle';
 @nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, 'pixel', 'border style 1pixel');
+is($nodes[0]->{border}, 'pixel', 'border style pixel');
 is($nodes[0]->{current_border_width}, 1, 'border width = 1px');
 
 cmd 'border toggle';
@@ -56,4 +56,19 @@ cmd 'border toggle';
 is($nodes[0]->{border}, 'normal', 'border style back to normal');
 is($nodes[0]->{current_border_width}, 2, 'border width = 2px');
 
+cmd 'border toggle 10';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'none', 'border style back to none even with width argument');
+is($nodes[0]->{current_border_width}, 0, 'border width = 0px');
+
+cmd 'border toggle 10';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'pixel', 'border style pixel');
+is($nodes[0]->{current_border_width}, 10, 'border width = 10px');
+
+cmd 'border toggle 10';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'normal', 'border style back to normal');
+is($nodes[0]->{current_border_width}, 10, 'border width = 10px');
+
 done_testing;
index 147890e1f9a20742842ebf5d16e4f37ca24315cc..fd3827f740c16de5b6526b237d74466c9c944f32 100644 (file)
@@ -471,4 +471,66 @@ is(scalar @$nodes, 0, 'no window on current ws anymore');
 
 is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed');
 
+################################################################################
+# 15: Verify that 'scratchpad show' returns correct info.
+################################################################################
+
+kill_all_windows;
+
+my $result = cmd 'scratchpad show';
+is($result->[0]->{success}, 0, 'no scratchpad window and call to scratchpad failed');
+
+open_window;
+cmd 'move scratchpad';
+$result = cmd 'scratchpad show';
+is($result->[0]->{success}, 1, 'call to scratchpad succeeded');
+$result = cmd 'scratchpad show';
+is($result->[0]->{success}, 1, 'call to scratchpad succeeded');
+
+kill_all_windows;
+$result = cmd 'scratchpad show';
+is($result->[0]->{success}, 0, 'call to scratchpad failed');
+
+################################################################################
+# 16: Verify that 'scratchpad show' with the criteria returns correct info.
+################################################################################
+
+open_window(name => "scratch-match");
+cmd 'move scratchpad';
+
+$result = cmd '[title="scratch-match"] scratchpad show';
+is($result->[0]->{success}, 1, 'call to scratchpad with the criteria succeeded');
+
+$result = cmd '[title="nomatch"] scratchpad show';
+is($result->[0]->{success}, 0, 'call to scratchpad with non-matching criteria failed');
+
+################################################################################
+# 17: Open a scratchpad window on a workspace, switch to another workspace and
+# call 'scratchpad show' again. Verify that it returns correct info.
+################################################################################
+
+fresh_workspace;
+open_window;
+cmd 'move scratchpad';
+
+fresh_workspace;
+$result = cmd 'scratchpad show';
+is($result->[0]->{success}, 1, 'call to scratchpad in another workspace succeeded');
+
+################################################################################
+# 18: Disabling floating for a scratchpad window should not work.
+################################################################################
+
+kill_all_windows;
+
+$ws = fresh_workspace;
+$window = open_window;
+cmd 'move scratchpad';
+cmd '[id=' . $window->id . '] floating disable';
+
+is(scalar @{get_ws_content($ws)}, 0, 'no window in workspace');
+cmd 'scratchpad show';
+is($x->input_focus, $window->id, 'scratchpad window shown');
+
+
 done_testing;
index 39da19d8aeefd578903f25f86bf4c0046783a175..48f50e8d1266f2dad143e87d8973bbc531d88437 100644 (file)
@@ -86,9 +86,9 @@ is(parser_calls(
    'resize shrink left 25 px or 33 ppt; ' .
    'resize shrink left 25'),
    "cmd_resize(shrink, left, 10, 10)\n" .
-   "cmd_resize(shrink, left, 25, 10)\n" .
+   "cmd_resize(shrink, left, 25, 0)\n" .
    "cmd_resize(shrink, left, 25, 33)\n" .
-   "cmd_resize(shrink, left, 25, 10)",
+   "cmd_resize(shrink, left, 25, 0)",
    'simple resize ok');
 
 is(parser_calls('resize shrink left 25 px or 33 ppt,'),
index 6b082bfdcb1bab61bbe2a108fd8bc9e47ccb3272..ba5f76aa11634fa0b7cec14e01429133c3af8b6d 100644 (file)
@@ -21,6 +21,7 @@
 #
 
 use i3test i3_autostart => 0;
+use X11::XCB qw/PROP_MODE_REPLACE/;
 
 ################################################################################
 # 1: check floating_minimum_size (with non-default limits)
@@ -218,4 +219,83 @@ is($rect->{height}, 70, 'height did not exceed minimum height');
 
 exit_gracefully($pid);
 
+################################################################################
+# 8: check minimum_size and maximum_size set by WM_NORMAL_HINTS
+################################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $min_width = 150;
+my $min_height = 100;
+my $max_width = 250;
+my $max_height = 200;
+
+my sub open_with_max_size {
+    # The type of the WM_NORMAL_HINTS property is WM_SIZE_HINTS
+    # https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.2.3
+    my $XCB_ICCCM_SIZE_HINT_P_MIN_SIZE = 0x32;
+    my $XCB_ICCCM_SIZE_HINT_P_MAX_SIZE = 0x16;
+
+    my $flags = $XCB_ICCCM_SIZE_HINT_P_MIN_SIZE | $XCB_ICCCM_SIZE_HINT_P_MAX_SIZE;
+
+    my $pad = 0x00;
+
+    my $window = open_window(
+        before_map => sub {
+            my ($window) = @_;
+
+            my $atomname = $x->atom(name => 'WM_NORMAL_HINTS');
+            my $atomtype = $x->atom(name => 'WM_SIZE_HINTS');
+            $x->change_property(
+                PROP_MODE_REPLACE,
+                $window->id,
+                $atomname->id,
+                $atomtype->id,
+                32,
+                13,
+                pack('C5N8', $flags, $pad, $pad, $pad, $pad, 0, 0, 0, $min_width, $min_height, $max_width, $max_height),
+            );
+        },
+    );
+
+    return $window;
+}
+
+my sub check_minsize {
+    sync_with_i3;
+    is($window->rect->{width}, $min_width, 'width = min_width');
+    is($window->rect->{height}, $min_height, 'height = min_height');
+}
+
+my sub check_maxsize {
+    sync_with_i3;
+    is($window->rect->{width}, $max_width, 'width = max_width');
+    is($window->rect->{height}, $max_height, 'height = max_height');
+}
+
+$pid = launch_with_config($config);
+
+$window = open_with_max_size;
+cmd 'floating enable';
+cmd 'border none';
+
+cmd "resize set $min_width px $min_height px";
+check_minsize;
+
+# Try to resize below minimum width
+cmd 'resize set ' . ($min_width - 10) . ' px ' . ($min_height - 50) . ' px';
+check_minsize;
+
+cmd "resize set $max_width px $max_height px";
+check_maxsize;
+
+# Try to resize above maximum width
+cmd 'resize set ' . ($max_width + 150) . ' px ' . ($max_height + 500) . ' px';
+check_maxsize;
+
+exit_gracefully($pid);
+
 done_testing;
index 6857b6211c2645ad456afeaf1521e09e063664d9..c6ce22eb9582eeaa35e1402c72d3ad3fb5c76377 100644 (file)
@@ -497,10 +497,12 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
         fake-outputs
         force_display_urgency_hint
         focus_on_window_activation
+        title_align
         show_marks
         workspace
         ipc_socket
         ipc-socket
+        ipc_kill_timeout
         restart_state
         popup_during_fullscreen
         exec_always
@@ -725,7 +727,7 @@ EOT
 $expected = <<'EOT';
 cfg_bar_start()
 cfg_bar_output(LVDS-1)
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'verbose', 'colors', '}'
+ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'colors', '}'
 ERROR: CONFIG: (in file <stdin>)
 ERROR: CONFIG: Line   1: bar {
 ERROR: CONFIG: Line   2:     output LVDS-1
index 54e7ce9cef876b75c5d4dbaed153045fb67be760..312c9660e51a0af91f3950aa5262684247d0317f 100644 (file)
@@ -45,7 +45,7 @@ is($x->input_focus, $win3->{id}, 'it should not disturb focus');
 ###############################################################################
 
 # test all window types
-my %window_types = ( 
+my %window_types = (
     'normal'        => '_NET_WM_WINDOW_TYPE_NORMAL',
     'dialog'        => '_NET_WM_WINDOW_TYPE_DIALOG',
     'utility'       => '_NET_WM_WINDOW_TYPE_UTILITY',
index 57060753978ac6f5c99085109e43b2c00a96df43..efcd5ca400c2a7853b15fabdb029038f4ac5a5f0 100644 (file)
@@ -22,7 +22,7 @@ use List::Util qw(first);
 my ($config, $pid, $first, $second, $ws1, $ws2);
 
 sub send_net_active_window {
-    my ($id) = @_; 
+    my ($id) = @_;
 
     my $msg = pack "CCSLLLLLLL",
         X11::XCB::CLIENT_MESSAGE, # response_type
index 5b507a78d98d2c2f988e5fead0de339773aed7f7..6fd27fe033852a95ce5d57c0510c0cb03b1e7d55 100644 (file)
@@ -68,7 +68,7 @@ is($x->input_focus, $first->id, 'input focus has not changed');
 exit_gracefully($pid);
 
 #####################################################################
-## 3: no_focus doesn't affect the first window opened on a workspace
+# 3: no_focus doesn't affect the first window opened on a workspace
 #####################################################################
 
 $config = <<EOT;
@@ -87,6 +87,13 @@ $first = open_window(wm_class => 'focusme');
 sync_with_i3;
 is($x->input_focus, $first->id, 'input focus has changed');
 
+# Also check that it counts floating windows
+# See issue #3423.
+open_floating_window(wm_class => 'focusme');
+
+sync_with_i3;
+is($x->input_focus, $first->id, 'input focus didn\'t change to floating window');
+
 exit_gracefully($pid);
 
 #####################################################################
index abc2b4c6ce270ca6b4501175146678903b62c0a7..5e806cd4653f37c7003e54b2d5309c99b4749b30 100644 (file)
@@ -31,7 +31,7 @@ my $_NET_WM_STATE_ADD = 1;
 my $_NET_WM_STATE_TOGGLE = 2;
 
 sub set_urgency {
-    my ($win, $urgent_flag) = @_; 
+    my ($win, $urgent_flag) = @_;
     my $msg = pack "CCSLLLLLL",
         X11::XCB::CLIENT_MESSAGE, # response_type
         32, # format
@@ -119,7 +119,7 @@ is($nodes->[0]->{window}, $M->{id}, 'M is left of S');
 is($nodes->[1]->{window}, $S->{id}, 'S is right of M');
 
 ###############################################################################
-# Given 'S' and 'M' on different workspaces and 'S' is urgent, when 'S' is 
+# Given 'S' and 'M' on different workspaces and 'S' is urgent, when 'S' is
 # moved to 'M', then the urgency flag is transferred to the target workspace.
 ###############################################################################
 
@@ -321,6 +321,27 @@ sync_with_i3;
 ($nodes, $focus) = get_ws_content($target_ws);
 is(@{$nodes}, 1, 'tiling container moved to the target workspace');
 
+###############################################################################
+# Given 'S' and 'M' where 'M' is inside a floating container but not its direct
+# child, when 'S' is moved to 'M', i3 should not crash.
+# See issue: #3402
+###############################################################################
+
+$target_ws = fresh_workspace;
+$S = open_window;
+open_window;
+cmd 'splitv';
+$M = open_window;
+cmd 'mark target';
+cmd 'focus parent, floating enable, focus child';
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+does_i3_live;
+
+# Note: this is not actively supported behavior.
+$nodes = get_ws($target_ws)->{floating_nodes}->[0]->{nodes}->[0]->{nodes};
+is(1, (grep { $_->{window} == $S->{id} } @{$nodes}), 'tiling container moved inside floating container');
+
 ###############################################################################
 # Given 'S' and 'M' are the same container, when 'S' is moved to 'M', then
 # the command is ignored.
@@ -336,6 +357,50 @@ sync_with_i3;
 
 does_i3_live;
 
+###############################################################################
+# Given 'S' and 'M' where 'M' is a workspace and 'S' is on a different
+# workspace, then 'S' ends up as a tiling container on 'M'.
+###############################################################################
+
+fresh_workspace;
+$S = open_window;
+$target_ws = fresh_workspace;
+$M = $target_ws;
+cmd 'mark target';
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+sync_with_i3;
+
+does_i3_live;
+
+($nodes, $focus) = get_ws_content($target_ws);
+is(@{$nodes}, 1, 'tiling container moved to the target workspace');
+
+###############################################################################
+# Given 'S' and 'M' where 'S' is a workspace and 'M' is a container on a
+# different workspace, then all the contents of workspace 'S' end up in 'M's
+# workspace.
+###############################################################################
+
+$S = fresh_workspace;
+cmd 'mark S';
+open_window;
+open_window;
+cmd 'splitv';
+open_window;
+open_floating_window;
+$target_ws = fresh_workspace;
+$M = open_window;
+cmd 'mark target';
+
+cmd '[con_mark=S] move container to mark target';
+sync_with_i3;
+
+($nodes, $focus) = get_ws_content($target_ws);
+is(@{$nodes}, 2, 'there is a window and a container with the contents of the original workspace');
+is($nodes->[0]->{window}, $M->{id}, 'M remains the first window');
+is(@{get_ws($target_ws)->{floating_nodes}}, 1, 'target workspace has the floating container');
+
 ###############################################################################
 
 done_testing;
index 12f890f281f92b7fa82c157627a5cb55e66f5340..01e2c30e121fe88404c941bc7aa38ff461d34201 100644 (file)
@@ -58,7 +58,7 @@ exit_gracefully($pid);
 
 ##########################################################################
 # Given a floating container and the cursor is in the left upper edge
-# of the output, when moving the container to the mouse, then the 
+# of the output, when moving the container to the mouse, then the
 # container is moved but adjusted to stay in-bounds.
 ##########################################################################
 
@@ -84,7 +84,7 @@ exit_gracefully($pid);
 
 ##########################################################################
 # Given a floating container and the cursor is in the left right lower
-# edge of the output, when moving the container to the mouse, then the 
+# edge of the output, when moving the container to the mouse, then the
 # container is moved but adjusted to stay in-bounds.
 ##########################################################################
 
index 225394f7b6e2237090bddd5240872fcee6b87000..a880f591b72a9028fbd2e1183640ab2d7870173e 100644 (file)
@@ -105,6 +105,16 @@ is(@{get_ws($ws)->{nodes}}, 2, 'sanity check: workspace contains two windows');
 cmd '[workspace=__focused__] move to workspace trash';
 is(@{get_ws($ws)->{nodes}}, 0, '__focused__ works for workspace');
 
+###############################################################################
+# 6: Test that __focused__ in command criteria when no window is focused does
+# not crash i3.
+# See issue: #3406
+###############################################################################
+
+fresh_workspace;
+cmd '[class=__focused__] focus';
+does_i3_live;
+
 ###############################################################################
 
 done_testing;
index 2c8edf397084f2226a62ce56c4da6a8ffb17e3da..5c746de9d6e25fec42febbc1a4a2522f0c9cd8d6 100644 (file)
@@ -26,76 +26,92 @@ workspace ws output fake-0
 EOT
 
 ################################################################################
-# Check that setting floating windows size works
+# Init variables used for all tests.
 ################################################################################
 
 my $tmp = fresh_workspace;
-
 open_floating_window;
-
 my @content = @{get_ws($tmp)->{floating_nodes}};
 is(@content, 1, 'one floating node on this ws');
-
 my $oldrect = $content[0]->{rect};
 
-cmd 'resize set 100 px 250 px';
+sub do_test {
+    my ($width, $height) = @_;
+
+    cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x unchanged');
+    cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y unchanged');
+
+    @content = @{get_ws($tmp)->{floating_nodes}};
+    if ($width) {
+        cmp_ok($content[0]->{rect}->{width}, '==', $width, "width changed to $width px");
+    } else {
+        cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'width unchanged');
+    }
+    if ($height) {
+        cmp_ok($content[0]->{rect}->{height}, '==', $height, "height changed to $height px");
+    } else {
+        cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height}, 'height unchanged');
+    }
+    $oldrect = $content[0]->{rect};
+}
 
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
-cmp_ok($content[0]->{rect}->{width}, '!=', $oldrect->{width}, 'width changed');
-cmp_ok($content[0]->{rect}->{height}, '!=', $oldrect->{width}, 'height changed');
-cmp_ok($content[0]->{rect}->{width}, '==', 100, 'width changed to 100 px');
-cmp_ok($content[0]->{rect}->{height}, '==', 250, 'height changed to 250 px');
+################################################################################
+# Check that setting floating windows size works
+################################################################################
+
+cmd 'resize set 100 px 250 px';
+do_test(100, 250);
 
 ################################################################################
 # Same but with ppt instead of px
 ################################################################################
 
-kill_all_windows;
-$tmp = 'ws';
-cmd "workspace $tmp";
-open_floating_window;
+cmd 'resize set 33 ppt 20 ppt';
+do_test(int(0.33 * 1333), int(0.2 * 999));
 
-@content = @{get_ws($tmp)->{floating_nodes}};
-is(@content, 1, 'one floating node on this ws');
+################################################################################
+# Mix ppt and px in a single resize set command
+################################################################################
 
-$oldrect = $content[0]->{rect};
+cmd 'resize set 44 ppt 111 px';
+do_test(int(0.44 * 1333), 111);
 
-cmd 'resize set 33 ppt 20 ppt';
-my $expected_width = int(0.33 * 1333);
-my $expected_height = int(0.2 * 999);
+cmd 'resize set 222 px 100 ppt';
+do_test(222, 999);
+
+################################################################################
+# Zero is interpreted as no change.
+# See issue: #3276.
+################################################################################
 
-@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");
+cmd 'resize set 0 px 333 px';
+do_test(0, 333);
+
+cmd 'resize set 333 px 0 ppt';
+do_test(333, 0);
+
+cmd 'resize set 0 px 0 ppt';
+do_test(0, 0);
+
+cmd 'resize set 100 ppt 0 px';
+do_test(1333, 0);
 
 ################################################################################
-# Mix ppt and px in a single resize set command
+# Use 'width' and 'height' keywords.
+# See issue: #3275.
 ################################################################################
 
-cmd 'resize set 44 ppt 111 px';
-$expected_width = int(0.44 * 1333);
-$expected_height = 111;
+cmd 'resize set width 200 px';
+do_test(200, 0);
 
-@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 height 200 px';
+do_test(0, 200);
 
-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");
+cmd 'resize set width 300 px height 300 px';
+do_test(300, 300);
+
+# ppt + keyword used only for height
+cmd 'resize set 100 ppt height 100 px';
+do_test(1333, 100);
 
 done_testing;
index 3790f92ddfee6ac5f98cb5b1f24235e7aaa83ec1..3a3e7c6e8bcf8a87b7686c3c43586bbba2aa839a 100644 (file)
@@ -21,22 +21,22 @@ use X11::XCB qw(:all);
 sub get_wm_state {
     sync_with_i3;
 
-    my ($con) = @_; 
+    my ($con) = @_;
     my $cookie = $x->get_property(
-        0,  
+        0,
         $con->{id},
         $x->atom(name => '_NET_WM_STATE')->id,
         GET_PROPERTY_TYPE_ANY,
-        0,  
+        0,
         4096
-    );  
+    );
 
     my $reply = $x->get_property_reply($cookie->{sequence});
     my $len = $reply->{length};
     return undef if $len == 0;
 
     my @atoms = unpack("L$len", $reply->{value});
-    return \@atoms;
+    return @atoms;
 }
 
 my $wm_state_sticky = $x->atom(name => '_NET_WM_STATE_STICKY')->id;
@@ -51,18 +51,24 @@ my $wm_state_fullscreen = $x->atom(name => '_NET_WM_STATE_FULLSCREEN')->id;
 fresh_workspace;
 my $window = open_window;
 cmd 'sticky enable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set');
+my @state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, 'sanity check: _NET_WM_STATE_STICKY is set');
 
 cmd 'fullscreen enable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky, $wm_state_fullscreen ],
-    'both _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_STICKY are set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set');
+ok((scalar grep { $_ == $wm_state_fullscreen } @state) > 0, '_NET_WM_STATE_FULLSCREEN is set');
 
 cmd 'sticky disable';
-is_deeply(get_wm_state($window), [ $wm_state_fullscreen ], 'only _NET_WM_STATE_FULLSCREEN is set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) == 0, '_NET_WM_STATE_STICKY is not set');
+ok((scalar grep { $_ == $wm_state_fullscreen } @state) > 0, '_NET_WM_STATE_FULLSCREEN is set');
 
 cmd 'sticky enable';
 cmd 'fullscreen disable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICKY is set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set');
+ok((scalar grep { $_ == $wm_state_fullscreen } @state) == 0, '_NET_WM_STATE_FULLSCREEN is not set');
 
 ###############################################################################
 # _NET_WM_STATE is removed when the window is withdrawn.
@@ -71,7 +77,8 @@ is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICK
 fresh_workspace;
 $window = open_window;
 cmd 'sticky enable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set');
 
 $window->unmap;
 wait_for_unmap($window);
index 8bca0d869996216c7aa4293e82703259841408bc..614164abf89cb381b82994c8252fb9f5d5e846c7 100644 (file)
@@ -29,6 +29,20 @@ bindsym --release Control+Print nop Control+Print
 # see issue #2442
 bindsym Mod1+b nop Mod1+b
 bindsym --release Mod1+Shift+b nop Mod1+Shift+b release
+
+bindsym --release Shift+x nop Shift+x
+
+# see issue #2733
+# 133 == Mod4
+bindcode 133 nop 133
+bindcode --release 133 nop 133 release
+
+mode "a_mode" {
+    # 27 == r
+    bindcode 27 --release mode "default"
+}
+bindsym Mod1+r mode "a_mode"
+bindcode 27 nop do not receive
 EOT
 use i3test::XTEST;
 use ExtUtils::PkgConfig;
@@ -85,6 +99,72 @@ is(listen_for_binding(
     'Mod1+Shift+b release',
     'triggered the "Mod1+Shift+b" release keybinding');
 
+is(listen_for_binding(
+        sub {
+            xtest_key_press(50); # Shift
+            xtest_key_press(53); # x
+            xtest_key_release(53); # x
+            xtest_key_release(50); # Shift
+            xtest_sync_with_i3;
+        },
+        ),
+       'Shift+x',
+       'triggered the "Shift+x" keybinding by releasing x first');
+
+is(listen_for_binding(
+        sub {
+            xtest_key_press(50); # Shift
+            xtest_key_press(53); # x
+            xtest_key_release(50); # Shift
+            xtest_key_release(53);  # x
+            xtest_sync_with_i3;
+        },
+        ),
+       'Shift+x',
+       'triggered the "Shift+x" keybinding by releasing Shift first');
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(133);
+        xtest_sync_with_i3;
+    },
+    ),
+    '133',
+    'triggered the 133 keycode press binding');
+
+is(listen_for_binding(
+    sub {
+        xtest_key_release(133);
+        xtest_sync_with_i3;
+    },
+    ),
+    '133 release',
+    'triggered the 133 keycode release binding');
+
+for my $i (1 .. 2) {
+    is(listen_for_binding(
+        sub {
+            xtest_key_press(64); # Alt_l
+            xtest_key_press(27); # r
+            xtest_key_release(27); # r
+            xtest_key_release(64); # Alt_l
+            xtest_sync_with_i3;
+        },
+        ),
+        'mode "a_mode"',
+        "switched to mode \"a_mode\" $i/2");
+
+    is(listen_for_binding(
+        sub {
+            xtest_key_press(27); # r
+            xtest_key_release(27); # r
+            xtest_sync_with_i3;
+        },
+        ),
+        'mode "default"',
+        "switched back to default $i/2");
+}
+
 }
 
 done_testing;
index f3606b4e8106b1c32d34a463cca45bb2d29d5d24..708963dfd649997f68d77f6161ee7e839fb299c0 100644 (file)
@@ -34,7 +34,7 @@ sub move_subtest {
     is($move[0]->{container}->{window}, $window->{id}, 'window id matches');
 }
 
-subtest 'move right', \&move_subtest, 'move right';
+subtest 'move left', \&move_subtest, 'move left';
 subtest 'move to workspace', \&move_subtest, 'move to workspace ws_new';
 
 done_testing;
index f53e4a9341c09e8fd3f65f1c0a7913cbb81080d2..8dfe9aeaeb78f6be2248e78b3da3fd47a9c29638 100644 (file)
 #
 # Tests sticky windows.
 # Ticket: #1455
-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
+
+workspace ws-on-0 output fake-0
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
 
 my ($ws, $tmp, $focused);
 
@@ -25,41 +32,41 @@ my ($ws, $tmp, $focused);
 #    nothing happens.
 ###############################################################################
 fresh_workspace;
-open_window(wm_class => 'findme');
+open_window;
 cmd 'sticky enable';
 $ws = fresh_workspace;
 
 is(@{get_ws($ws)->{nodes}}, 0, 'tiling sticky container did not move');
 is(@{get_ws($ws)->{floating_nodes}}, 0, 'tiling sticky container did not move');
-cmd '[class="findme"] kill';
+kill_all_windows;
 
 ###############################################################################
 # 2: Given a sticky floating container, when the workspace is switched, then
 #    the container moves to the new workspace.
 ###############################################################################
 $ws = fresh_workspace;
-open_floating_window(wm_class => 'findme');
+open_floating_window;
 $focused = get_focused($ws);
 cmd 'sticky enable';
 $ws = fresh_workspace;
 
 is(@{get_ws($ws)->{floating_nodes}}, 1, 'floating sticky container moved to new workspace');
 is(get_focused($ws), $focused, 'sticky container has focus');
-cmd '[class="findme"] kill';
+kill_all_windows;
 
 ###############################################################################
 # 3: Given two sticky floating containers, when the workspace is switched,
 #    then both containers move to the new workspace.
 ###############################################################################
 fresh_workspace;
-open_floating_window(wm_class => 'findme');
+open_floating_window;
 cmd 'sticky enable';
-open_floating_window(wm_class => 'findme');
+open_floating_window;
 cmd 'sticky enable';
 $ws = fresh_workspace;
 
 is(@{get_ws($ws)->{floating_nodes}}, 2, 'multiple sticky windows can be used at the same time');
-cmd '[class="findme"] kill';
+kill_all_windows;
 
 ###############################################################################
 # 4: Given an unfocused sticky floating container and a tiling container on the
@@ -70,13 +77,13 @@ $ws = fresh_workspace;
 open_window;
 $focused = get_focused($ws);
 fresh_workspace;
-open_floating_window(wm_class => 'findme');
+open_floating_window;
 cmd 'sticky enable';
 open_window;
 cmd 'workspace ' . $ws;
 
 is(get_focused($ws), $focused, 'the tiling container has focus');
-cmd '[class="findme"] kill';
+kill_all_windows;
 
 ###############################################################################
 # 5: Given a focused sticky floating container and a tiling container on the
@@ -86,13 +93,13 @@ cmd '[class="findme"] kill';
 $ws = fresh_workspace;
 open_window;
 $tmp = fresh_workspace;
-open_floating_window(wm_class => 'findme');
+open_floating_window;
 $focused = get_focused($tmp);
 cmd 'sticky enable';
 cmd 'workspace ' . $ws;
 
 is(get_focused($ws), $focused, 'the sticky container has focus');
-cmd '[class="findme"] kill';
+kill_all_windows;
 
 ###############################################################################
 # 6: Given a floating container on a non-visible workspace, when the window
@@ -100,13 +107,31 @@ cmd '[class="findme"] kill';
 #    visible workspace.
 ###############################################################################
 fresh_workspace;
-open_floating_window(wm_class => 'findme');
+open_floating_window;
 cmd 'mark sticky';
 $ws = fresh_workspace;
 cmd '[con_mark=sticky] sticky enable';
 
 is(@{get_ws($ws)->{floating_nodes}}, 1, 'the sticky window jumps to the front');
-cmd '[class="findme"] kill';
+kill_all_windows;
+
+###############################################################################
+# 7: Given a sticky floating container and a workspace on another output, when
+#    a new workspace assigned to the first output is focused, then the sticky
+#    container should jump to the new workspace and have input focus correctly.
+###############################################################################
+$ws = fresh_workspace(output => 0);
+open_floating_window;
+cmd 'sticky enabled';
+$focused = get_focused($ws);
+$ws = fresh_workspace(output => 1);
+
+is(@{get_ws($ws)->{floating_nodes}}, 0, 'the sticky window didn\'t jump to a workspace on a different output');
+$ws = 'ws-on-0';
+cmd "workspace $ws";
+is(@{get_ws($ws)->{floating_nodes}}, 1, 'the sticky window moved to new workspace on first output');
+is(get_focused($ws), $focused, 'the sticky window has focus');
+kill_all_windows;
 
 ###############################################################################
 
index 3b61fdab36e8ba35a914f34fdb5a71138f011abc..da2d564d0df069d4d054f85732a7e58ed8c9d604 100644 (file)
@@ -372,26 +372,31 @@ for my $fullscreen (@fullscreen_permutations){
 # +---+---+    Layout: H2[ B, F ]
 # | B | F |    Focus Stacks:
 # +---+---+        H2: F, B
+#
+# See issue: #3259
 ###############################################################################
 
-$ws1 = fresh_workspace;
-$A = open_window(wm_class => 'mark_A');
+for my $fullscreen (0..1){
+    $ws1 = fresh_workspace;
+    $A = open_window(wm_class => 'mark_A');
 
-$ws2 = fresh_workspace;
-$B = open_window(wm_class => 'mark_B');
-open_window;
-$expected_focus = get_focused($ws2);
+    $ws2 = fresh_workspace;
+    $B = open_window(wm_class => 'mark_B');
+    open_window;
+    cmd 'fullscreen enable' if $fullscreen;
+    $expected_focus = get_focused($ws2);
 
-cmd '[con_mark=B] swap container with mark A';
+    cmd '[con_mark=B] swap container with mark A';
 
-$nodes = get_ws_content($ws1);
-is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace');
+    $nodes = get_ws_content($ws1);
+    is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace');
 
-$nodes = get_ws_content($ws2);
-is($nodes->[0]->{window}, $A->{id}, 'A is on the left of the second workspace');
-is(get_focused($ws2), $expected_focus, 'F is still focused');
+    $nodes = get_ws_content($ws2);
+    is($nodes->[0]->{window}, $A->{id}, 'A is on the left of the second workspace');
+    is(get_focused($ws2), $expected_focus, 'F is still focused');
 
-kill_all_windows;
+    kill_all_windows;
+}
 
 ###############################################################################
 # 1. A container cannot be swapped with its parent.
index 0cd6e5c3228f72d405237eb4a55ba6d7351f05c6..55958c3ea3b8bd218f35a2f29e3f3af9130448a4 100644 (file)
@@ -67,11 +67,8 @@ 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_floating = open_floating_window(rect => [ 1, 1, 100, 100 ]);
+$second_floating = open_floating_window(rect => [ 50, 50, 100, 100 ]);
 $first = open_window;
 
 is($x->input_focus, $first->id, 'first (tiling) window focused');
index 41c0bf07009f43ed073a9b466b28526503110e0c..6b16540c85d68e17285969962687ac6ea17e8867 100644 (file)
 #
 # Verify that the corrent focus stack order is preserved after various
 # operations.
-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 1024x768+0+0,1024x768+1024+0
+EOT
 
 sub kill_and_confirm_focus {
     my $focus = shift;
@@ -27,6 +31,7 @@ sub kill_and_confirm_focus {
 }
 
 my @windows;
+my $ws;
 
 sub focus_windows {
     for (my $i = $#windows; $i >= 0; $i--) {
@@ -67,7 +72,6 @@ confirm_focus('tabbed');
 #####################################################################
 
 fresh_workspace;
-
 $windows[3] = open_window;
 $windows[1] = open_window;
 $windows[0] = open_window;
@@ -106,4 +110,135 @@ $windows[0] = open_window;
 cmd 'move left';
 confirm_focus('split-v + move');
 
+#####################################################################
+# Test that moving an unfocused container from another output
+# maintains the correct focus order.
+#####################################################################
+
+fresh_workspace(output => 0);
+$windows[3] = open_window;
+fresh_workspace(output => 1);
+$windows[2] = open_window;
+$windows[1] = open_window;
+$windows[0] = open_window;
+
+cmd '[id=' . $windows[3]->id . '] move right';
+confirm_focus('unfocused move from other output');
+
+#####################################################################
+# Test that moving an unfocused container inside its original parent
+# maintains the correct focus order.
+#####################################################################
+
+fresh_workspace;
+$windows[0] = open_window;
+$windows[1] = open_window;
+cmd 'split v';
+$windows[2] = open_window;
+$windows[3] = open_window;
+focus_windows;
+
+cmd '[id=' . $windows[2]->id . '] move up';
+confirm_focus('split-v + unfocused move inside parent');
+
+######################################################################
+# Test that moving an unfocused container maintains the correct focus
+# order.
+# Layout: H [ A V1 [ B C D ] ]
+######################################################################
+
+fresh_workspace;
+$windows[3] = open_window;
+$windows[2] = open_window;
+cmd 'split v';
+$windows[1] = open_window;
+$windows[0] = open_window;
+
+cmd '[id=' . $windows[3]->id . '] move right';
+confirm_focus('split-v + unfocused move');
+
+######################################################################
+# Test that moving an unfocused container from inside a split
+# container to another workspace doesn't focus sibling.
+######################################################################
+
+$ws = fresh_workspace;
+$windows[0] = open_window;
+$windows[1] = open_window;
+cmd 'split v';
+open_window;
+cmd 'mark a';
+
+cmd '[id=' . $windows[0]->id . '] focus';
+cmd '[con_mark=a] move to workspace ' . get_unused_workspace;
+
+is(@{get_ws_content($ws)}, 2, 'Sanity check: marked window moved');
+confirm_focus('Move unfocused window from split container');
+
+######################################################################
+# Moving containers to another workspace puts them on the top of the
+# focus stack but behind the focused container.
+######################################################################
+
+for my $new_workspace (0 .. 1) {
+    fresh_workspace;
+    $windows[2] = open_window;
+    $windows[1] = open_window;
+    fresh_workspace if $new_workspace;
+    $windows[3] = open_window;
+    $windows[0] = open_window;
+    cmd 'mark target';
+
+    cmd '[id=' . $windows[2]->id . '] move to mark target';
+    cmd '[id=' . $windows[1]->id . '] move to mark target';
+    confirm_focus('\'move to mark\' focus order' . ($new_workspace ? ' when moving containers from other workspace' : ''));
+}
+
+######################################################################
+# Same but with workspace commands.
+######################################################################
+
+fresh_workspace;
+$windows[2] = open_window;
+$windows[1] = open_window;
+$ws = fresh_workspace;
+$windows[3] = open_window;
+$windows[0] = open_window;
+cmd 'mark target';
+
+cmd '[id=' . $windows[2]->id . '] move to workspace ' . $ws;
+cmd '[id=' . $windows[1]->id . '] move to workspace ' . $ws;
+confirm_focus('\'move to workspace\' focus order when moving containers from other workspace');
+
+######################################################################
+# Test focus order with floating and tiling windows.
+# See issue: 1975
+######################################################################
+
+fresh_workspace;
+$windows[2] = open_window;
+$windows[0] = open_window;
+$windows[3] = open_floating_window;
+$windows[1] = open_floating_window;
+focus_windows;
+
+confirm_focus('mix of floating and tiling windows');
+
+######################################################################
+# Same but an unfocused tiling window is killed first.
+######################################################################
+
+fresh_workspace;
+$windows[2] = open_window;
+$windows[0] = open_window;
+$windows[3] = open_floating_window;
+$windows[1] = open_floating_window;
+focus_windows;
+
+cmd '[id=' . $windows[1]->id . '] focus';
+cmd '[id=' . $windows[0]->id . '] kill';
+
+kill_and_confirm_focus($windows[2]->id, 'window 2 focused after tiling killed');
+kill_and_confirm_focus($windows[3]->id, 'window 3 focused after tiling killed');
+
 done_testing;
diff --git a/testcases/t/295-net-wm-state-focused.t b/testcases/t/295-net-wm-state-focused.t
new file mode 100644 (file)
index 0000000..fd7c251
--- /dev/null
@@ -0,0 +1,39 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests for setting and removing the _NET_WM_STATE_FOCUSED atom properly.
+# Ticket: #2273
+use i3test;
+use X11::XCB qw(:all);
+
+my ($windowA, $windowB);
+
+fresh_workspace;
+$windowA = open_window;
+ok(is_net_wm_state_focused($windowA), 'a newly opened window that is focused should have _NET_WM_STATE_FOCUSED set');
+
+$windowB = open_window;
+ok(!is_net_wm_state_focused($windowA), 'when a another window is focused, the old window should not have _NET_WM_STATE_FOCUSED set');
+ok(is_net_wm_state_focused($windowB), 'a newly opened window that is focused should have _NET_WM_STATE_FOCUSED set');
+
+# See issue #3495.
+cmd 'kill';
+ok(is_net_wm_state_focused($windowA), 'when the second window is closed, the first window should have _NET_WM_STATE_FOCUSED set');
+
+fresh_workspace;
+ok(!is_net_wm_state_focused($windowA), 'when focus moves to the ewmh support window, no window should have _NET_WM_STATE_FOCUSED set');
+
+done_testing;
diff --git a/testcases/t/296-regress-focus-behind-fullscreen-floating.t b/testcases/t/296-regress-focus-behind-fullscreen-floating.t
new file mode 100644 (file)
index 0000000..0867f08
--- /dev/null
@@ -0,0 +1,40 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Test that directional focus gives focus to floating fullscreen containers when
+# switching workspaces.
+# Ticket: #3201
+# Bug still in: 4.15-59-gb849fe3e
+use i3test i3_config => <<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
+
+fresh_workspace(output => 0);
+my $ws = fresh_workspace(output => 1);
+open_window;
+open_floating_window;
+cmd 'fullscreen enable';
+my $expected_focus = get_focused($ws);
+
+cmd 'focus left';
+cmd 'focus right';
+
+is (get_focused($ws), $expected_focus, 'floating fullscreen window focused after directional focus');
+
+done_testing;
diff --git a/testcases/t/297-assign-workspace-to-output.t b/testcases/t/297-assign-workspace-to-output.t
new file mode 100644 (file)
index 0000000..a7b75be
--- /dev/null
@@ -0,0 +1,102 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Test assignments of workspaces to outputs.
+use i3test i3_autostart => 0;
+
+################################################################################
+# Test initial workspaces.
+################################################################################
+
+my $config = <<EOT;
+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
+
+bindsym Mod1+x workspace bindingname
+
+workspace 9 output doesnotexist
+workspace special output fake-0
+workspace 1 output doesnotexist
+workspace dontusethisname output doesnotexist
+workspace donotoverride output fake-0
+workspace 2 output fake-0
+workspace 3 output fake-0
+EOT
+
+my $pid = launch_with_config($config);
+
+sub check_output {
+    my ($workspace, $output, $msg) = @_;
+    is(get_output_for_workspace($workspace), $output, $msg);
+}
+
+check_output('9', '', 'Numbered workspace with a big number that is assigned to output that does not exist is not used');
+check_output('special', 'fake-0', 'Output gets special workspace because of assignment');
+check_output('bindingname', 'fake-1', 'Bindings give workspace names');
+check_output('1', 'fake-2', 'Numbered workspace that is assigned to output that does not exist is used');
+check_output('2', '', 'Numbered workspace assigned to output with existing workspace is not used');
+check_output('3', '', 'Numbered workspace assigned to output with existing workspace is not used');
+check_output('4', 'fake-3', 'First available number that is not assigned to existing output is used');
+check_output('dontusethisname', '', 'Named workspace that is assigned to output that does not exist is not used');
+check_output('donotoverride', '', 'Named workspace assigned to already occupied output is not used');
+
+exit_gracefully($pid);
+
+################################################################################
+# Test multiple assignments
+################################################################################
+
+sub do_test {
+    my ($workspace, $output, $msg) = @_;
+    cmd 'focus output ' . ($output eq 'fake-3' ? 'fake-0' : 'fake-3');
+
+    cmd "workspace $workspace";
+    check_output($workspace, $output, $msg)
+}
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0P,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
+
+workspace 1 output fake-0
+workspace 1 output fake-1 fake-2
+workspace 2 output fake-3 fake-4 fake-0 fake-1
+workspace 3 output these outputs do not exist but these do: fake-0 fake-3
+workspace 4 output whitespace                    fake-0
+workspace special output doesnotexist1 doesnotexist2 doesnotexist3
+EOT
+
+$pid = launch_with_config($config);
+
+do_test('1', 'fake-0', 'Multiple assignments do not override a single one');
+do_test('2', 'fake-3', 'First output out of multiple assignments is used');
+do_test('3', 'fake-0', 'First existing output is used');
+do_test('4', 'fake-0', 'Excessive whitespace is ok');
+do_test('5', 'fake-1', 'Numbered initialization for fake-1');
+do_test('6', 'fake-2', 'Numbered initialization for fake-2');
+
+cmd 'focus output fake-0, workspace special';
+check_output('special', 'fake-0', 'Workspace with only non-existing assigned outputs opened in current output.');
+
+# Moving assigned workspaces.
+cmd 'workspace 2, move workspace to output left';
+check_output('2', 'fake-2', 'Moved assigned workspace up');
+
+exit_gracefully($pid);
+done_testing;
diff --git a/testcases/t/297-scroll-tabbed.t b/testcases/t/297-scroll-tabbed.t
new file mode 100644 (file)
index 0000000..b535d0c
--- /dev/null
@@ -0,0 +1,84 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests if scrolling the tab bar on a tabbed container works and verifies that
+# only one window is focused as a result.
+# Ticket: #3215 (PR)
+# Bug still in: 4.15-92-g666aa9e0
+use i3test;
+use i3test::XTEST;
+
+sub scroll_down {
+    # button5 = scroll down
+    xtest_button_press(5, 3, 3);
+    xtest_button_release(5, 3, 3);
+    xtest_sync_with_i3;
+}
+
+sub scroll_up {
+    # button4 = scroll up
+    xtest_button_press(4, 3, 3);
+    xtest_button_release(4, 3, 3);
+    xtest_sync_with_i3;
+}
+
+# Decoration of top left window.
+$x->root->warp_pointer(3, 3);
+
+# H [ T [ H [ A B ] C D V [ E F ] ] G ]
+# Inner horizontal split.
+open_window;
+cmd 'layout tabbed';
+cmd 'splith';
+my $first = open_window;
+cmd 'focus parent';
+# Simple tabs.
+open_window;
+my $second_last = open_window;
+# V-Split container
+open_window;
+cmd 'splitv';
+my $last = open_window;
+# Second child of the outer horizontal split, next to the tabbed one.
+my $outside = open_window;
+cmd 'move right, move right';
+
+cmd '[id=' . $first->id . '] focus';
+
+# Scroll from first to last.
+scroll_down;
+scroll_down;
+is($x->input_focus, $second_last->id, 'Sanity check: scrolling');
+scroll_down;
+is($x->input_focus, $last->id, 'Last window focused through scrolling');
+scroll_down;
+is($x->input_focus, $last->id, 'Scrolling again doesn\'t leave the tabbed container and doesn\'t focus the whole sibling');
+
+# Scroll from last to first.
+scroll_up;
+is($x->input_focus, $second_last->id, 'Scrolling up works');
+scroll_up;
+scroll_up;
+is($x->input_focus, $first->id, 'First window focused through scrolling');
+scroll_up;
+is($x->input_focus, $first->id, 'Scrolling again doesn\'t focus the whole sibling');
+
+# Try scrolling with another window focused
+cmd '[id=' . $outside->id . '] focus';
+scroll_up;
+is($x->input_focus, $first->id, 'Scrolling from outside the tabbed container works');
+
+done_testing;
diff --git a/testcases/t/298-ipc-misbehaving-connection.t b/testcases/t/298-ipc-misbehaving-connection.t
new file mode 100644 (file)
index 0000000..d53ee92
--- /dev/null
@@ -0,0 +1,69 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Test that i3 will not hang if a connected client stops reading from its
+# subscription socket and that the client is killed after a delay.
+# Ticket: #2999
+# Bug still in: 4.15-180-g715cea61
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+# Set the timeout to 500ms to reduce the duration of this test.
+ipc_kill_timeout 500
+EOT
+
+# Manually connect to i3 so that we can choose to not read events
+use IO::Socket::UNIX;
+my $sock = IO::Socket::UNIX->new(Peer => get_socket_path());
+my $magic = "i3-ipc";
+my $payload = '["workspace"]';
+my $message = $magic . pack("LL", length($payload), 2) . $payload;
+print $sock $message;
+
+# Constantly switch between 2 workspaces to generate events.
+fresh_workspace;
+open_window;
+fresh_workspace;
+open_window;
+
+eval {
+    local $SIG{ALRM} = sub { die "Timeout\n" };
+    # 500 is an arbitrarily large number to make sure that the socket becomes
+    # non-writeable.
+    for (my $i = 0; $i < 500; $i++) {
+        alarm 1;
+        cmd 'workspace back_and_forth';
+        alarm 0;
+    }
+};
+ok(!$@, 'i3 didn\'t hang');
+
+# Wait for connection timeout
+sleep 1;
+
+use IO::Select;
+my $s = IO::Select->new($sock);
+my $reached_eof = 0;
+while ($s->can_read(0.05)) {
+    if (read($sock, my $buffer, 100) == 0) {
+        $reached_eof = 1;
+        last;
+    }
+}
+ok($reached_eof, 'socket connection closed');
+
+close $sock;
+done_testing;
diff --git a/testcases/t/299-regress-scratchpad-focus.t b/testcases/t/299-regress-scratchpad-focus.t
new file mode 100644 (file)
index 0000000..504ecd3
--- /dev/null
@@ -0,0 +1,33 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Regression test: verify that a scratchpad container that was open in another
+# workspace and is moved to the current workspace after a 'scratchpad show' is
+# focused.
+# Ticket: #3361
+# Bug still in: 4.15-190-g4b3ff9cd
+use i3test;
+
+my $expected_focus = open_window;
+cmd 'move to scratchpad';
+cmd 'scratchpad show';
+my $ws = fresh_workspace;
+open_window;
+cmd 'scratchpad show';
+sync_with_i3;
+is($x->input_focus, $expected_focus->id, 'scratchpad window brought from other workspace is focused');
+
+done_testing;
diff --git a/testcases/t/300-restart-non-utf8.t b/testcases/t/300-restart-non-utf8.t
new file mode 100644 (file)
index 0000000..81b5658
--- /dev/null
@@ -0,0 +1,34 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verify that i3 does not crash when restart is issued while a window with a
+# title that contains non-UTF8 characters is open.
+# Ticket: #3156
+# Bug still in: 4.15-241-g9dc4df81
+use i3test;
+
+my $ws = fresh_workspace;
+open_window(name => "\x{AA} <-- invalid");
+
+cmd 'restart';
+does_i3_live;
+
+# Confirm that the invalid character got replaced with U+FFFD - "REPLACEMENT
+# CHARACTER"
+cmd '[title="^' . "\x{fffd}" . ' <-- invalid$"] fullscreen enable';
+is_num_fullscreen($ws, 1, 'title based criterion works');
+
+done_testing;
index 5bcdb09afe8f59c756286bdd90e370998b46929b..3db8c4f01a005e5bb4da0e175fa51b417b357133 100644 (file)
@@ -97,4 +97,32 @@ is(scalar @{get_ws_content('left-top')}, 1, 'moved some window to left-bottom wo
 $compare_window = shift @{get_ws_content('left-top')};
 is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace');
 
+#####################################################################
+# Moving a fullscreen container should change its output.
+#####################################################################
+
+kill_all_windows;
+
+cmd 'workspace left-top';
+open_window;
+my $fs_window = open_window;
+open_window;
+
+cmd '[id=' . $fs_window->id . '] fullscreen enable, move right';
+is(scalar @{get_ws_content('right-top')}, 1, 'moved fullscreen window to right-top workspace');
+
+#####################################################################
+# Moving a global fullscreen container should not change its output.
+#####################################################################
+
+kill_all_windows;
+
+cmd 'workspace left-top';
+open_window;
+open_window;
+open_window;
+
+cmd 'fullscreen global, move right, fullscreen disable';
+is(scalar @{get_ws_content('right-top')}, 0, 'global fullscreen window didn\'t change workspace with move');
+
 done_testing;
index e9ea76c4f674d46699d66c45a8cb9f515f29e693..c7ab8367c0dd9285110491ba718f4251a6bc6251 100644 (file)
@@ -26,25 +26,12 @@ workspace 1:override output fake-0
 workspace 2 output fake-0
 workspace 1 output fake-1
 workspace 2:override output fake-1
+workspace 3 output fake-0
+workspace 3:override output doesnotexist fake-1
 
 fake-outputs 1024x768+0+0,1024x768+1024+0
 EOT
 
-my $i3 = i3(get_socket_path());
-$i3->connect->recv;
-
-# Returns the name of the output on which this workspace resides
-sub get_output_for_workspace {
-    my $ws_name = shift @_;
-
-    foreach (grep { not $_->{name} =~ /^__/ } @{$i3->get_tree->recv->{nodes}}) {
-        my $output = $_->{name};
-        foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) {
-            return $output if $_->{nodes}[0]->{name} =~ $ws_name;
-        }
-    }
-}
-
 ################################################################################
 # Workspace assignments with bare numbers should be interpreted as `workspace
 # number` config directives. Any workspace beginning with that number should be
@@ -69,4 +56,10 @@ is(get_output_for_workspace('1:override'), 'fake-0',
     'Assignment rules should not be affected by the order assignments are declared')
     or diag 'Since workspace "1:override" is assigned by name to fake-0, it should open on fake-0';
 
+cmd 'focus output fake-1';
+cmd 'workspace "3:override"';
+is(get_output_for_workspace('3:override'), 'fake-1',
+   'Assignment rules should not be affected by multiple output assignments')
+    or diag 'Since workspace "3:override" is assigned by name to fake-1, it should open on fake-1';
+
 done_testing;
index 981471f78f7821eb0f6ef495f3721484f6d656ce..9897e4ee263860be01b52f8f185496282e3228ed 100644 (file)
@@ -28,23 +28,11 @@ workspace 1 output fake-0
 workspace 2 output fake-1
 workspace 3:foo output fake-1
 workspace baz output fake-1
+workspace 5 output left
+workspace 6 output doesnotexist fake-0
+workspace 7 output fake-1 fake-0
 EOT
 
-my $i3 = i3(get_socket_path());
-$i3->connect->recv;
-
-# Returns the name of the output on which this workspace resides
-sub get_output_for_workspace {
-    my $ws_name = shift @_;
-
-    foreach (grep { not $_->{name} =~ /^__/ } @{$i3->get_tree->recv->{nodes}}) {
-        my $output = $_->{name};
-        foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) {
-            return $output if $_->{nodes}[0]->{name} =~ $ws_name;
-        }
-    }
-}
-
 ##########################################################################
 # Renaming the workspace to an unassigned name does not move the workspace
 # (regression test)
@@ -82,4 +70,62 @@ cmd 'rename workspace to baz';
 is(get_output_for_workspace('baz'), 'fake-1',
     'Renaming the workspace to a number and name should move it to the assigned output');
 
+##########################################################################
+# Renaming a workspace so that it is assigned a directional output does
+# not move the workspace or crash
+##########################################################################
+
+cmd 'focus output fake-0';
+cmd 'workspace bar';
+cmd 'rename workspace to 5';
+is(get_output_for_workspace('5'), 'fake-0',
+    'Renaming the workspace to a workspace assigned to a directional output should not move the workspace');
+
+##########################################################################
+# Renaming an unfocused workspace, triggering an assignment to the output
+# which holds the currently focused empty workspace should result in the
+# original workspace replacing the empty one.
+# See issue #3228.
+##########################################################################
+
+cmd 'workspace baz';
+cmd 'rename workspace 5 to 2';
+is(get_output_for_workspace('2'), 'fake-1',
+    'Renaming an unfocused workspace, triggering an assignment to the output which holds the currently focused empty workspace should result in the original workspace replacing the empty one');
+
+##########################################################################
+# Renaming an unfocused empty workspace, triggering an assignment to the
+# output which holds the currently focused non-empty workspace should
+# close the empty workspace and not crash i3.
+# See issue #3248.
+##########################################################################
+
+cmd 'workspace 1';
+cmd 'workspace 2';
+open_window;
+cmd 'rename workspace 1 to baz';
+is(get_output_for_workspace('baz'), '',
+    'Renaming an unfocused empty workspace, triggering an assignment to the output which holds the currently focused non-empty workspace should close the empty workspace and not crash i3');
+kill_all_windows;
+
+##########################################################################
+# Renaming a workspace with multiple assignments, where the first output
+# doesn't exist.
+##########################################################################
+
+cmd 'focus output fake-1';
+cmd 'rename workspace to 6';
+is(get_output_for_workspace('6'), 'fake-0',
+   'Renaming the workspace while first target output doesn\'t exist moves it to the second assigned output');
+
+##########################################################################
+# Renaming a workspace with multiple assignments, where both outputs exist
+# moves it to the first output.
+##########################################################################
+
+cmd 'focus output fake-0';
+cmd 'rename workspace to 7';
+is(get_output_for_workspace('7'), 'fake-1',
+   'Renaming a workspace with multiple assignments, where both outputs exist moves it to the first output.');
+
 done_testing;
index 8755278508e0391563ee8f0ae96ab8db7ff12898..3593ea0b54cc70f72da6dc1b82336cd2dfe219cc 100644 (file)
@@ -100,11 +100,19 @@ sub focus_subtest {
     is_deeply(\@focus, $want, $msg);
 }
 
+sub sync {
+    # Ensure XTEST events were sent to i3, which grabs and hence needs to
+    # forward any events to i3bar:
+    xtest_sync_with_i3;
+    # Ensure any pending i3bar IPC messages were handled by i3:
+    xtest_sync_with($i3bar_window);
+}
+
 subtest 'button 1 moves focus left', \&focus_subtest,
     sub {
        xtest_button_press(1, 3, 3);
        xtest_button_release(1, 3, 3);
-       xtest_sync_with($i3bar_window);
+       sync;
     },
     [ $left->{id} ],
     'button 1 moves focus left';
@@ -113,7 +121,7 @@ subtest 'button 2 moves focus right', \&focus_subtest,
     sub {
        xtest_button_press(2, 3, 3);
        xtest_button_release(2, 3, 3);
-       xtest_sync_with($i3bar_window);
+       sync;
     },
     [ $right->{id} ],
     'button 2 moves focus right';
@@ -122,7 +130,7 @@ subtest 'button 3 moves focus left', \&focus_subtest,
     sub {
        xtest_button_press(3, 3, 3);
        xtest_button_release(3, 3, 3);
-       xtest_sync_with($i3bar_window);
+       sync;
     },
     [ $left->{id} ],
     'button 3 moves focus left';
@@ -131,7 +139,7 @@ subtest 'button 4 moves focus right', \&focus_subtest,
     sub {
        xtest_button_press(4, 3, 3);
        xtest_button_release(4, 3, 3);
-       xtest_sync_with($i3bar_window);
+       sync;
     },
     [ $right->{id} ],
     'button 4 moves focus right';
@@ -140,7 +148,7 @@ subtest 'button 5 moves focus left', \&focus_subtest,
     sub {
        xtest_button_press(5, 3, 3);
        xtest_button_release(5, 3, 3);
-       xtest_sync_with($i3bar_window);
+       sync;
     },
     [ $left->{id} ],
     'button 5 moves focus left';
@@ -152,7 +160,7 @@ my $old_focus = get_focused($ws);
 subtest 'button 6 does not move focus while pressed', \&focus_subtest,
     sub {
         xtest_button_press(6, 3, 3);
-        xtest_sync_with($i3bar_window);
+        sync;
     },
     [],
     'button 6 does not move focus while pressed';
@@ -161,7 +169,7 @@ is(get_focused($ws), $old_focus, 'focus unchanged');
 subtest 'button 6 release moves focus right', \&focus_subtest,
     sub {
         xtest_button_release(6, 3, 3);
-        xtest_sync_with($i3bar_window);
+        sync;
     },
     [ $right->{id} ],
     'button 6 release moves focus right';
@@ -171,7 +179,7 @@ subtest 'button 6 release moves focus right', \&focus_subtest,
 subtest 'button 7 press moves focus left', \&focus_subtest,
     sub {
         xtest_button_press(7, 3, 3);
-        xtest_sync_with($i3bar_window);
+        sync;
     },
     [ $left->{id} ],
     'button 7 press moves focus left';
@@ -179,7 +187,7 @@ subtest 'button 7 press moves focus left', \&focus_subtest,
 subtest 'button 7 release moves focus right', \&focus_subtest,
     sub {
         xtest_button_release(7, 3, 3);
-        xtest_sync_with($i3bar_window);
+        sync;
     },
     [ $right->{id} ],
     'button 7 release moves focus right';
index ae596203e4ddda18f5b3ffc4f26a0cf889bc37ac..f1da29805df7f4cd02cb463fe95a0e7f832654e5 100644 (file)
@@ -31,15 +31,15 @@ use X11::XCB qw(:all);
 sub get_net_wm_desktop {
     sync_with_i3;
 
-    my ($con) = @_; 
+    my ($con) = @_;
     my $cookie = $x->get_property(
-        0,  
+        0,
         $con->{id},
         $x->atom(name => '_NET_WM_DESKTOP')->id,
         $x->atom(name => 'CARDINAL')->id,
-        0,  
+        0,
         1
-    );  
+    );
 
     my $reply = $x->get_property_reply($cookie->{sequence});
     return undef if $reply->{length} != 1;
index 91a9ef86f5cbe4ef2dac25a2720554117ebdaf28..a177ee3cf1e62d36bf469cad04a82208fb318ba9 100644 (file)
@@ -24,8 +24,6 @@ fake-outputs 400x400+0+0,400x400+400+0
 workspace_auto_back_and_forth no
 EOT
 
-my $i3 = i3(get_socket_path());
-
 # Set it up such that workspace 3 is on the left output and
 # workspace 4 is on the right output
 cmd 'focus output fake-0';
@@ -37,10 +35,6 @@ open_window;
 
 cmd 'move workspace to output left';
 
-# ensure that workspace 3 has now vanished
-my $get_ws = $i3->get_workspaces->recv;
-my @ws_names = map { $_->{name} } @$get_ws;
-# TODO get rid of smartmatch
-ok(!('3' ~~ @ws_names), 'workspace 3 has been closed');
+ok(!workspace_exists('3'), 'workspace 3 has been closed');
 
 done_testing;
index 82267baf6c73d8c65c6b6644faef67f41150964c..0298fecd31692b783f23f431145c57479d451455 100644 (file)
@@ -39,11 +39,26 @@ my ($nodes, $focus) = get_ws_content($tmp);
 cmp_float($nodes->[0]->{percent}, 0.25, 'left window got only 25%');
 cmp_float($nodes->[1]->{percent}, 0.75, 'right window got 75%');
 
+# Same but use the 'width' keyword.
+cmd 'resize set width 80 ppt';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.20, 'left window got 20%');
+cmp_float($nodes->[1]->{percent}, 0.80, 'right window got 80%');
+
+# Same but with px.
+cmd 'resize set width 200 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{rect}->{width}, 200, 'right window got 200 px');
+
 ############################################################
 # resize vertically
 ############################################################
 
-my $tmp = fresh_workspace;
+$tmp = fresh_workspace;
 
 cmd 'split v';
 
@@ -56,20 +71,34 @@ is($x->input_focus, $bottom->id, 'Bottom window focused');
 
 cmd 'resize set 0 ppt 75 ppt';
 
-my ($nodes, $focus) = get_ws_content($tmp);
+($nodes, $focus) = get_ws_content($tmp);
 
 cmp_float($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
 cmp_float($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
 
+# Same but use the 'height' keyword.
+cmd 'resize set height 80 ppt';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.20, 'top window got 20%');
+cmp_float($nodes->[1]->{percent}, 0.80, 'bottom window got 80%');
+
+# Same but with px.
+cmd 'resize set height 200 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{rect}->{height}, 200, 'bottom window got 200 px');
 
 ############################################################
 # resize horizontally and vertically
 ############################################################
 
-my $tmp = fresh_workspace;
+$tmp = fresh_workspace;
 
 cmd 'split h';
-my $left = open_window;
+$left = open_window;
 my $top_right = open_window;
 cmd 'split v';
 my $bottom_right = open_window;
@@ -80,23 +109,47 @@ is($x->input_focus, $bottom_right->id, 'Bottom-right window focused');
 
 cmd 'resize set 75 ppt 75 ppt';
 
-my ($nodes, $focus) = get_ws_content($tmp);
+($nodes, $focus) = get_ws_content($tmp);
 
 cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
 cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
 cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.25, 'top-right window got 25%');
 cmp_float($nodes->[1]->{nodes}->[1]->{percent}, 0.75, 'bottom-right window got 75%');
 
+# Same but with px.
+cmd 'resize set 155 px 135 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{width}, 155, 'bottom-right window got 155 px width');
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{height}, 135, 'bottom-right window got 135 px height');
+
+# Without specifying mode
+cmd 'resize set 201 131';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{width}, 201, 'bottom-right window got 201 px width');
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{height}, 131, 'bottom-right window got 131 px height');
+
+# Mix ppt and px
+cmd 'resize set 75 ppt 200 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{height}, 200, 'bottom-right window got 200 px height');
 
 ############################################################
 # resize from inside a tabbed container
 ############################################################
 
-my $tmp = fresh_workspace;
+$tmp = fresh_workspace;
 
 cmd 'split h';
 
-my $left = open_window;
+$left = open_window;
 my $right1 = open_window;
 
 cmd 'split h';
@@ -110,27 +163,33 @@ is($x->input_focus, $right2->id, '2nd right window focused');
 
 cmd 'resize set 75 ppt 0 ppt';
 
-my ($nodes, $focus) = get_ws_content($tmp);
+($nodes, $focus) = get_ws_content($tmp);
 
 cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
 cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
 
+# Same but with px.
+cmd 'resize set 155 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{rect}->{width}, 155, 'right container got 155 px');
 
 ############################################################
 # resize from inside a stacked container
 ############################################################
 
-my $tmp = fresh_workspace;
+$tmp = fresh_workspace;
 
 cmd 'split h';
 
-my $left = open_window;
-my $right1 = open_window;
+$left = open_window;
+$right1 = open_window;
 
 cmd 'split h';
 cmd 'layout stacked';
 
-my $right2 = open_window;
+$right2 = open_window;
 
 diag("left = " . $left->id . ", right1 = " . $right1->id . ", right2 = " . $right2->id);
 
@@ -138,10 +197,16 @@ is($x->input_focus, $right2->id, '2nd right window focused');
 
 cmd 'resize set 75 ppt 0 ppt';
 
-my ($nodes, $focus) = get_ws_content($tmp);
+($nodes, $focus) = get_ws_content($tmp);
 
 cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
 cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
 
+# Same but with px.
+cmd 'resize set 130 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{rect}->{width}, 130, 'right container got 130 px');
 
 done_testing;
index ff406bea2042a3c1833e44e2d9e33aa5fb2c8d27..d232339cd3c1487d508aec24396a41f254d64dfc 100755 (executable)
@@ -3,4 +3,4 @@
 set -e
 set -x
 
-clang-format-3.8 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
+clang-format-4.0 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
index ddb3874ec6765f3a197f2b0bc5f76bd99eb24685..e6ad51f860034ad3e74997a09dc27b95d09fa07d 100644 (file)
@@ -13,12 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
 # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
 
 # Install mk-build-deps (for installing the i3 build dependencies),
-# clang and clang-format-3.8 (for checking formatting and building with clang),
+# clang and clang-format-4.0 (for checking formatting and building with clang),
 # lintian (for checking spelling errors),
 RUN linux32 apt-get update && \
     DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
     dpkg-dev devscripts git equivs \
-    clang clang-format-3.8 \
+    clang clang-format-4.0 \
     lintian && \
     rm -rf /var/lib/apt/lists/*
 
index 1014407a39fcfc8bd0fb61e67594c98166f5a996..82e5ca29b9c09d29b884c41e2377b4feb2cbd3da 100644 (file)
@@ -13,12 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
 # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
 
 # Install mk-build-deps (for installing the i3 build dependencies),
-# clang and clang-format-3.8 (for checking formatting and building with clang),
+# clang and clang-format-4.0 (for checking formatting and building with clang),
 # lintian (for checking spelling errors),
 RUN linux32 apt-get update && \
     DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
     dpkg-dev devscripts git equivs \
-    clang clang-format-3.8 \
+    clang clang-format-4.0 \
     lintian && \
     rm -rf /var/lib/apt/lists/*
 
index 0b4ec206156a2eb2a247f19b005f42a1951f4a2c..05066ec89c325a79756da8d90f1773c86deeaf63 100644 (file)
@@ -13,13 +13,13 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
 # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
 
 # Install mk-build-deps (for installing the i3 build dependencies),
-# clang and clang-format-3.8 (for checking formatting and building with clang),
+# clang and clang-format-4.0 (for checking formatting and building with clang),
 # lintian (for checking spelling errors),
 # test suite dependencies (for running tests)
 RUN apt-get update && \
     DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
     dpkg-dev devscripts git equivs \
-    clang clang-format-3.8 \
+    clang clang-format-4.0 \
     lintian && \
     rm -rf /var/lib/apt/lists/*
 
index 7eafb9fb12dcf6f3750a5012a2af30e8541c0ea9..907002e87be0639ea84877af02d82d1c1c4c5482 100644 (file)
@@ -11,19 +11,20 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
 # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
 
 # Install mk-build-deps (for installing the i3 build dependencies),
-# clang and clang-format-3.8 (for checking formatting and building with clang),
+# clang and clang-format-4.0 (for checking formatting and building with clang),
 # lintian (for checking spelling errors),
 # test suite dependencies (for running tests)
 RUN apt-get update && \
     DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
     dpkg-dev devscripts git equivs \
-    clang clang-format-3.8 \
+    clang clang-format-4.0 \
     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 x11-xserver-utils && \
     rm -rf /var/lib/apt/lists/*
 
 # Install i3 build dependencies.
 COPY debian/control /usr/src/i3-debian-packaging/control
+COPY debian/changelog /usr/src/i3-debian-packaging/changelog
 RUN apt-get update && \
     DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \
     rm -rf /var/lib/apt/lists/*