summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Stapelberg <michael@stapelberg.de>2022-09-21 18:26:55 +0200
committerMichael Stapelberg <michael@stapelberg.de>2022-09-21 18:26:55 +0200
commitc0ef3caec86a0a80a10d641ba04daf217d875b95 (patch)
treea1aacac1afd69f55421c75a7d835176e0693810b
parentd83940a8fc9d9a617257a6c9d0a9f74bbf3bb4ef (diff)
parent2bdcae8149c29e77d0b034f6fbc1be9ca180ab3f (diff)
downloadi3-c0ef3caec86a0a80a10d641ba04daf217d875b95.tar.gz
i3-c0ef3caec86a0a80a10d641ba04daf217d875b95.zip
Merge branch 'next' into stable
-rw-r--r--.github/CONTRIBUTING.md13
-rw-r--r--.github/workflows/main.yml20
-rw-r--r--RELEASE-NOTES-4.20.127
-rw-r--r--RELEASE-NOTES-4.2174
-rw-r--r--debian/changelog8
-rw-r--r--debian/control2
-rwxr-xr-xdebian/rules4
-rw-r--r--docs/ipc6
-rw-r--r--docs/testsuite5
-rw-r--r--docs/userguide71
-rwxr-xr-xgenerate-command-parser.pl11
-rw-r--r--i3-config-wizard/main.c8
-rwxr-xr-xi3-dmenu-desktop3
-rw-r--r--i3-input/keysym.map2
-rwxr-xr-xi3-sensible-pager5
-rwxr-xr-xi3-sensible-terminal2
-rw-r--r--i3bar/include/common.h2
-rw-r--r--i3bar/src/config.c15
-rw-r--r--i3bar/src/xcb.c2
-rw-r--r--include/all.h1
-rw-r--r--include/commands.h2
-rw-r--r--include/commands_parser.h2
-rw-r--r--include/con.h9
-rw-r--r--include/config_parser.h16
-rw-r--r--include/configuration.h2
-rw-r--r--include/data.h11
-rw-r--r--include/display_version.h2
-rw-r--r--include/i3.h1
-rw-r--r--include/libi3.h6
-rw-r--r--include/randr.h6
-rw-r--r--include/tiling_drag.h16
-rw-r--r--include/util.h12
-rw-r--r--include/window.h2
-rw-r--r--include/workspace.h2
-rw-r--r--libi3/create_socket.c13
-rw-r--r--libi3/font.c2
-rw-r--r--libi3/ipc_connect.c24
-rw-r--r--man/i3-input.man2
-rw-r--r--meson.build5
-rw-r--r--parser-specs/commands.spec8
-rw-r--r--parser-specs/config.spec6
-rw-r--r--release-notes/bugfixes/0-example1
-rw-r--r--release-notes/bugfixes/1-replace-socket1
-rw-r--r--release-notes/bugfixes/2-fix-focus-wrap1
-rw-r--r--release-notes/bugfixes/3-transient_for1
-rw-r--r--release-notes/bugfixes/4-failed-workspace-output1
-rw-r--r--release-notes/bugfixes/5-fix-wm-registration1
-rw-r--r--release-notes/bugfixes/6-fix-graphics-artifacts1
-rw-r--r--release-notes/bugfixes/7-fix-segfault1
-rw-r--r--release-notes/bugfixes/7-update-parent-con-title-on-sibling-move1
-rw-r--r--release-notes/bugfixes/8-bar-font1
-rw-r--r--release-notes/bugfixes/8-bar-output-trailing-whitespace1
-rw-r--r--release-notes/bugfixes/8-fix-nested-variables-crash1
-rw-r--r--release-notes/bugfixes/8-mode-default-sigsegv1
-rw-r--r--release-notes/bugfixes/9-bs-normal1
-rw-r--r--release-notes/changes/0-example1
-rw-r--r--release-notes/changes/1-valid-socket1
-rw-r--r--release-notes/changes/2-client.focused_tab_title1
-rw-r--r--release-notes/changes/3-focus-outputs1
-rw-r--r--release-notes/changes/3-tiling-drag1
-rw-r--r--release-notes/changes/4-title_window_icon-toggle1
-rwxr-xr-xrelease-notes/generator.pl96
-rwxr-xr-xrelease.sh3
-rw-r--r--src/click.c34
-rw-r--r--src/commands.c214
-rw-r--r--src/commands_parser.c2
-rw-r--r--src/con.c76
-rw-r--r--src/config.c10
-rw-r--r--src/config_directives.c53
-rw-r--r--src/config_parser.c20
-rw-r--r--src/display_version.c2
-rw-r--r--src/floating.c2
-rw-r--r--src/handlers.c22
-rw-r--r--src/log.c6
-rw-r--r--src/main.c22
-rw-r--r--src/manage.c31
-rw-r--r--src/move.c16
-rw-r--r--src/randr.c26
-rw-r--r--src/regex.c39
-rw-r--r--src/render.c28
-rw-r--r--src/tiling_drag.c385
-rw-r--r--src/util.c32
-rw-r--r--src/window.c13
-rw-r--r--src/workspace.c6
-rw-r--r--src/x.c38
-rw-r--r--testcases/inject_randr1.5.c2
-rw-r--r--testcases/lib/i3test.pm.in44
-rw-r--r--testcases/t/117-workspace.t2
-rw-r--r--testcases/t/171-config-migrate.t2
-rw-r--r--testcases/t/201-config-parser.t13
-rw-r--r--testcases/t/210-mark-unmark.t2
-rw-r--r--testcases/t/219-ipc-window-focus.t8
-rw-r--r--testcases/t/247-config-line-continuation.t2
-rw-r--r--testcases/t/289-ipc-shutdown-event.t6
-rw-r--r--testcases/t/294-focus-order.t2
-rw-r--r--testcases/t/301-shape.t4
-rw-r--r--testcases/t/304-ipc-workspace-init.t2
-rwxr-xr-x[-rw-r--r--]testcases/t/314-window-icon-padding.t30
-rw-r--r--testcases/t/315-long-commands.t46
-rw-r--r--testcases/t/316-drag-container.t321
-rw-r--r--testcases/t/316-transient-for-loop.t42
-rw-r--r--testcases/t/317-bar-config-font-order.t39
-rw-r--r--testcases/t/317-bar-output-trailing-space.t71
-rw-r--r--testcases/t/502-focus-output.t7
-rw-r--r--testcases/t/506-focus-right.t21
-rw-r--r--testcases/t/531-fullscreen-on-given-output.t2
-rw-r--r--testcases/t/543-move-workspace-to-multiple-outputs.t14
-rw-r--r--testcases/t/544-focus-multiple-outputs.t67
-rw-r--r--testcases/t/545-i3-registration.t11
-rw-r--r--testcases/t/546-empty-bindcommand.t31
-rw-r--r--testcases/t/547-explicit-mode-default.t34
-rw-r--r--testcases/t/547-nested-variables.t81
-rwxr-xr-xtravis/check-formatting.sh6
-rwxr-xr-xtravis/check-spelling.pl19
-rwxr-xr-xtravis/cleanup-bintray.pl39
-rw-r--r--travis/travis-base-386.Dockerfile8
-rw-r--r--travis/travis-base-ubuntu-386.Dockerfile8
-rw-r--r--travis/travis-base-ubuntu.Dockerfile8
-rw-r--r--travis/travis-base.Dockerfile8
119 files changed, 2107 insertions, 521 deletions
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index a8aea200..f033a48d 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -41,11 +41,14 @@ Note that bug reports and feature requests for related projects should be filed
* Use `clang-format` to format your code.
* Run the [testsuite](https://i3wm.org/docs/testsuite.html)
* If your changes should be reported on the next release's changelog, also
- update the [RELEASE-notes-next](../RELEASE-notes-next) file in the root
- folder. Example of changes that should be reported are bug fixes present in
- the latest stable version of i3 and new enhancements. Example of changes that
- should not be reported are minor code improvements, documentation, regression
- and fixes for bugs that were introduced in the `next` branch.
+ add a small single-line file starting with a number (see examples) containing
+ a short explanation of your change either in the
+ [changes](../release-notes/changes) or the
+ [bugfixes](../release-notes/bugfixes/) folder. Example of changes that should
+ be reported are bug fixes present in the latest stable version of i3 and new
+ enhancements. Example of changes that should not be reported are minor code
+ improvements, documentation, regression and fixes for bugs that were
+ introduced in the `next` branch.
## Finding something to do
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 655382f5..0bba6a04 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -45,12 +45,6 @@ jobs:
echo "::group::Ubuntu i386"
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_UBUNTU_386 }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU_386 }} travis/travis-base-ubuntu-386.Dockerfile
echo "::endgroup::"
- - name: verify safe wrapper functions are used
- run: |
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-safe-wrappers.sh
- - name: verify code formatting
- run: |
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-formatting.sh
- name: build i3
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common" meson .. -Ddocs=true -Dmans=true -Db_sanitize=address && ninja -v'
@@ -92,3 +86,17 @@ jobs:
- name: push docs to GitHub pages
run: |
./travis/skip-pkg.sh || travis/deploy-github-pages.sh
+ formatting:
+ name: Check formatting
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: check & print release notes
+ run: ./release-notes/generator.pl
+ - name: Install dependencies
+ run: |
+ sudo apt-get install -y clang-format-10
+ - name: Check formatting
+ run: clang-format-10 --dry-run --Werror $(git ls-files '*.c' '*.h')
+ - name: Verify safe wrapper functions are used
+ run: ./travis/check-safe-wrappers.sh
diff --git a/RELEASE-NOTES-4.20.1 b/RELEASE-NOTES-4.20.1
deleted file mode 100644
index 62127460..00000000
--- a/RELEASE-NOTES-4.20.1
+++ /dev/null
@@ -1,27 +0,0 @@
-
- ┌──────────────────────────────┐
- │ Release notes for i3 v4.20.1 │
- └──────────────────────────────┘
-
-This is i3 v4.20.1. This version is considered stable. All users of i3 are
-strongly encouraged to upgrade.
-
- ┌────────────────────────────┐
- │ Bugfixes │
- └────────────────────────────┘
-
- • i3bar: fix crash with multiple monitors
- • xmlto: fix broken .TH line by extending title length
- • i3-msg: fix --raw short form (-r) in manpage
- • libi3: add missing sys/stat.h header
- • use getcwd(NULL, 0) instead of GNU extension get_current_dir_name()
-
- ┌────────────────────────────┐
- │ Thanks! │
- └────────────────────────────┘
-
-Thanks for testing, bugfixes, discussions and everything I forgot go out to:
-
- rvalieris, Jakob Haufe, lycurgus, Baptiste Daroussin
-
--- Michael Stapelberg, 2021-11-03
diff --git a/RELEASE-NOTES-4.21 b/RELEASE-NOTES-4.21
new file mode 100644
index 00000000..9d4c708e
--- /dev/null
+++ b/RELEASE-NOTES-4.21
@@ -0,0 +1,74 @@
+
+ ┌──────────────────────────────┐
+ │ Release notes for i3 v4.21 │
+ └──────────────────────────────┘
+
+This is i3 v4.21. This version is considered stable. All users of i3 are
+strongly encouraged to upgrade.
+
+The biggest change in this release is that you can now drag tiling windows
+with your mouse (floating windows could already be dragged). For more details
+on how to use this feature, please refer to the userguide:
+
+https://i3wm.org/docs/userguide.html#_moving_tiling_containers_with_the_mouse
+
+A big thank you goes out to our core i3 developer Orestis Floros who made this
+feature possible, based on previous work from Michael Forster and Tony Crisci!
+
+ ┌────────────────────────────┐
+ │ Changes in i3 v4.21 │
+ └────────────────────────────┘
+
+ • Allow dragging tiling windows with the mouse
+ • Add client.focused_tab_title color option
+ • Add support for multiple output names in the focus command,
+ allowing users to cycle focus between e.g. VGA1 and LVDS1 but not DVI0.
+ • Add a toggle option to the title_window_icon command
+ • i3 switched from the obsolete PCRE 8.x regular expression matching
+ library to the current PCRE2 10.x version.
+
+ ┌────────────────────────────┐
+ │ Bugfixes │
+ └────────────────────────────┘
+
+ • docs/ipc: document all window_type values
+ • docs/userguide: clarify the difference between the “workspace N” and
+ “workspace number N” commands
+ • i3bar: fix default font not being applied to bars if defined after bar block
+ • i3-dmenu-desktop: add backslashes for the exec command,
+ which fixes opening some .desktop files (e.g. electrum)
+ • i3-sensible-pager: sanitize LESS environment variable to remove -E or -F
+ • testsuite: catch i3 crashes instead of hanging on crash
+ • Fix logging on machines with 256 GB of RAM
+ • Do not replace existing IPC socket on start, to prevent clobbering
+ the IPC socket when running i3 within i3 (e.g. in Xepyhr, for development)
+ • Refuse to start without a valid IPC socket
+ • Fix focus when moving container between outputs with mouse warp and
+ focus_follows_mouse
+ • Fix endless loop with transient_for windows
+ • Fix wrong “failed” IPC reply on move workspace to output
+ • Fix WM registration selection (from WM_S_S<screen> to WM_S<screen>)
+ • avoid graphics artifacts when changing the layout tree by
+ initializing surfaces to all black
+ • update parent split con titles when child container swaps position with
+ another child container
+ • Fix segfault if command in bindsym is empty
+ • Fix segfault with explicit mode "default" key bindings
+ • Fix crash if config contains nested variables.
+ • strip trailing whitespace in bar output names
+ • Fix crash with long commands
+ • Fix changing borders by restoring BS_NORMAL _MOTIF_WM_HINTS correctly
+
+ ┌────────────────────────────┐
+ │ Thanks! │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+André Silva, Anton Älgmyr, Baptiste Daroussin, bodea, Chris Templin, George
+Rodrigues, Gergely Risko, Ingo Bürk, Jakob Haufe, Jay Ta'ala, Jeff Smith, Jonta,
+Josh Soref, Kjetil Torgrim Homme, lycurgus, mariano, Michael Forster, Orestis
+Floros, paperluigis, Peder Stray, rvalieris, sergio, Tony Crisci, takelley1, Uli
+Schlachter, viri, zhiv-git, zhrvn
+
+-- Michael Stapelberg, 2021-10-19
diff --git a/debian/changelog b/debian/changelog
index e39f5add..19d0ae53 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+i3-wm (4.20.1-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org> Wed, 03 Nov 2021 09:22:48 +0100
+
i3-wm (4.20-1) unstable; urgency=medium
* New upstream release.
@@ -621,7 +627,7 @@ i3-wm (3.d-bf1-1) unstable; urgency=low
* Bugfix: Resize client after updating base_width/base_height
* Bugfix: Force render containers after setting the client active
* Bugfix: Fix two problems in resizing floating windows with right mouse
- * Bugfix: Use more precise floating point arithmetics
+ * Bugfix: Use more precise floating point arithmetic
* Bugfix: Correctly place new windows below fullscreen windows
-- Michael Stapelberg <michael@stapelberg.de> Mon, 21 Dec 2009 22:33:02 +0100
diff --git a/debian/control b/debian/control
index 5cd27cd6..6ddc64e6 100644
--- a/debian/control
+++ b/debian/control
@@ -22,7 +22,7 @@ Build-Depends: debhelper (>= 10),
pkg-config,
libev-dev (>= 1:4.04),
libyajl-dev (>= 2.0.4),
- libpcre3-dev (>= 1:8.10),
+ libpcre2-dev,
libstartup-notification0-dev (>= 0.10),
libcairo2-dev (>= 1.14.4),
libpango1.0-dev,
diff --git a/debian/rules b/debian/rules
index 26e303e6..e8323c24 100755
--- a/debian/rules
+++ b/debian/rules
@@ -17,9 +17,5 @@ override_dh_auto_configure:
# Set -Ddocdir; the default is /usr/share/doc/i3
dh_auto_configure -- -Ddocdir=/usr/share/doc/i3-wm -Dmans=true
-override_dh_builddeb:
- # bintray does not support xz currently.
- dh_builddeb -- -Zgzip
-
%:
dh $@ --buildsystem=meson
diff --git a/docs/ipc b/docs/ipc
index 02c6f9a9..64a2af91 100644
--- a/docs/ipc
+++ b/docs/ipc
@@ -423,9 +423,9 @@ window_properties (map)::
following list: *title*, *instance*, *class*, *window_role*, *machine*
and *transient_for*.
window_type (string)::
- The window type (_NET_WM_WINDOW_TYPE). Possible values are undefined, normal,
- dialog, utility, toolbar, splash, menu, dropdown_menu, popup_menu, tooltip and
- notification.
+ The window type (_NET_WM_WINDOW_TYPE). Possible values are `undefined`,
+ unknown, normal, dialog, utility, toolbar, splash, menu, dropdown_menu,
+ popup_menu, tooltip and notification.
urgent (bool)::
Whether this container (window, split container, floating container or
workspace) has the urgency hint set, directly or indirectly. All parent
diff --git a/docs/testsuite b/docs/testsuite
index d681549c..ec87429c 100644
--- a/docs/testsuite
+++ b/docs/testsuite
@@ -4,11 +4,10 @@ Michael Stapelberg <michael@i3wm.org>
September 2012
This document explains how the i3 testsuite works, how to use it and extend it.
-It is targeted at developers who not necessarily have been doing testing before
-or have not been testing in Perl before. In general, the testsuite is not of
+It is targeted at developers who haven't necessarily done testing before,
+or have not used Perl for testing before. In general, the testsuite is not of
interest for end users.
-
== Introduction
The i3 testsuite is a collection of files which contain testcases for various
diff --git a/docs/userguide b/docs/userguide
index f3162264..7b233844 100644
--- a/docs/userguide
+++ b/docs/userguide
@@ -196,6 +196,48 @@ provided by the i3 https://github.com/i3/i3/blob/next/etc/config.keycodes[defaul
Floating windows are always on top of tiling windows.
+=== Moving tiling containers with the mouse
+
+Since i3 4.21, it's possible to drag tiling containers using the mouse. The
+drag can be initiated either by dragging the window's titlebar or by pressing
+the <<floating_modifier>> and dragging the container while holding the
+left-click button.
+
+Once the drag is initiated and the cursor has left the original container, drop
+indicators are created according to the position of the cursor relatively to
+the target container. These indicators help you understand what the resulting
+<<tree>> layout is going to be after you release the mouse button.
+
+The possible drop positions are:
+
+Drop on container::
+ This happens when the mouse is relatively near the center of a container.
+ If the mouse is released, the result is exactly as if you had run the
+ +move container to mark+ command. See <<move_to_mark>>.
+Drop as sibling::
+ This happens when the mouse is relatively near the edge of a container. If
+ the mouse is released, the dragged container will become a sibling of the
+ target container, placed left/right/up/down according to the position of
+ the indicator.
+ This might or might not create a new v-split or h-split according to the
+ previous layout of the target container. For example, if the target
+ container is in an h-split and you drop the dragged container below it, the
+ new layout will have to be a v-split.
+Drop to parent::
+ This happens when the mouse is relatively near the edge of a container (but
+ even closer to the border in comparison to the sibling case above) *and* if
+ that edge is also the parent container's edge. For example, if three
+ containers are in a horizontal layout then edges where this can happen is
+ the left edge of the left container, the right edge of the right container
+ and all bottom and top edges of all three containers.
+ If the mouse is released, the container is first dropped as a sibling to
+ the target container, like in the case above, and then is moved
+ directionally like with the +move left|right|down|up+ command. See
+ <<move_direction>>.
+
+The color of the indicator matches the +client.focused+ setting. See <<client_colors>>.
+
+[[tree]]
== Tree
i3 stores all information about the X11 outputs, workspaces and layout of the
@@ -538,7 +580,7 @@ for the keybinding.
# The middle button over a titlebar kills the window
bindsym --release button2 kill
-# The middle button and a modifer over any part of the window kills the window
+# The middle button and a modifier over any part of the window kills the window
bindsym --whole-window $mod+button2 kill
# The right button toggles floating
@@ -1041,6 +1083,7 @@ workspace 5 output VGA1 LVDS1
workspace "2: vim" output VGA1
---------------------------
+[[client_colors]]
=== Changing colors
You can change all colors which i3 uses to draw the window decorations.
@@ -1057,6 +1100,10 @@ client.focused::
client.focused_inactive::
A client which is the focused one of its container, but it does not have
the focus at the moment.
+client.focused_tab_title::
+ Tab or stack container title that is the parent of the focused container
+ but not directly focused. Defaults to focused_inactive if not specified and
+ does not use the indicator and child_border colors.
client.unfocused::
A client which is not the focused one of its container.
client.urgent::
@@ -1100,7 +1147,7 @@ i3 uses Unix sockets to provide an IPC interface. This allows third-party
programs to get information from i3, such as the current workspaces
(to display a workspace bar), and to control i3.
-The IPC socket is enabled by default and will be created in
+By default, an IPC socket will be created in
+$XDG_RUNTIME_DIR/i3/ipc-socket.%p+ if the directory is available, falling back
to +/tmp/i3-%u.XXXXXX/ipc-socket.%p+, where +%u+ is your UNIX username, +%p+ is
the PID of i3 and XXXXXX is a string of random characters from the portable
@@ -2208,7 +2255,7 @@ output::
focus left|right|down|up
focus parent|child|floating|tiling|mode_toggle
focus next|prev [sibling]
-focus output left|right|up|down|primary|<output>
+focus output left|right|down|up|current|primary|next|<output1> [output2]…
----------------------------------------------
*Examples*:
@@ -2228,6 +2275,9 @@ bindsym $mod+u focus parent
# Focus last floating/tiling container
bindsym $mod+g focus mode_toggle
+# Focus the next output (effectively toggles when you only have two outputs)
+bindsym $mod+x move workspace to output next
+
# Focus the output right to the current one
bindsym $mod+x focus output right
@@ -2236,6 +2286,9 @@ bindsym $mod+x focus output HDMI-2
# Focus the primary output
bindsym $mod+x focus output primary
+
+# Cycle focus between outputs VGA1 and LVDS1 but not DVI0
+bindsym $mod+x move workspace to output VGA1 LVDS1
-------------------------------------------------
Note that you might not have a primary output configured yet. To do so, run:
@@ -2243,6 +2296,7 @@ Note that you might not have a primary output configured yet. To do so, run:
xrandr --output <output> --primary
-------------------------
+[[move_direction]]
=== Moving containers
Use the +move+ command to move a container.
@@ -2477,9 +2531,11 @@ bindsym $mod+2 workspace number "2: mail"
If a workspace does not exist, the command +workspace number "1: mail"+ will
create workspace "1: mail".
-If a workspace with number 1 does already exist, the command will switch to this
+If a workspace with number 1 already exists, the command will switch to this
workspace and ignore the text part. So even when the workspace has been renamed
-to "1: web", the above command will still switch to it.
+"1: web", the above command will still switch to it. The command +workspace 1+
+will however create and move to a new workspace "1" alongside the existing
+"1: mail" workspace.
=== Moving workspaces to a different screen
@@ -2525,6 +2581,7 @@ Note that you might not have a primary output configured yet. To do so, run:
xrandr --output <output> --primary
-------------------------
+[[move_to_mark]]
=== Moving containers/windows to marks
To move a container to another container with a specific mark (see <<vim_like_marks>>),
@@ -2718,8 +2775,8 @@ specific windows or for all windows (using the <<for_window>> directive).
*Syntax*:
-----------------------------
-title_window_icon <yes|no>
-title_window_icon padding <px>
+title_window_icon <yes|no|toggle>
+title_window_icon <padding|toggle> <px>
------------------------------
*Examples*:
diff --git a/generate-command-parser.pl b/generate-command-parser.pl
index cef4eda5..17728736 100755
--- a/generate-command-parser.pl
+++ b/generate-command-parser.pl
@@ -228,8 +228,15 @@ for my $state (@keys) {
($call_identifier) = ($next_state =~ /^call ([0-9]+)$/);
$next_state = '__CALL';
}
- my $identifier = $token->{identifier};
- say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } },|;
+ my $identifier;
+ # Set $identifier to NULL if there is no identifier
+ if ($token->{identifier} eq ""){
+ $identifier = "NULL"
+ }
+ else{
+ $identifier = qq|"$token->{identifier}"|;
+ }
+ say $tokfh qq| { "$token_name", $identifier, $next_state, { $call_identifier } },|;
}
say $tokfh '};';
}
diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c
index 25117f2b..c5ec071e 100644
--- a/i3-config-wizard/main.c
+++ b/i3-config-wizard/main.c
@@ -332,8 +332,6 @@ static char *rewrite_binding(const char *input) {
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
walk++;
- //printf("remaining input: %s\n", walk);
-
cmdp_token_ptr *ptr = &(tokens[state]);
for (c = 0; c < ptr->n; c++) {
token = &(ptr->array[c]);
@@ -426,15 +424,13 @@ static char *rewrite_binding(const char *input) {
}
if (strcmp(token->name, "end") == 0) {
- //printf("checking for end: *%s*\n", walk);
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
if ((result = next_state(token)) != NULL)
return result;
- /* To make sure we start with an appropriate matching
- * datastructure for commands which do *not* specify any
+ /* To make sure we start with an appropriate matching data
+ * structure for commands which do *not* specify any
* criteria, we re-initialize the criteria system after
* every command. */
- // TODO: make this testable
walk++;
break;
}
diff --git a/i3-dmenu-desktop b/i3-dmenu-desktop
index 09670f65..e43d95aa 100755
--- a/i3-dmenu-desktop
+++ b/i3-dmenu-desktop
@@ -482,7 +482,8 @@ EOT
# double quote which is NOT preceded by a backslash (\).
#
# Therefore, we escape all double quotes (") by replacing them with \"
- $exec =~ s/"/\\"/g;
+ $exec =~ s/\\"/\\\\\\"/g;
+ $exec =~ s/([^\\])"/$1\\"/g;
if (exists($app->{StartupNotify}) && !$app->{StartupNotify}) {
$nosn = '--no-startup-id';
diff --git a/i3-input/keysym.map b/i3-input/keysym.map
index b198dd6d..aaecdfc3 100644
--- a/i3-input/keysym.map
+++ b/i3-input/keysym.map
@@ -1,6 +1,6 @@
# This list can be used to convert X11 Keysyms to Unicode 2.1 character.
# The list is not checked for correctness by Unicode officials. Use it
-# at your own risk and the creator is not responsable for any damage that
+# at your own risk and the creator is not responsible for any damage that
# occurred due to using this list.
#
# The list is created by looking at the Keysym names and the Unicode data
diff --git a/i3-sensible-pager b/i3-sensible-pager
index 386e2988..2d824684 100755
--- a/i3-sensible-pager
+++ b/i3-sensible-pager
@@ -8,6 +8,11 @@
# Distributions/packagers can enhance this script with a
# distribution-specific mechanism to find the preferred pager.
+# The less -E and -F options exit immediately for short files, strip if present.
+case "$LESS" in
+ *[EF]*) LESS=`echo "$LESS" | tr -d EF`
+esac
+
# Hopefully one of these is installed (no flamewars about preference please!):
# We don't use 'more' because it will exit if the file is too short.
# Worst case scenario we'll open the file in your editor.
diff --git a/i3-sensible-terminal b/i3-sensible-terminal
index fa1443c4..bee303f8 100755
--- a/i3-sensible-terminal
+++ b/i3-sensible-terminal
@@ -14,7 +14,7 @@
# 2. Distribution-specific mechanisms come next, e.g. x-terminal-emulator
# 3. The terminal emulator with best accessibility comes first.
# 4. No order is guaranteed/desired for the remaining terminal emulators.
-for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper; do
+for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper wezterm; do
if command -v "$terminal" > /dev/null 2>&1; then
exec "$terminal" "$@"
fi
diff --git a/i3bar/include/common.h b/i3bar/include/common.h
index 52f77b0e..e0f2e7e4 100644
--- a/i3bar/include/common.h
+++ b/i3bar/include/common.h
@@ -66,7 +66,7 @@ struct status_block {
uint32_t border_left;
bool pango_markup;
- /* The amount of pixels necessary to render a separater after the block. */
+ /* The amount of pixels necessary to render a separator after the block. */
uint32_t sep_block_width;
/* Continuously-updated information on how to render this status block. */
diff --git a/i3bar/src/config.c b/i3bar/src/config.c
index 1cff085e..14d54ed6 100644
--- a/i3bar/src/config.c
+++ b/i3bar/src/config.c
@@ -13,7 +13,6 @@
#include <stdlib.h>
#include <string.h>
-#include <X11/Xlib.h>
#include <yajl/yajl_parse.h>
config_t config;
@@ -126,31 +125,31 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
}
if (len == strlen("shift") && !strncmp((const char *)val, "shift", strlen("shift"))) {
- config.modifier = ShiftMask;
+ config.modifier = XCB_MOD_MASK_SHIFT;
return 1;
}
if (len == strlen("ctrl") && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
- config.modifier = ControlMask;
+ config.modifier = XCB_MOD_MASK_CONTROL;
return 1;
}
if (len == strlen("Mod") + 1 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
switch (val[3]) {
case '1':
- config.modifier = Mod1Mask;
+ config.modifier = XCB_MOD_MASK_1;
return 1;
case '2':
- config.modifier = Mod2Mask;
+ config.modifier = XCB_MOD_MASK_2;
return 1;
case '3':
- config.modifier = Mod3Mask;
+ config.modifier = XCB_MOD_MASK_3;
return 1;
case '5':
- config.modifier = Mod5Mask;
+ config.modifier = XCB_MOD_MASK_5;
return 1;
}
}
- config.modifier = Mod4Mask;
+ config.modifier = XCB_MOD_MASK_4;
return 1;
}
diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c
index 671588fe..1cfd457b 100644
--- a/i3bar/src/xcb.c
+++ b/i3bar/src/xcb.c
@@ -203,7 +203,7 @@ static uint32_t predict_statusline_length(bool use_short_text) {
if (block->border)
render->width += logical_px(block->border_left + block->border_right);
- /* Compute offset and append for text aligment in min_width. */
+ /* Compute offset and append for text alignment in min_width. */
if (block->min_width <= render->width) {
render->x_offset = 0;
render->x_append = 0;
diff --git a/include/all.h b/include/all.h
index b9a1a7a9..5941b5e6 100644
--- a/include/all.h
+++ b/include/all.h
@@ -58,6 +58,7 @@
#include "match.h"
#include "xcursor.h"
#include "resize.h"
+#include "tiling_drag.h"
#include "sighandler.h"
#include "move.h"
#include "output.h"
diff --git a/include/commands.h b/include/commands.h
index c27cba4a..d0f0140b 100644
--- a/include/commands.h
+++ b/include/commands.h
@@ -333,7 +333,7 @@ void cmd_shmlog(I3_CMD, const char *argument);
void cmd_debuglog(I3_CMD, const char *argument);
/**
- * Implementation of 'title_window_icon <yes|no>' and 'title_window_icon padding <px>'
+ * Implementation of 'title_window_icon <yes|no|toggle>' and 'title_window_icon <padding|toggle> <px>'
*
*/
void cmd_title_window_icon(I3_CMD, const char *enable, int padding);
diff --git a/include/commands_parser.h b/include/commands_parser.h
index 31333af7..7e1c5203 100644
--- a/include/commands_parser.h
+++ b/include/commands_parser.h
@@ -14,7 +14,7 @@
#include <yajl/yajl_gen.h>
/**
- * Holds an intermediate represenation of the result of a call to any command.
+ * Holds an intermediate representation 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.
*/
diff --git a/include/con.h b/include/con.h
index d8330098..b8bff080 100644
--- a/include/con.h
+++ b/include/con.h
@@ -209,6 +209,14 @@ Con *con_by_frame_id(xcb_window_t frame);
Con *con_by_mark(const char *mark);
/**
+ * Start from a container and traverse the transient_for linked list. Returns
+ * true if target window is found in the list. Protects againsts potential
+ * cycles.
+ *
+ */
+bool con_find_transient_for_window(Con *start, xcb_window_t target);
+
+/**
* Returns true if and only if the given containers holds the mark.
*
*/
@@ -363,6 +371,7 @@ void con_move_to_output(Con *con, Output *output, bool fix_coordinates);
*/
bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates);
+bool con_move_to_target(Con *con, Con *target);
/**
* Moves the given container to the given mark.
*
diff --git a/include/config_parser.h b/include/config_parser.h
index 00d01e45..82c57090 100644
--- a/include/config_parser.h
+++ b/include/config_parser.h
@@ -41,18 +41,18 @@ struct parser_ctx {
Match current_match;
/* A list which contains the states that lead to the current state, e.g.
- * INITIAL, WORKSPACE_LAYOUT.
- * When jumping back to INITIAL, statelist_idx will simply be set to 1
- * (likewise for other states, e.g. MODE or BAR).
- * This list is used to process the nearest error token. */
+ * INITIAL, WORKSPACE_LAYOUT.
+ * When jumping back to INITIAL, statelist_idx will simply be set to 1
+ * (likewise for other states, e.g. MODE or BAR).
+ * This list is used to process the nearest error token. */
int statelist[10];
/* NB: statelist_idx points to where the next entry will be inserted */
int statelist_idx;
/*******************************************************************************
- * The (small) stack where identified literals are stored during the parsing
- * of a single config directive (like $workspace).
- ******************************************************************************/
+ * The (small) stack where identified literals are stored during the parsing
+ * of a single config directive (like $workspace).
+ ******************************************************************************/
struct stack *stack;
struct variables_head variables;
@@ -61,7 +61,7 @@ struct parser_ctx {
};
/**
- * An intermediate reprsentation of the result of a parse_config call.
+ * An intermediate representation 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.
*
diff --git a/include/configuration.h b/include/configuration.h
index be072cf8..843e156f 100644
--- a/include/configuration.h
+++ b/include/configuration.h
@@ -238,9 +238,11 @@ struct Config {
color_t background;
struct Colortriple focused;
struct Colortriple focused_inactive;
+ struct Colortriple focused_tab_title;
struct Colortriple unfocused;
struct Colortriple urgent;
struct Colortriple placeholder;
+ bool got_focused_tab_title;
} client;
struct config_bar {
struct Colortriple focused;
diff --git a/include/data.h b/include/data.h
index 95acd66d..1b3c7119 100644
--- a/include/data.h
+++ b/include/data.h
@@ -9,11 +9,13 @@
*/
#pragma once
+#define PCRE2_CODE_UNIT_WIDTH 8
+
#define SN_API_NOT_YET_FROZEN 1
#include <libsn/sn-launcher.h>
#include <xcb/randr.h>
-#include <pcre.h>
+#include <pcre2.h>
#include <sys/time.h>
#include <cairo/cairo.h>
@@ -248,8 +250,7 @@ struct Startup_Sequence {
*/
struct regex {
char *pattern;
- pcre *regex;
- pcre_extra *extra;
+ pcre2_code *regex;
};
/**
@@ -662,8 +663,8 @@ struct Con {
char *title_format;
/** Whether the window icon should be displayed, and with what padding. -1
- * means display no window icon (default behavior), 0 means display without
- * any padding, 1 means display with 1 pixel of padding and so on. */
+ * means display no window icon (default behavior), 0 means display without
+ * any padding, 1 means display with 1 pixel of padding and so on. */
int window_icon_padding;
/* a sticky-group is an identifier which bundles several containers to a
diff --git a/include/display_version.h b/include/display_version.h
index d8dac30b..6996038e 100644
--- a/include/display_version.h
+++ b/include/display_version.h
@@ -14,7 +14,7 @@
/**
* Connects to i3 to find out the currently running version. Useful since it
* might be different from the version compiled into this binary (maybe the
- * user didn’t correctly install i3 or forgot te restart it).
+ * user didn’t correctly install i3 or forgot to restart it).
*
* The output looks like this:
* Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804)
diff --git a/include/i3.h b/include/i3.h
index 15ca1d46..4e2eb5c0 100644
--- a/include/i3.h
+++ b/include/i3.h
@@ -56,7 +56,6 @@ extern xcb_timestamp_t last_timestamp;
extern SnDisplay *sndisplay;
extern xcb_key_symbols_t *keysyms;
extern char **start_argv;
-extern Display *xlibdpy, *xkbdpy;
extern int xkb_current_group;
extern TAILQ_HEAD(bindings_head, Binding) *bindings;
extern const char *current_binding_mode;
diff --git a/include/libi3.h b/include/libi3.h
index fd5fb03b..005167c7 100644
--- a/include/libi3.h
+++ b/include/libi3.h
@@ -295,6 +295,12 @@ size_t i3string_get_num_glyphs(i3String *str);
int ipc_connect(const char *socket_path);
/**
+ * Connects to the socket at the given path with no fallback paths. Returns
+ * -1 if connect() fails and die()s for other errors.
+ */
+int ipc_connect_impl(const char *socket_path);
+
+/**
* Formats a message (payload) of the given size and type and sends it to i3 via
* the given socket file descriptor.
*
diff --git a/include/randr.h b/include/randr.h
index ae6a20a9..6fd7ea99 100644
--- a/include/randr.h
+++ b/include/randr.h
@@ -51,12 +51,6 @@ void output_init_con(Output *output);
void init_ws_for_output(Output *output);
/**
- * Initializes the specified output, assigning the specified workspace to it.
- *
- */
-//void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace);
-
-/**
* (Re-)queries the outputs via RandR and stores them in the list of outputs.
*
*/
diff --git a/include/tiling_drag.h b/include/tiling_drag.h
new file mode 100644
index 00000000..ab002d43
--- /dev/null
+++ b/include/tiling_drag.h
@@ -0,0 +1,16 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * tiling_drag.h: Reposition tiled windows by dragging.
+ *
+ */
+#pragma once
+
+/**
+ * Initiates a mouse drag operation on a tiled window.
+ *
+ */
+void tiling_drag(Con *con, xcb_button_press_event_t *event);
diff --git a/include/util.h b/include/util.h
index 09ad941f..8525b6d9 100644
--- a/include/util.h
+++ b/include/util.h
@@ -183,3 +183,15 @@ position_t position_from_direction(direction_t direction);
*
*/
direction_t direction_from_orientation_position(orientation_t orientation, position_t position);
+
+/**
+ * Converts direction to a string representation.
+ *
+ */
+const char *direction_to_string(direction_t direction);
+
+/**
+ * Converts position to a string representation.
+ *
+ */
+const char *position_to_string(position_t position);
diff --git a/include/window.h b/include/window.h
index 9759cc4b..7b43ab1f 100644
--- a/include/window.h
+++ b/include/window.h
@@ -94,7 +94,7 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
* it is still in use by popular widget toolkits such as GTK+ and Java AWT.
*
*/
-void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
+bool window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
/**
* Updates the WM_CLIENT_MACHINE
diff --git a/include/workspace.h b/include/workspace.h
index 2193ed0b..fe6d9f88 100644
--- a/include/workspace.h
+++ b/include/workspace.h
@@ -92,7 +92,7 @@ Con *create_workspace_on_output(Output *output, Con *content);
/**
* Returns true if the workspace is currently visible. Especially important for
- * multi-monitor environments, as they can have multiple currenlty active
+ * multi-monitor environments, as they can have multiple currently active
* workspaces.
*
*/
diff --git a/libi3/create_socket.c b/libi3/create_socket.c
index 4b93ff2d..d476f43a 100644
--- a/libi3/create_socket.c
+++ b/libi3/create_socket.c
@@ -10,6 +10,7 @@
#include <unistd.h>
#include <libgen.h>
#include <err.h>
+#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
@@ -34,10 +35,20 @@ int create_socket(const char *filename, char **out_socketpath) {
}
free(copy);
+ /* Check if the socket is in use by another process (this call does not
+ * succeed if the socket is stale / the owner already exited) */
+ int sockfd = ipc_connect_impl(resolved);
+ if (sockfd != -1) {
+ ELOG("Refusing to create UNIX socket at %s: Socket is already in use\n", resolved);
+ close(sockfd);
+ errno = EEXIST;
+ return -1;
+ }
+
/* Unlink the unix domain socket before */
unlink(resolved);
- int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket()");
free(resolved);
diff --git a/libi3/font.c b/libi3/font.c
index 514328f6..5a11b536 100644
--- a/libi3/font.c
+++ b/libi3/font.c
@@ -160,7 +160,7 @@ i3Font load_font(const char *pattern, const bool fallback) {
font.type = FONT_TYPE_NONE;
font.pattern = NULL;
- /* No XCB connction, return early because we're just validating the
+ /* No XCB connection, return early because we're just validating the
* configuration file. */
if (conn == NULL) {
return font;
diff --git a/libi3/ipc_connect.c b/libi3/ipc_connect.c
index 871fe083..5da9f129 100644
--- a/libi3/ipc_connect.c
+++ b/libi3/ipc_connect.c
@@ -13,6 +13,7 @@
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
+#include <unistd.h>
/*
* Connects to the i3 IPC socket and returns the file descriptor for the
@@ -39,6 +40,20 @@ int ipc_connect(const char *socket_path) {
path = sstrdup("/tmp/i3-ipc.sock");
}
+ int sockfd = ipc_connect_impl(path);
+ if (sockfd < 0) {
+ err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path);
+ }
+ free(path);
+ return sockfd;
+}
+
+/**
+ * Connects to the socket at the given path with no fallback paths. Returns
+ * -1 if connect() fails and die()s for other errors.
+ *
+ */
+int ipc_connect_impl(const char *socket_path) {
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd == -1)
err(EXIT_FAILURE, "Could not create socket");
@@ -48,9 +63,10 @@ int ipc_connect(const char *socket_path) {
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
- strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
- if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
- err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path);
- free(path);
+ strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
+ if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
+ close(sockfd);
+ return -1;
+ }
return sockfd;
}
diff --git a/man/i3-input.man b/man/i3-input.man
index dc145914..9eb6c541 100644
--- a/man/i3-input.man
+++ b/man/i3-input.man
@@ -39,7 +39,7 @@ Display the <prompt> string in front of user input text field.
The prompt string is not included in the user input/command.
-f <font>::
-Use the specified X11 core font (use +xfontsel+ to chose a font).
+Use the specified X11 core font (use +xfontsel+ to choose a font).
-v::
Show version and exit.
diff --git a/meson.build b/meson.build
index 27fc9fb5..8c139c2a 100644
--- a/meson.build
+++ b/meson.build
@@ -6,7 +6,7 @@
project(
'i3',
'c',
- version: '4.20.1',
+ version: '4.21',
default_options: [
'c_std=c11',
'warning_level=1', # enable all warnings (-Wall)
@@ -316,7 +316,7 @@ xcb_util_xrm_dep = dependency('xcb-xrm', method: 'pkg-config')
xkbcommon_dep = dependency('xkbcommon', method: 'pkg-config')
xkbcommon_x11_dep = dependency('xkbcommon-x11', method: 'pkg-config')
yajl_dep = dependency('yajl', method: 'pkg-config')
-libpcre_dep = dependency('libpcre', version: '>=8.10', method: 'pkg-config')
+libpcre_dep = dependency('libpcre2-8', version: '>=10', method: 'pkg-config')
cairo_dep = dependency('cairo', version: '>=1.14.4', method: 'pkg-config')
pangocairo_dep = dependency('pangocairo', method: 'pkg-config')
glib_dep = dependency('glib-2.0', method: 'pkg-config')
@@ -409,6 +409,7 @@ i3srcs = [
'src/sighandler.c',
'src/startup.c',
'src/sync.c',
+ 'src/tiling_drag.c',
'src/tree.c',
'src/util.c',
'src/version.c',
diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec
index 7d3367ea..486f20dd 100644
--- a/parser-specs/commands.spec
+++ b/parser-specs/commands.spec
@@ -166,8 +166,10 @@ state FOCUS_AUTO:
-> call cmd_focus_direction($direction)
state FOCUS_OUTPUT:
- output = string
- -> call cmd_focus_output($output)
+ output = word
+ -> call cmd_focus_output($output); FOCUS_OUTPUT
+ end
+ -> call cmd_focus_output(NULL); INITIAL
# kill [window|client]
state KILL:
@@ -466,6 +468,8 @@ state TITLE_FORMAT:
state TITLE_WINDOW_ICON:
'padding'
-> TITLE_WINDOW_ICON_PADDING
+ enable = 'toggle'
+ -> TITLE_WINDOW_ICON_PADDING
enable = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive'
-> call cmd_title_window_icon($enable, 0)
diff --git a/parser-specs/config.spec b/parser-specs/config.spec
index a52a769f..617ed130 100644
--- a/parser-specs/config.spec
+++ b/parser-specs/config.spec
@@ -56,7 +56,7 @@ state INITIAL:
exectype = 'exec_always', 'exec' -> EXEC
colorclass = 'client.background'
-> COLOR_SINGLE
- colorclass = 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
+ colorclass = 'client.focused_inactive', 'client.focused_tab_title', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
-> COLOR_BORDER
# We ignore comments and 'set' lines (variables).
@@ -400,8 +400,6 @@ state BINDCOMMAND:
->
command = string
-> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
- end
- -> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
################################################################################
# Mode configuration
@@ -567,7 +565,7 @@ state BAR_POSITION:
-> call cfg_bar_position($position); BAR
state BAR_OUTPUT:
- output = string
+ output = word
-> call cfg_bar_output($output); BAR
state BAR_TRAY_OUTPUT:
diff --git a/release-notes/bugfixes/0-example b/release-notes/bugfixes/0-example
new file mode 100644
index 00000000..2cb1f668
--- /dev/null
+++ b/release-notes/bugfixes/0-example
@@ -0,0 +1 @@
+fix crash with "layout default"
diff --git a/release-notes/bugfixes/1-replace-socket b/release-notes/bugfixes/1-replace-socket
new file mode 100644
index 00000000..82c78b61
--- /dev/null
+++ b/release-notes/bugfixes/1-replace-socket
@@ -0,0 +1 @@
+Do not replace existing IPC socket on start
diff --git a/release-notes/bugfixes/2-fix-focus-wrap b/release-notes/bugfixes/2-fix-focus-wrap
new file mode 100644
index 00000000..3971238d
--- /dev/null
+++ b/release-notes/bugfixes/2-fix-focus-wrap
@@ -0,0 +1 @@
+fix focus when moving container between outputs with mouse warp and focus_follows_mouse
diff --git a/release-notes/bugfixes/3-transient_for b/release-notes/bugfixes/3-transient_for
new file mode 100644
index 00000000..c652874f
--- /dev/null
+++ b/release-notes/bugfixes/3-transient_for
@@ -0,0 +1 @@
+Fix endless loop with transient_for windows
diff --git a/release-notes/bugfixes/4-failed-workspace-output b/release-notes/bugfixes/4-failed-workspace-output
new file mode 100644
index 00000000..b3d23957
--- /dev/null
+++ b/release-notes/bugfixes/4-failed-workspace-output
@@ -0,0 +1 @@
+fix wrong failed reply on move workspace to output
diff --git a/release-notes/bugfixes/5-fix-wm-registration b/release-notes/bugfixes/5-fix-wm-registration
new file mode 100644
index 00000000..513c96eb
--- /dev/null
+++ b/release-notes/bugfixes/5-fix-wm-registration
@@ -0,0 +1 @@
+changed WM registration selection from WM_S_S<screen> to WM_S<screen>
diff --git a/release-notes/bugfixes/6-fix-graphics-artifacts b/release-notes/bugfixes/6-fix-graphics-artifacts
new file mode 100644
index 00000000..a13e2b0f
--- /dev/null
+++ b/release-notes/bugfixes/6-fix-graphics-artifacts
@@ -0,0 +1 @@
+avoid graphics artifacts when changing the layout tree by initializing surfaces to all black
diff --git a/release-notes/bugfixes/7-fix-segfault b/release-notes/bugfixes/7-fix-segfault
new file mode 100644
index 00000000..5c280bee
--- /dev/null
+++ b/release-notes/bugfixes/7-fix-segfault
@@ -0,0 +1 @@
+Fix segfault if command in bindsym is empty
diff --git a/release-notes/bugfixes/7-update-parent-con-title-on-sibling-move b/release-notes/bugfixes/7-update-parent-con-title-on-sibling-move
new file mode 100644
index 00000000..7bd57dfe
--- /dev/null
+++ b/release-notes/bugfixes/7-update-parent-con-title-on-sibling-move
@@ -0,0 +1 @@
+update parent split con titles when child con swaps position with another child con
diff --git a/release-notes/bugfixes/8-bar-font b/release-notes/bugfixes/8-bar-font
new file mode 100644
index 00000000..618d3c15
--- /dev/null
+++ b/release-notes/bugfixes/8-bar-font
@@ -0,0 +1 @@
+fix default font not being applied to bars if defined after bar block
diff --git a/release-notes/bugfixes/8-bar-output-trailing-whitespace b/release-notes/bugfixes/8-bar-output-trailing-whitespace
new file mode 100644
index 00000000..c551d883
--- /dev/null
+++ b/release-notes/bugfixes/8-bar-output-trailing-whitespace
@@ -0,0 +1 @@
+strip trailing whitespace in bar output names
diff --git a/release-notes/bugfixes/8-fix-nested-variables-crash b/release-notes/bugfixes/8-fix-nested-variables-crash
new file mode 100644
index 00000000..44058ce0
--- /dev/null
+++ b/release-notes/bugfixes/8-fix-nested-variables-crash
@@ -0,0 +1 @@
+Fix crash if config contains nested variables.
diff --git a/release-notes/bugfixes/8-mode-default-sigsegv b/release-notes/bugfixes/8-mode-default-sigsegv
new file mode 100644
index 00000000..75ce814f
--- /dev/null
+++ b/release-notes/bugfixes/8-mode-default-sigsegv
@@ -0,0 +1 @@
+Fix segfault with explicit mode "default" key bindings.
diff --git a/release-notes/bugfixes/9-bs-normal b/release-notes/bugfixes/9-bs-normal
new file mode 100644
index 00000000..0342fbf0
--- /dev/null
+++ b/release-notes/bugfixes/9-bs-normal
@@ -0,0 +1 @@
+Restore BS_NORMAL _MOTIF_WM_HINTS correctly
diff --git a/release-notes/changes/0-example b/release-notes/changes/0-example
new file mode 100644
index 00000000..6242e4ed
--- /dev/null
+++ b/release-notes/changes/0-example
@@ -0,0 +1 @@
+Acquire the WM_Sn selection when starting as required by ICCCM
diff --git a/release-notes/changes/1-valid-socket b/release-notes/changes/1-valid-socket
new file mode 100644
index 00000000..e605e6dd
--- /dev/null
+++ b/release-notes/changes/1-valid-socket
@@ -0,0 +1 @@
+Refuse to start without valid IPC socket
diff --git a/release-notes/changes/2-client.focused_tab_title b/release-notes/changes/2-client.focused_tab_title
new file mode 100644
index 00000000..04802192
--- /dev/null
+++ b/release-notes/changes/2-client.focused_tab_title
@@ -0,0 +1 @@
+Add client.focused_tab_title color option
diff --git a/release-notes/changes/3-focus-outputs b/release-notes/changes/3-focus-outputs
new file mode 100644
index 00000000..cecd5b42
--- /dev/null
+++ b/release-notes/changes/3-focus-outputs
@@ -0,0 +1 @@
+Add support for multiple outputs in focus command
diff --git a/release-notes/changes/3-tiling-drag b/release-notes/changes/3-tiling-drag
new file mode 100644
index 00000000..195fcdbc
--- /dev/null
+++ b/release-notes/changes/3-tiling-drag
@@ -0,0 +1 @@
+Allow moving tiling windows with the mouse
diff --git a/release-notes/changes/4-title_window_icon-toggle b/release-notes/changes/4-title_window_icon-toggle
new file mode 100644
index 00000000..f15e7a4a
--- /dev/null
+++ b/release-notes/changes/4-title_window_icon-toggle
@@ -0,0 +1 @@
+Add title_window_icon toggle
diff --git a/release-notes/generator.pl b/release-notes/generator.pl
new file mode 100755
index 00000000..d77eb098
--- /dev/null
+++ b/release-notes/generator.pl
@@ -0,0 +1,96 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use v5.10;
+use Getopt::Long;
+
+my @template = (
+'
+ ┌──────────────────────────────┐
+ │ Release notes for i3 v4.21 │
+ └──────────────────────────────┘
+
+This is i3 v4.21. This version is considered stable. All users of i3 are
+strongly encouraged to upgrade.
+
+
+ ┌────────────────────────────┐
+ │ Changes in i3 v4.21 │
+ └────────────────────────────┘
+
+',
+'
+ ┌────────────────────────────┐
+ │ Bugfixes │
+ └────────────────────────────┘
+
+');
+
+my $print_urls = 0;
+my $result = GetOptions('print-urls' => \$print_urls);
+
+sub get_number {
+ my $s = shift;
+ return $1 if $s =~ m/^(\d+)/;
+ return -1;
+}
+
+sub read_changefiles {
+ my $dirpath = shift;
+ opendir my $dir, $dirpath or die "Cannot open directory $dirpath: $!";
+ my @files = sort { get_number($a) <=> get_number($b) } readdir $dir;
+
+ closedir $dir;
+
+ my $s = '';
+ for my $filename (@files) {
+ next if $filename eq '.';
+ next if $filename eq '..';
+ next if $filename eq '0-example';
+
+ die "Filename $filename should start with a number (e.g. the pull request number)" unless get_number($filename) > 0;
+
+ $filename = $dirpath . '/' . $filename;
+ open my $in, '<', $filename or die "can't open $filename: $!";
+ my @lines = <$in>;
+ close $in or die "can't close $filename: $!";
+
+ my $content = trim(join("\n ", map { trim($_) } @lines));
+ die "$filename can't be empty" unless length($content) > 0;
+
+ my $url = '';
+ if ($print_urls) {
+ my $commit = `git log --diff-filter=A --pretty=format:"%H" $filename`;
+ $commit = trim($commit) if defined($commit);
+ die "$filename: git log failed to find commit" if ($?) || (length($commit) == 0);
+
+ my $pr = find_pr($commit);
+ $url = 'https://github.com/i3/i3/commit/' . $commit;
+ $url = 'https://github.com/i3/i3/pull/' . $pr if defined($pr);
+ $url = $url . "\n";
+ }
+
+ $s = $s . ' • ' . $content . "\n" . $url;
+ }
+ return $s;
+}
+
+sub find_pr {
+ my $hash = shift;
+ my $result = `git log --merges --ancestry-path --oneline $hash..next | grep 'Merge pull request' | tail -n1`;
+ return unless defined($result);
+
+ return unless ($result =~ /Merge pull request .([0-9]+)/);
+ return $1;
+}
+
+sub trim {
+ (my $s = $_[0]) =~ s/^\s+|\s+$//g;
+ return $s;
+}
+
+# Expected to run for i3's git root
+my $changes = read_changefiles('release-notes/changes');
+my $bugfixes = read_changefiles('release-notes/bugfixes');
+
+print $template[0] . $changes . $template[1] . $bugfixes;
diff --git a/release.sh b/release.sh
index 2df88ab0..59391441 100755
--- a/release.sh
+++ b/release.sh
@@ -22,7 +22,8 @@ fi
if [ ! -e "RELEASE-NOTES-${RELEASE_VERSION}" ]
then
- echo "RELEASE-NOTES-${RELEASE_VERSION} not found."
+ echo "RELEASE-NOTES-${RELEASE_VERSION} not found. Here is the output from the generator:"
+ ./release-notes/generator.pl --print-urls
exit 1
fi
diff --git a/src/click.c b/src/click.c
index 7f8dd278..10af756e 100644
--- a/src/click.c
+++ b/src/click.c
@@ -80,7 +80,7 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
*/
static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) {
/* The client is in tiling layout. We can still initiate a resize with the
- * right mouse button, by chosing the border which is the most near one to
+ * right mouse button, by choosing the border which is the most near one to
* the position of the mouse pointer */
int to_right = con->rect.width - event->event_x,
to_left = event->event_x,
@@ -188,9 +188,6 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
goto done;
}
- if (ws != focused_workspace)
- workspace_show(ws);
-
/* get the floating con */
Con *floatingcon = con_inside_floating(con);
const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT;
@@ -218,7 +215,13 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
goto done;
}
- /* 2: focus this con or one of its children. */
+ /* 2: floating modifier pressed, initiate a drag */
+ if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1 && !floatingcon) {
+ tiling_drag(con, event);
+ goto done;
+ }
+
+ /* 3: focus this con or one of its children. */
Con *con_to_focus = con;
if (in_stacked && dest == CLICK_DECORATION) {
/* If the container is a tab/stacked container and the click happened
@@ -231,19 +234,22 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
}
}
}
+ if (ws != focused_workspace) {
+ workspace_show(ws);
+ }
con_activate(con_to_focus);
- /* 3: For floating containers, we also want to raise them on click.
+ /* 4: For floating containers, we also want to raise them on click.
* We will skip handling events on floating cons in fullscreen mode */
Con *fs = con_get_fullscreen_covering_ws(ws);
if (floatingcon != NULL && fs != con) {
- /* 4: floating_modifier plus left mouse button drags */
+ /* 5: floating_modifier plus left mouse button drags */
if (mod_pressed && is_left_click) {
floating_drag_window(floatingcon, event, false);
return;
}
- /* 5: resize (floating) if this was a (left or right) click on the
+ /* 6: resize (floating) if this was a (left or right) click on the
* left/right/bottom border, or a right click on the decoration.
* also try resizing (tiling) if possible */
if (mod_pressed && is_right_click) {
@@ -272,7 +278,7 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
return;
}
- /* 6: dragging, if this was a click on a decoration (which did not lead
+ /* 7: dragging, if this was a click on a decoration (which did not lead
* to a resize) */
if (dest == CLICK_DECORATION && is_left_click) {
floating_drag_window(floatingcon, event, !was_focused);
@@ -282,7 +288,13 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
goto done;
}
- /* 7: floating modifier pressed, initiate a resize */
+ /* 8: floating modifier pressed, initiate a drag */
+ if ((mod_pressed || dest == CLICK_DECORATION) && event->detail == XCB_BUTTON_INDEX_1) {
+ tiling_drag(con, event);
+ goto done;
+ }
+
+ /* 9: floating modifier pressed, initiate a resize */
if (dest == CLICK_INSIDE && mod_pressed && is_right_click) {
if (floating_mod_on_tiled_client(con, event)) {
return;
@@ -293,7 +305,7 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
xcb_flush(conn);
return;
}
- /* 8: otherwise, check for border/decoration clicks and resize */
+ /* 10: otherwise, check for border/decoration clicks and resize */
if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
is_left_or_right_click) {
DLOG("Trying to resize (tiling)\n");
diff --git a/src/commands.c b/src/commands.c
index 798e2f6c..27853bd0 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -1023,6 +1023,76 @@ void cmd_mode(I3_CMD, const char *mode) {
ysuccess(true);
}
+typedef struct user_output_name {
+ char *name;
+ TAILQ_ENTRY(user_output_name) user_output_names;
+} user_output_name;
+typedef TAILQ_HEAD(user_output_names_head, user_output_name) user_output_names_head;
+
+static void user_output_names_add(user_output_names_head *list, const char *name) {
+ if (strcmp(name, "next") == 0) {
+ /* "next" here works like a wildcard: It "expands" to all available
+ * outputs. */
+ Output *output;
+ TAILQ_FOREACH (output, &outputs, outputs) {
+ user_output_name *co = scalloc(sizeof(user_output_name), 1);
+ co->name = sstrdup(output_primary_name(output));
+ TAILQ_INSERT_TAIL(list, co, user_output_names);
+ }
+ return;
+ }
+
+ user_output_name *co = scalloc(sizeof(user_output_name), 1);
+ co->name = sstrdup(name);
+ TAILQ_INSERT_TAIL(list, co, user_output_names);
+ return;
+}
+
+static Output *user_output_names_find_next(user_output_names_head *names, Output *current_output) {
+ Output *target_output = NULL;
+ user_output_name *uo;
+ TAILQ_FOREACH (uo, names, user_output_names) {
+ if (!target_output) {
+ /* The first available output from the list is used in 2 cases:
+ * 1. When we must wrap around the user list. For example, if user
+ * specifies outputs A B C and C is `current_output`.
+ * 2. When the current output is not in the user list. For example,
+ * user specifies A B C and D is `current_output`. */
+ target_output = get_output_from_string(current_output, uo->name);
+ }
+ if (strcasecmp(output_primary_name(current_output), uo->name) == 0) {
+ /* The current output is in the user list */
+ while (true) {
+ /* This corrupts the outer loop but it is ok since we are going
+ * to break anyway. */
+ uo = TAILQ_NEXT(uo, user_output_names);
+ if (!uo) {
+ /* We reached the end of the list. We should use the first
+ * available output that, if it exists, is already saved in
+ * target_output. */
+ break;
+ }
+ Output *out = get_output_from_string(current_output, uo->name);
+ if (out) {
+ return out;
+ }
+ }
+ break;
+ }
+ }
+ return target_output;
+}
+
+static void user_output_names_free(user_output_names_head *names) {
+ user_output_name *uo;
+ while (!TAILQ_EMPTY(names)) {
+ uo = TAILQ_FIRST(names);
+ free(uo->name);
+ TAILQ_REMOVE(names, uo, user_output_names);
+ free(uo);
+ }
+}
+
/*
* Implementation of 'move [window|container|workspace] [to] output <strings>'.
*
@@ -1031,40 +1101,21 @@ void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
/* Initialize a data structure that is used to save multiple user-specified
* output names since this function is called multiple types for each
* command call. */
- typedef struct user_output_name {
- char *name;
- TAILQ_ENTRY(user_output_name) user_output_names;
- } user_output_name;
- static TAILQ_HEAD(user_output_names_head, user_output_name) user_output_names = TAILQ_HEAD_INITIALIZER(user_output_names);
+ static user_output_names_head names = TAILQ_HEAD_INITIALIZER(names);
if (name) {
- if (strcmp(name, "next") == 0) {
- /* "next" here works like a wildcard: It "expands" to all available
- * outputs. */
- Output *output;
- TAILQ_FOREACH (output, &outputs, outputs) {
- user_output_name *co = scalloc(sizeof(user_output_name), 1);
- co->name = sstrdup(output_primary_name(output));
- TAILQ_INSERT_TAIL(&user_output_names, co, user_output_names);
- }
- return;
- }
-
- user_output_name *co = scalloc(sizeof(user_output_name), 1);
- co->name = sstrdup(name);
- TAILQ_INSERT_TAIL(&user_output_names, co, user_output_names);
+ user_output_names_add(&names, name);
return;
}
HANDLE_EMPTY_MATCH;
- if (TAILQ_EMPTY(&user_output_names)) {
+ if (TAILQ_EMPTY(&names)) {
yerror("At least one output must be specified");
return;
}
bool success = false;
- user_output_name *uo;
owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) {
Con *ws = con_get_workspace(current->con);
@@ -1073,41 +1124,7 @@ void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
}
Output *current_output = get_output_for_con(ws);
-
- Output *target_output = NULL;
- TAILQ_FOREACH (uo, &user_output_names, user_output_names) {
- if (strcasecmp(output_primary_name(current_output), uo->name) == 0) {
- /* The current output is in the user list */
- while (true) {
- /* This corrupts the outer loop but it is ok since we are
- * going to break anyway. */
- uo = TAILQ_NEXT(uo, user_output_names);
- if (!uo) {
- /* We reached the end of the list. We should use the
- * first available output that, if it exists, is
- * already saved in target_output. */
- break;
- }
- Output *out = get_output_from_string(current_output, uo->name);
- if (out) {
- DLOG("Found next target for workspace %s from user list: %s\n", ws->name, uo->name);
- target_output = out;
- break;
- }
- }
- break;
- }
- if (!target_output) {
- /* The first available output from the list is used in 2 cases:
- * 1. When we must wrap around the user list. For example, if
- * user specifies outputs A B C and C is `current_output`.
- * 2. When the current output is not in the user list. For
- * example, user specifies A B C and D is `current_output`.
- */
- DLOG("Found first target for workspace %s from user list: %s\n", ws->name, uo->name);
- target_output = get_output_from_string(current_output, uo->name);
- }
- }
+ Output *target_output = user_output_names_find_next(&names, current_output);
if (target_output) {
if (move_workspace) {
workspace_move_to_output(ws, target_output);
@@ -1117,13 +1134,7 @@ void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
success = true;
}
}
-
- while (!TAILQ_EMPTY(&user_output_names)) {
- uo = TAILQ_FIRST(&user_output_names);
- free(uo->name);
- TAILQ_REMOVE(&user_output_names, uo, user_output_names);
- free(uo);
- }
+ user_output_names_free(&names);
cmd_output->needs_tree_render = success;
if (success) {
@@ -1757,6 +1768,17 @@ void cmd_open(I3_CMD) {
*
*/
void cmd_focus_output(I3_CMD, const char *name) {
+ static user_output_names_head names = TAILQ_HEAD_INITIALIZER(names);
+ if (name) {
+ user_output_names_add(&names, name);
+ return;
+ }
+
+ if (TAILQ_EMPTY(&names)) {
+ yerror("At least one output must be specified");
+ return;
+ }
+
HANDLE_EMPTY_MATCH;
if (TAILQ_EMPTY(&owindows)) {
@@ -1765,25 +1787,29 @@ void cmd_focus_output(I3_CMD, const char *name) {
}
Output *current_output = get_output_for_con(TAILQ_FIRST(&owindows)->con);
- Output *output = get_output_from_string(current_output, name);
+ Output *target_output = user_output_names_find_next(&names, current_output);
+ user_output_names_free(&names);
+ bool success = false;
+ if (target_output) {
+ success = true;
- if (!output) {
- yerror("Output %s not found.", name);
- return;
- }
+ /* get visible workspace on output */
+ Con *ws = NULL;
+ GREP_FIRST(ws, output_get_content(target_output->con), workspace_is_visible(child));
+ if (!ws) {
+ yerror("BUG: No workspace found on output.");
+ return;
+ }
- /* get visible workspace on output */
- Con *ws = NULL;
- GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
- if (!ws) {
- yerror("BUG: No workspace found on output.");
- return;
+ workspace_show(ws);
}
- workspace_show(ws);
-
- cmd_output->needs_tree_render = true;
- ysuccess(true);
+ cmd_output->needs_tree_render = success;
+ if (success) {
+ ysuccess(true);
+ } else {
+ yerror("No output matched");
+ }
}
/*
@@ -2038,20 +2064,40 @@ void cmd_title_format(I3_CMD, const char *format) {
}
/*
- * Implementation of 'title_window_icon <yes|no>' and 'title_window_icon padding <px>'
+ * Implementation of 'title_window_icon <yes|no|toggle>' and 'title_window_icon padding <px>'
*
*/
void cmd_title_window_icon(I3_CMD, const char *enable, int padding) {
- if (enable != NULL && !boolstr(enable)) {
- padding = -1;
+ bool is_toggle = false;
+ if (enable != NULL) {
+ if (strcmp(enable, "toggle") == 0) {
+ is_toggle = true;
+ } else if (!boolstr(enable)) {
+ padding = -1;
+ }
}
DLOG("setting window_icon=%d\n", padding);
HANDLE_EMPTY_MATCH;
owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) {
- DLOG("setting window_icon for %p / %s\n", current->con, current->con->name);
- current->con->window_icon_padding = padding;
+ if (is_toggle) {
+ const int current_padding = current->con->window_icon_padding;
+ if (padding > 0) {
+ if (current_padding < 0) {
+ current->con->window_icon_padding = padding;
+ } else {
+ /* toggle off, but store padding given */
+ current->con->window_icon_padding = -(padding + 1);
+ }
+ } else {
+ /* Set to negative of (current value+1) to keep old padding when toggling */
+ current->con->window_icon_padding = -(current_padding + 1);
+ }
+ } else {
+ current->con->window_icon_padding = padding;
+ }
+ DLOG("Set window_icon for %p / %s to %d\n", current->con, current->con->name, current->con->window_icon_padding);
if (current->con->window != NULL) {
/* Make sure the window title is redrawn immediately. */
diff --git a/src/commands_parser.c b/src/commands_parser.c
index fd02293d..7cdf6c68 100644
--- a/src/commands_parser.c
+++ b/src/commands_parser.c
@@ -390,7 +390,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
free(possible_tokens);
/* Contains the same amount of characters as 'input' has, but with
- * the unparseable part highlighted using ^ characters. */
+ * the unparsable part highlighted using ^ characters. */
char *position = smalloc(len + 1);
for (const char *copywalk = input; *copywalk != '\0'; copywalk++)
position[(copywalk - input)] = (copywalk >= walk ? '^' : ' ');
diff --git a/src/con.c b/src/con.c
index 18338235..e7d18ff9 100644
--- a/src/con.c
+++ b/src/con.c
@@ -731,6 +731,41 @@ Con *con_by_mark(const char *mark) {
}
/*
+ * Start from a container and traverse the transient_for linked list. Returns
+ * true if target window is found in the list. Protects againsts potential
+ * cycles.
+ *
+ */
+bool con_find_transient_for_window(Con *start, xcb_window_t target) {
+ Con *transient_con = start;
+ int count = con_num_windows(croot);
+ while (transient_con != NULL &&
+ transient_con->window != NULL &&
+ transient_con->window->transient_for != XCB_NONE) {
+ DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, target = 0x%08x\n",
+ transient_con->window->id, transient_con->window->transient_for, target);
+ if (transient_con->window->transient_for == target) {
+ return true;
+ }
+ Con *next_transient = con_by_window_id(transient_con->window->transient_for);
+ if (next_transient == NULL) {
+ break;
+ }
+ /* Some clients (e.g. x11-ssh-askpass) actually set WM_TRANSIENT_FOR to
+ * their own window id, so break instead of looping endlessly. */
+ if (transient_con == next_transient) {
+ break;
+ }
+ transient_con = next_transient;
+
+ if (count-- <= 0) { /* Avoid cycles, see #4404 */
+ break;
+ }
+ }
+ return false;
+}
+
+/*
* Returns true if and only if the given containers holds the mark.
*
*/
@@ -852,8 +887,6 @@ void con_unmark(Con *con, const char *name) {
Con *con_for_window(Con *con, i3Window *window, Match **store_match) {
Con *child;
Match *match;
- //DLOG("searching con for window %p starting at con %p\n", window, con);
- //DLOG("class == %s\n", window->class_class);
TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
TAILQ_FOREACH (match, &(child->swallow_head), matches) {
@@ -1012,8 +1045,8 @@ void con_fix_percent(Con *con) {
Con *child;
int children = con_num_children(con);
- // calculate how much we have distributed and how many containers
- // with a percentage set we have
+ /* calculate how much we have distributed and how many containers with a
+ * percentage set we have */
double total = 0.0;
int children_with_percent = 0;
TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
@@ -1023,8 +1056,8 @@ void con_fix_percent(Con *con) {
}
}
- // if there were children without a percentage set, set to a value that
- // will make those children proportional to all others
+ /* if there were children without a percentage set, set to a value that
+ * will make those children proportional to all others */
if (children_with_percent != children) {
TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
if (child->percent <= 0.0) {
@@ -1037,8 +1070,8 @@ void con_fix_percent(Con *con) {
}
}
- // if we got a zero, just distribute the space equally, otherwise
- // distribute according to the proportions we got
+ /* if we got a zero, just distribute the space equally, otherwise
+ * distribute according to the proportions we got */
if (total == 0.0) {
TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
child->percent = 1.0 / children;
@@ -1356,17 +1389,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
return true;
}
-/*
- * Moves the given container to the given mark.
- *
- */
-bool con_move_to_mark(Con *con, const char *mark) {
- Con *target = con_by_mark(mark);
- if (target == NULL) {
- DLOG("found no container with mark \"%s\"\n", mark);
- return false;
- }
-
+bool con_move_to_target(Con *con, Con *target) {
/* For target containers in the scratchpad, we just send the window to the scratchpad. */
if (con_get_workspace(target) == workspace_get("__i3_scratch")) {
DLOG("target container is in the scratchpad, moving container to scratchpad.\n");
@@ -1404,6 +1427,20 @@ bool con_move_to_mark(Con *con, const char *mark) {
}
/*
+ * Moves the given container to the given mark.
+ *
+ */
+bool con_move_to_mark(Con *con, const char *mark) {
+ Con *target = con_by_mark(mark);
+ if (target == NULL) {
+ DLOG("found no container with mark \"%s\"\n", mark);
+ return false;
+ }
+
+ return con_move_to_target(con, target);
+}
+
+/*
* Moves the given container to the currently focused container on the given
* workspace.
*
@@ -2202,7 +2239,6 @@ void con_set_urgency(Con *con, bool urgent) {
} else
DLOG("Discarding urgency WM_HINT because timer is running\n");
- //CLIENT_LOG(con);
if (con->window) {
if (con->urgent) {
gettimeofday(&con->window->urgent, NULL);
diff --git a/src/config.c b/src/config.c
index c590f3c5..e2dd69bd 100644
--- a/src/config.c
+++ b/src/config.c
@@ -197,6 +197,7 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff", "#484e50");
INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888", "#292d2e");
INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff", "#900000");
+ config.client.got_focused_tab_title = false;
/* border and indicator color are ignored for placeholder contents */
INIT_COLOR(config.client.placeholder, "#000000", "#0c0c0c", "#ffffff", "#000000");
@@ -272,6 +273,15 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
set_font(&config.font);
}
+ /* Make bar config blocks without a configured font use the i3-wide font. */
+ Barconfig *current;
+ TAILQ_FOREACH (current, &barconfigs, configs) {
+ if (current->font != NULL) {
+ continue;
+ }
+ current->font = sstrdup(config.font.pattern);
+ }
+
if (load_type == C_RELOAD) {
translate_keysyms();
grab_all_keys(conn);
diff --git a/src/config_directives.c b/src/config_directives.c
index 3bb6e793..a611da27 100644
--- a/src/config_directives.c
+++ b/src/config_directives.c
@@ -163,15 +163,9 @@ i3_event_state_mask_t event_state_from_str(const char *str) {
return result;
}
-static char *font_pattern;
-
CFGFUN(font, const char *font) {
config.font = load_font(font, true);
set_font(&config.font);
-
- /* Save the font pattern for using it as bar font later on */
- FREE(font_pattern);
- font_pattern = sstrdup(font);
}
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
@@ -186,6 +180,11 @@ static char *current_mode;
static bool current_mode_pango_markup;
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
+ if (current_mode == NULL) {
+ /* When using an invalid mode name, e.g. “default” */
+ return;
+ }
+
configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, current_mode, current_mode_pango_markup);
}
@@ -467,24 +466,32 @@ CFGFUN(color_single, const char *colorclass, const char *color) {
}
CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border) {
-#define APPLY_COLORS(classname) \
- do { \
- if (strcmp(colorclass, "client." #classname) == 0) { \
- config.client.classname.border = draw_util_hex_to_color(border); \
- config.client.classname.background = draw_util_hex_to_color(background); \
- config.client.classname.text = draw_util_hex_to_color(text); \
- if (indicator != NULL) { \
- config.client.classname.indicator = draw_util_hex_to_color(indicator); \
- } \
- if (child_border != NULL) { \
- config.client.classname.child_border = draw_util_hex_to_color(child_border); \
- } else { \
- config.client.classname.child_border = config.client.classname.background; \
- } \
- } \
+#define APPLY_COLORS(classname) \
+ do { \
+ if (strcmp(colorclass, "client." #classname) == 0) { \
+ if (strcmp("focused_tab_title", #classname) == 0) { \
+ config.client.got_focused_tab_title = true; \
+ if (indicator || child_border) { \
+ ELOG("indicator and child_border colors have no effect for client.focused_tab_title\n"); \
+ } \
+ } \
+ config.client.classname.border = draw_util_hex_to_color(border); \
+ config.client.classname.background = draw_util_hex_to_color(background); \
+ config.client.classname.text = draw_util_hex_to_color(text); \
+ if (indicator != NULL) { \
+ config.client.classname.indicator = draw_util_hex_to_color(indicator); \
+ } \
+ if (child_border != NULL) { \
+ config.client.classname.child_border = draw_util_hex_to_color(child_border); \
+ } else { \
+ config.client.classname.child_border = config.client.classname.background; \
+ } \
+ return; \
+ } \
} while (0)
APPLY_COLORS(focused_inactive);
+ APPLY_COLORS(focused_tab_title);
APPLY_COLORS(focused);
APPLY_COLORS(unfocused);
APPLY_COLORS(urgent);
@@ -744,10 +751,6 @@ CFGFUN(bar_finish) {
config.number_barconfigs++;
- /* If no font was explicitly set, we use the i3 font as default */
- if (current_bar->font == NULL && font_pattern != NULL)
- current_bar->font = sstrdup(font_pattern);
-
TAILQ_INSERT_TAIL(&barconfigs, current_bar, configs);
/* Simply reset the pointer, but don't free the resources. */
current_bar = NULL;
diff --git a/src/config_parser.c b/src/config_parser.c
index 9cba93bc..baf5848a 100644
--- a/src/config_parser.c
+++ b/src/config_parser.c
@@ -165,8 +165,6 @@ static void clear_stack(struct stack *ctx) {
static void next_state(const cmdp_token *token, struct parser_ctx *ctx) {
cmdp_state _next_state = token->next_state;
- //printf("token = name %s identifier %s\n", token->name, token->identifier);
- //printf("next_state = %d\n", token->next_state);
if (token->next_state == __CALL) {
struct ConfigResultIR subcommand_output = {
.ctx = ctx,
@@ -254,7 +252,6 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
bool token_handled;
linecnt = 1;
-// TODO: make this testable
#ifndef TEST_PARSER
struct ConfigResultIR subcommand_output = {
.ctx = ctx,
@@ -270,8 +267,6 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
walk++;
- //printf("remaining input: %s\n", walk);
-
cmdp_token_ptr *ptr = &(tokens[ctx->state]);
token_handled = false;
for (c = 0; c < ptr->n; c++) {
@@ -378,7 +373,6 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
}
if (strcmp(token->name, "end") == 0) {
- //printf("checking for end: *%s*\n", walk);
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
next_state(token, ctx);
token_handled = true;
@@ -386,7 +380,6 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
* datastructure for commands which do *not* specify any
* criteria, we re-initialize the criteria system after
* every command. */
-// TODO: make this testable
#ifndef TEST_PARSER
cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
#endif
@@ -445,7 +438,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
const char *error_line = start_of_line(walk, input);
/* Contains the same amount of characters as 'input' has, but with
- * the unparseable part highlighted using ^ characters. */
+ * the unparsable part highlighted using ^ characters. */
char *position = scalloc(strlen(error_line) + 1, 1);
const char *copywalk;
for (copywalk = error_line;
@@ -1008,9 +1001,14 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi
char *next;
for (next = bufcopy;
next < (bufcopy + stbuf.st_size) &&
- (next = strcasestr(next, current->key)) != NULL;
- next += strlen(current->key)) {
- *next = '_';
+ (next = strcasestr(next, current->key)) != NULL;) {
+ /* We need to invalidate variables completely (otherwise we may count
+ * the same variable more than once, thus causing buffer overflow or
+ * allocation failure) with spaces (variable names cannot contain spaces) */
+ char *end = next + strlen(current->key);
+ while (next < end) {
+ *next++ = ' ';
+ }
extra_bytes += extra;
}
}
diff --git a/src/display_version.c b/src/display_version.c
index bced4c19..bfdbc8d6 100644
--- a/src/display_version.c
+++ b/src/display_version.c
@@ -69,7 +69,7 @@ static void print_config_path(const char *path, const char *role) {
/*
* Connects to i3 to find out the currently running version. Useful since it
* might be different from the version compiled into this binary (maybe the
- * user didn’t correctly install i3 or forgot te restart it).
+ * user didn’t correctly install i3 or forgot to restart it).
*
* The output looks like this:
* Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804)
diff --git a/src/floating.c b/src/floating.c
index f087832d..7888d579 100644
--- a/src/floating.c
+++ b/src/floating.c
@@ -253,7 +253,7 @@ bool floating_enable(Con *con, bool automatic) {
}
/* 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
+ * Where S_{x} is a container tree and the container 'con' that is being 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}.
diff --git a/src/handlers.c b/src/handlers.c
index 86859ebc..b0354d1c 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -70,11 +70,9 @@ bool event_is_ignored(const int sequence, const int response_type) {
event->response_type != response_type)
continue;
- /* instead of removing a sequence number we better wait until it gets
- * garbage collected. it may generate multiple events (there are multiple
- * enter_notifies for one configure_request, for example). */
- //SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events);
- //free(event);
+ /* Instead of removing & freeing a sequence number we better wait until
+ * it gets garbage collected. It may generate multiple events (there
+ * are multiple enter_notifies for one configure_request, for example). */
return true;
}
@@ -270,7 +268,7 @@ static void handle_map_request(xcb_map_request_event_t *event) {
* Configure requests are received when the application wants to resize windows
* on their own.
*
- * We generate a synthethic configure notify event to signalize the client its
+ * We generate a synthetic configure notify event to signalize the client its
* "new" position.
*
*/
@@ -801,7 +799,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
} 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;
+ /* For compatibility 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 = %08x)\n", event->window);
long data[] = {XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE};
@@ -1184,14 +1182,14 @@ static bool handle_machine_change(Con *con, xcb_get_property_reply_t *prop) {
}
/*
- * Handles the _MOTIF_WM_HINTS property of specifing window deocration settings.
+ * Handles the _MOTIF_WM_HINTS property of specifying window deocration settings.
*
*/
static bool handle_motif_hints_change(Con *con, xcb_get_property_reply_t *prop) {
border_style_t motif_border_style;
- window_update_motif_hints(con->window, prop, &motif_border_style);
+ bool has_mwm_hints = window_update_motif_hints(con->window, prop, &motif_border_style);
- if (motif_border_style != con->border_style && motif_border_style != BS_NORMAL) {
+ if (has_mwm_hints && motif_border_style != con->border_style) {
DLOG("Update border style of con %p to %d\n", con, motif_border_style);
con_set_border_style(con, motif_border_style, con->current_border_width);
@@ -1346,7 +1344,7 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom)
}
if (handler == NULL) {
- //DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom);
+ /* DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom); */
return;
}
@@ -1524,7 +1522,7 @@ void handle_event(int type, xcb_generic_event_t *event) {
break;
default:
- //DLOG("Unhandled event of type %d\n", type);
+ /* DLOG("Unhandled event of type %d\n", type); */
break;
}
}
diff --git a/src/log.c b/src/log.c
index 183bb8e6..010d2a53 100644
--- a/src/log.c
+++ b/src/log.c
@@ -137,7 +137,11 @@ void open_logbuffer(void) {
* For 512 MiB of RAM this will lead to a 5 MiB log buffer.
* At the moment (2011-12-10), no testcase leads to an i3 log
* of more than ~ 600 KiB. */
- logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size);
+ logbuffer_size = shmlog_size;
+ if (physical_mem_bytes * 0.01 < (long long)shmlog_size) {
+ logbuffer_size = physical_mem_bytes * 0.01;
+ }
+
#if defined(__FreeBSD__)
sasprintf(&shmlogname, "/tmp/i3-log-%d", getpid());
#else
diff --git a/src/main.c b/src/main.c
index c73601da..6d6b9c9c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -683,6 +683,11 @@ int main(int argc, char *argv[]) {
else
config.ipc_socket_path = sstrdup(config.ipc_socket_path);
}
+ /* Create the UNIX domain socket for IPC */
+ int ipc_socket = create_socket(config.ipc_socket_path, &current_socketpath);
+ if (ipc_socket == -1) {
+ die("Could not create the IPC socket: %s", config.ipc_socket_path);
+ }
if (config.force_xinerama) {
force_xinerama = true;
@@ -691,11 +696,11 @@ int main(int argc, char *argv[]) {
/* Acquire the WM_Sn selection. */
{
/* Get the WM_Sn atom */
- char *atom_name = xcb_atom_name_by_screen("WM_S", conn_screen);
+ char *atom_name = xcb_atom_name_by_screen("WM", conn_screen);
wm_sn_selection_owner = xcb_generate_id(conn);
if (atom_name == NULL) {
- ELOG("xcb_atom_name_by_screen(\"WM_S\", %d) failed, exiting\n", conn_screen);
+ ELOG("xcb_atom_name_by_screen(\"WM\", %d) failed, exiting\n", conn_screen);
return 1;
}
@@ -988,15 +993,10 @@ int main(int argc, char *argv[]) {
tree_render();
- /* Create the UNIX domain socket for IPC */
- int ipc_socket = create_socket(config.ipc_socket_path, &current_socketpath);
- if (ipc_socket == -1) {
- ELOG("Could not create the IPC socket, IPC disabled\n");
- } else {
- struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
- ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
- ev_io_start(main_loop, ipc_io);
- }
+ /* Listen to the IPC socket for clients */
+ struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
+ ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
+ ev_io_start(main_loop, ipc_io);
/* Chose a file name in /tmp/ based on the PID */
char *log_stream_socket_path = get_process_filename("log-stream-socket");
diff --git a/src/manage.c b/src/manage.c
index ee046f0f..56a6d0ba 100644
--- a/src/manage.c
+++ b/src/manage.c
@@ -123,7 +123,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
geomc = xcb_get_geometry(conn, d);
- /* Check if the window is mapped (it could be not mapped when intializing and
+ /* Check if the window is mapped (it could be not mapped when initializing and
calling manage_window() for every window) */
if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
DLOG("Could not get attributes\n");
@@ -214,8 +214,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL));
bool urgency_hint;
window_update_hints(cwindow, xcb_get_property_reply(conn, wm_hints_cookie, NULL), &urgency_hint);
- border_style_t motif_border_style = BS_NORMAL;
- window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
+ border_style_t motif_border_style;
+ bool has_mwm_hints = window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
window_update_normal_hints(cwindow, xcb_get_property_reply(conn, wm_normal_hints_cookie, NULL), geom);
window_update_machine(cwindow, xcb_get_property_reply(conn, wm_machine_cookie, NULL));
xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
@@ -485,34 +485,17 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
(cwindow->leader != XCB_NONE &&
cwindow->leader != cwindow->id &&
con_by_window_id(cwindow->leader) != NULL)) {
- LOG("This window is transient for another window, setting floating\n");
+ DLOG("This window is transient for another window, setting floating\n");
want_floating = true;
if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN &&
fs != NULL) {
- LOG("There is a fullscreen window, leaving fullscreen mode\n");
+ DLOG("There is a fullscreen window, leaving fullscreen mode\n");
con_toggle_fullscreen(fs, CF_OUTPUT);
} else if (config.popup_during_fullscreen == PDF_SMART &&
fs != NULL &&
fs->window != NULL) {
- i3Window *transient_win = cwindow;
- while (transient_win != NULL &&
- transient_win->transient_for != XCB_NONE) {
- if (transient_win->transient_for == fs->window->id) {
- LOG("This floating window belongs to the fullscreen window (popup_during_fullscreen == smart)\n");
- set_focus = true;
- break;
- }
- Con *next_transient = con_by_window_id(transient_win->transient_for);
- if (next_transient == NULL)
- break;
- /* Some clients (e.g. x11-ssh-askpass) actually set
- * WM_TRANSIENT_FOR to their own window id, so break instead of
- * looping endlessly. */
- if (transient_win == next_transient->window)
- break;
- transient_win = next_transient->window;
- }
+ set_focus = con_find_transient_for_window(nc, fs->window->id);
}
}
@@ -528,7 +511,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
if (nc->geometry.width == 0)
nc->geometry = (Rect){geom->x, geom->y, geom->width, geom->height};
- if (motif_border_style != BS_NORMAL) {
+ if (has_mwm_hints) {
DLOG("MOTIF_WM_HINTS specifies decorations (border_style = %d)\n", motif_border_style);
if (want_floating) {
con_set_border_style(nc, motif_border_style, config.default_floating_border_width);
diff --git a/src/move.c b/src/move.c
index 967b4c02..1a161647 100644
--- a/src/move.c
+++ b/src/move.c
@@ -228,10 +228,19 @@ static void move_to_output_directed(Con *con, direction_t direction) {
* 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. */
+ * to the old workspace.
+ *
+ * The following happen:
+ * 1. Focus con to push it on the top of the focus stack in its new
+ * workspace
+ * 2. Set focused to the old workspace to force workspace_show to
+ * execute
+ * 3. workspace_show will descend focus and target our con for
+ * focusing. This also ensures that the mouse warps correctly.
+ * See: #3518. */
+ con_focus(con);
focused = old_ws;
workspace_show(ws);
- con_focus(con);
}
/* force re-painting the indicators */
@@ -321,6 +330,9 @@ void tree_move(Con *con, direction_t direction) {
TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes);
}
+ /* redraw parents to ensure all parent split container titles are updated correctly */
+ con_force_split_parents_redraw(con);
+
ipc_send_window_event("move", con);
return;
}
diff --git a/src/randr.c b/src/randr.c
index b4d1d094..26b4ca34 100644
--- a/src/randr.c
+++ b/src/randr.c
@@ -665,11 +665,12 @@ static bool randr_query_outputs_15(void) {
new->primary = monitor_info->primary;
- new->changed =
- update_if_necessary(&(new->rect.x), monitor_info->x) |
- update_if_necessary(&(new->rect.y), monitor_info->y) |
- update_if_necessary(&(new->rect.width), monitor_info->width) |
- update_if_necessary(&(new->rect.height), monitor_info->height);
+ const bool update_x = update_if_necessary(&(new->rect.x), monitor_info->x);
+ const bool update_y = update_if_necessary(&(new->rect.y), monitor_info->y);
+ const bool update_w = update_if_necessary(&(new->rect.width), monitor_info->width);
+ const bool update_h = update_if_necessary(&(new->rect.height), monitor_info->height);
+
+ new->changed = update_x || update_y || update_w || update_h;
DLOG("name %s, x %d, y %d, width %d px, height %d px, width %d mm, height %d mm, primary %d, automatic %d\n",
name,
@@ -743,10 +744,11 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
return;
}
- bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
- update_if_necessary(&(new->rect.y), crtc->y) |
- update_if_necessary(&(new->rect.width), crtc->width) |
- update_if_necessary(&(new->rect.height), crtc->height);
+ const bool update_x = update_if_necessary(&(new->rect.x), crtc->x);
+ const bool update_y = update_if_necessary(&(new->rect.y), crtc->y);
+ const bool update_w = update_if_necessary(&(new->rect.width), crtc->width);
+ const bool update_h = update_if_necessary(&(new->rect.height), crtc->height);
+ const bool updated = update_x || update_y || update_w || update_h;
free(crtc);
new->active = (new->rect.width != 0 && new->rect.height != 0);
if (!new->active) {
@@ -943,9 +945,11 @@ void randr_query_outputs(void) {
uint32_t width = min(other->rect.width, output->rect.width);
uint32_t height = min(other->rect.height, output->rect.height);
- if (update_if_necessary(&(output->rect.width), width) |
- update_if_necessary(&(output->rect.height), height))
+ const bool update_w = update_if_necessary(&(output->rect.width), width);
+ const bool update_h = update_if_necessary(&(output->rect.height), height);
+ if (update_w || update_h) {
output->changed = true;
+ }
update_if_necessary(&(other->rect.width), width);
update_if_necessary(&(other->rect.height), height);
diff --git a/src/regex.c b/src/regex.c
index 8f039157..66ae5113 100644
--- a/src/regex.c
+++ b/src/regex.c
@@ -20,34 +20,23 @@
*
*/
struct regex *regex_new(const char *pattern) {
- const char *error;
- int errorcode, offset;
+ int errorcode;
+ PCRE2_SIZE offset;
struct regex *re = scalloc(1, sizeof(struct regex));
re->pattern = sstrdup(pattern);
- int options = PCRE_UTF8;
+ uint32_t options = PCRE2_UTF;
/* We use PCRE_UCP so that \B, \b, \D, \d, \S, \s, \W, \w and some POSIX
* character classes play nicely with Unicode */
- options |= PCRE_UCP;
- while (!(re->regex = pcre_compile2(pattern, options, &errorcode, &error, &offset, NULL))) {
- /* If the error is that PCRE was not compiled with UTF-8 support we
- * disable it and try again */
- if (errorcode == 32) {
- options &= ~PCRE_UTF8;
- continue;
- }
- ELOG("PCRE regular expression compilation failed at %d: %s\n",
- offset, error);
+ options |= PCRE2_UCP;
+ if (!(re->regex = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, options, &errorcode, &offset, NULL))) {
+ PCRE2_UCHAR buffer[256];
+ pcre2_get_error_message(errorcode, buffer, sizeof(buffer));
+ ELOG("PCRE regular expression compilation failed at %lu: %s\n",
+ offset, buffer);
regex_free(re);
return NULL;
}
- re->extra = pcre_study(re->regex, 0, &error);
- /* If an error happened, we print the error message, but continue.
- * Studying the regular expression leads to faster matching, but it’s not
- * absolutely necessary. */
- if (error) {
- ELOG("PCRE regular expression studying failed: %s\n", error);
- }
return re;
}
@@ -60,7 +49,6 @@ void regex_free(struct regex *regex) {
return;
FREE(regex->pattern);
FREE(regex->regex);
- FREE(regex->extra);
FREE(regex);
}
@@ -71,17 +59,22 @@ void regex_free(struct regex *regex) {
*
*/
bool regex_matches(struct regex *regex, const char *input) {
+ pcre2_match_data *match_data;
int rc;
+ match_data = pcre2_match_data_create_from_pattern(regex->regex, NULL);
+
/* We use strlen() because pcre_exec() expects the length of the input
* string in bytes */
- if ((rc = pcre_exec(regex->regex, regex->extra, input, strlen(input), 0, 0, NULL, 0)) == 0) {
+ rc = pcre2_match(regex->regex, (PCRE2_SPTR)input, strlen(input), 0, 0, match_data, NULL);
+ pcre2_match_data_free(match_data);
+ if (rc > 0) {
LOG("Regular expression \"%s\" matches \"%s\"\n",
regex->pattern, input);
return true;
}
- if (rc == PCRE_ERROR_NOMATCH) {
+ if (rc == PCRE2_ERROR_NOMATCH) {
LOG("Regular expression \"%s\" does not match \"%s\"\n",
regex->pattern, input);
return false;
diff --git a/src/render.c b/src/render.c
index cf4fd45f..cc1c01f8 100644
--- a/src/render.c
+++ b/src/render.c
@@ -226,34 +226,12 @@ static void render_root(Con *con, Con *fullscreen) {
}
Con *floating_child = con_descend_focused(child);
- Con *transient_con = floating_child;
- bool is_transient_for = false;
- while (transient_con != NULL &&
- transient_con->window != NULL &&
- transient_con->window->transient_for != XCB_NONE) {
- DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, fullscreen_id = 0x%08x\n",
- transient_con->window->id, transient_con->window->transient_for, fullscreen->window->id);
- if (transient_con->window->transient_for == fullscreen->window->id) {
- is_transient_for = true;
- break;
- }
- Con *next_transient = con_by_window_id(transient_con->window->transient_for);
- if (next_transient == NULL)
- break;
- /* Some clients (e.g. x11-ssh-askpass) actually set
- * WM_TRANSIENT_FOR to their own window id, so break instead of
- * looping endlessly. */
- if (transient_con == next_transient)
- break;
- transient_con = next_transient;
- }
-
- if (!is_transient_for)
- continue;
- else {
+ if (con_find_transient_for_window(floating_child, fullscreen->window->id)) {
DLOG("Rendering floating child even though in fullscreen mode: "
"floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n",
floating_child->window->transient_for, fullscreen->window->id);
+ } else {
+ continue;
}
}
DLOG("floating child at (%d,%d) with %d x %d\n",
diff --git a/src/tiling_drag.c b/src/tiling_drag.c
new file mode 100644
index 00000000..cce91f29
--- /dev/null
+++ b/src/tiling_drag.c
@@ -0,0 +1,385 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * tiling_drag.c: Reposition tiled windows by dragging.
+ *
+ */
+#include "all.h"
+static xcb_window_t create_drop_indicator(Rect rect);
+
+/*
+ * Includes decoration (container title) to the container's rect. This way we
+ * can find the correct drop target if the mouse is on a container's
+ * decoration.
+ *
+ */
+static Rect con_rect_plus_deco_height(Con *con) {
+ Rect rect = con->rect;
+ rect.height += con->deco_rect.height;
+ if (rect.y < con->deco_rect.height) {
+ rect.y = 0;
+ } else {
+ rect.y -= con->deco_rect.height;
+ }
+ return rect;
+}
+
+/*
+ * Return an appropriate target at given coordinates.
+ *
+ */
+static Con *find_drop_target(uint32_t x, uint32_t y) {
+ Con *con;
+ TAILQ_FOREACH (con, &all_cons, all_cons) {
+ Rect rect = con_rect_plus_deco_height(con);
+
+ if (rect_contains(rect, x, y) &&
+ con_has_managed_window(con) &&
+ !con_is_floating(con) &&
+ !con_is_hidden(con)) {
+ Con *ws = con_get_workspace(con);
+ if (!workspace_is_visible(ws)) {
+ continue;
+ }
+ Con *fs = con_get_fullscreen_covering_ws(ws);
+ return fs ? fs : con;
+ }
+ }
+
+ /* Couldn't find leaf container, get a workspace. */
+ Output *output = get_output_containing(x, y);
+ if (!output) {
+ return NULL;
+ }
+ Con *content = output_get_content(output->con);
+ /* Still descend because you can drag to the bar on an non-empty workspace. */
+ return con_descend_tiling_focused(content);
+}
+
+typedef enum { DT_SIBLING,
+ DT_CENTER,
+ DT_PARENT
+} drop_type_t;
+
+struct callback_params {
+ xcb_window_t *indicator;
+ Con **target;
+ direction_t *direction;
+ drop_type_t *drop_type;
+};
+
+static Rect adjust_rect(Rect rect, direction_t direction, uint32_t threshold) {
+ switch (direction) {
+ case D_LEFT:
+ rect.width = threshold;
+ break;
+ case D_UP:
+ rect.height = threshold;
+ break;
+ case D_RIGHT:
+ rect.x += (rect.width - threshold);
+ rect.width = threshold;
+ break;
+ case D_DOWN:
+ rect.y += (rect.height - threshold);
+ rect.height = threshold;
+ break;
+ }
+ return rect;
+}
+
+static bool con_on_side_of_parent(Con *con, direction_t direction) {
+ const orientation_t orientation = orientation_from_direction(direction);
+ direction_t reverse_direction;
+ switch (direction) {
+ case D_LEFT:
+ reverse_direction = D_RIGHT;
+ break;
+ case D_RIGHT:
+ reverse_direction = D_LEFT;
+ break;
+ case D_UP:
+ reverse_direction = D_DOWN;
+ break;
+ case D_DOWN:
+ reverse_direction = D_UP;
+ break;
+ }
+ return (con_orientation(con->parent) != orientation ||
+ con->parent->layout == L_STACKED || con->parent->layout == L_TABBED ||
+ con_descend_direction(con->parent, reverse_direction) == con);
+}
+
+/*
+ * The callback that is executed on every mouse move while dragging. On each
+ * invocation we determine the drop target and the direction in which to insert
+ * the dragged container. The indicator window is updated to show the new
+ * position of the dragged container. The target container and direction are
+ * passed out using the callback params.
+ *
+ */
+DRAGGING_CB(drag_callback) {
+ /* 30% of the container (minus the parent indicator) is used to drop the
+ * dragged container as a sibling to the target */
+ const double sibling_indicator_percent_of_rect = 0.3;
+ /* Use the base decoration height and add a few pixels. This makes the
+ * outer indicator generally thin but at least thick enough to cover
+ * container titles */
+ const double parent_indicator_max_size = render_deco_height() + logical_px(5);
+
+ Con *target = find_drop_target(new_x, new_y);
+ if (target == NULL) {
+ return;
+ }
+
+ Rect rect = con_rect_plus_deco_height(target);
+
+ direction_t direction = 0;
+ drop_type_t drop_type = DT_CENTER;
+ bool draw_window = true;
+ const struct callback_params *params = extra;
+
+ if (target->type == CT_WORKSPACE) {
+ goto create_indicator;
+ }
+
+ /* Define the thresholds in pixels. The drop type depends on the cursor
+ * position. */
+ const uint32_t min_rect_dimension = min(rect.width, rect.height);
+ const uint32_t sibling_indicator_size = max(logical_px(2), (uint32_t)(sibling_indicator_percent_of_rect * min_rect_dimension));
+ const uint32_t parent_indicator_size = min(
+ parent_indicator_max_size,
+ /* For small containers, start where the sibling indicator finishes.
+ * This is always at least 1 pixel. We use min() to not override the
+ * sibling indicator: */
+ sibling_indicator_size - 1);
+
+ /* Find which edge the cursor is closer to. */
+ const uint32_t d_left = new_x - rect.x;
+ const uint32_t d_top = new_y - rect.y;
+ const uint32_t d_right = rect.x + rect.width - new_x;
+ const uint32_t d_bottom = rect.y + rect.height - new_y;
+ const uint32_t d_min = min(min(d_left, d_right), min(d_top, d_bottom));
+ /* And move the container towards that direction. */
+ if (d_left == d_min) {
+ direction = D_LEFT;
+ } else if (d_top == d_min) {
+ direction = D_UP;
+ } else if (d_right == d_min) {
+ direction = D_RIGHT;
+ } else if (d_bottom == d_min) {
+ direction = D_DOWN;
+ } else {
+ /* Keep the compiler happy */
+ ELOG("min() is broken\n");
+ assert(false);
+ }
+ const bool target_parent = (d_min < parent_indicator_size &&
+ con_on_side_of_parent(target, direction));
+ const bool target_sibling = (d_min < sibling_indicator_size);
+ drop_type = target_parent ? DT_PARENT : (target_sibling ? DT_SIBLING : DT_CENTER);
+
+ /* target == con makes sense only when we are moving away from target's parent. */
+ if (drop_type != DT_PARENT && target == con) {
+ draw_window = false;
+ xcb_destroy_window(conn, *(params->indicator));
+ *(params->indicator) = 0;
+ goto create_indicator;
+ }
+
+ switch (drop_type) {
+ case DT_PARENT:
+ while (target->parent->type != CT_WORKSPACE && con_on_side_of_parent(target->parent, direction)) {
+ target = target->parent;
+ }
+ rect = adjust_rect(target->parent->rect, direction, parent_indicator_size);
+ break;
+ case DT_CENTER:
+ rect = target->rect;
+ rect.x += sibling_indicator_size;
+ rect.y += sibling_indicator_size;
+ rect.width -= sibling_indicator_size * 2;
+ rect.height -= sibling_indicator_size * 2;
+ break;
+ case DT_SIBLING:
+ rect = adjust_rect(target->rect, direction, sibling_indicator_size);
+ break;
+ }
+
+create_indicator:
+ if (draw_window) {
+ if (*(params->indicator) == 0) {
+ *(params->indicator) = create_drop_indicator(rect);
+ } else {
+ const uint32_t values[4] = {rect.x, rect.y, rect.width, rect.height};
+ const uint32_t mask = XCB_CONFIG_WINDOW_X |
+ XCB_CONFIG_WINDOW_Y |
+ XCB_CONFIG_WINDOW_WIDTH |
+ XCB_CONFIG_WINDOW_HEIGHT;
+ xcb_configure_window(conn, *(params->indicator), mask, values);
+ }
+ }
+ x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
+ xcb_flush(conn);
+
+ *(params->target) = target;
+ *(params->direction) = direction;
+ *(params->drop_type) = drop_type;
+}
+
+/*
+ * Returns a new drop indicator window with the given initial coordinates.
+ *
+ */
+static xcb_window_t create_drop_indicator(Rect rect) {
+ uint32_t mask = 0;
+ uint32_t values[2];
+
+ mask = XCB_CW_BACK_PIXEL;
+ values[0] = config.client.focused.indicator.colorpixel;
+
+ mask |= XCB_CW_OVERRIDE_REDIRECT;
+ values[1] = 1;
+
+ xcb_window_t indicator = create_window(conn, rect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_MOVE, false, mask, values);
+ /* Change the window class to "i3-drag", so that it can be matched in a
+ * compositor configuration. Note that the class needs to be changed before
+ * mapping the window. */
+ xcb_change_property(conn,
+ XCB_PROP_MODE_REPLACE,
+ indicator,
+ XCB_ATOM_WM_CLASS,
+ XCB_ATOM_STRING,
+ 8,
+ (strlen("i3-drag") + 1) * 2,
+ "i3-drag\0i3-drag\0");
+ xcb_map_window(conn, indicator);
+ xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, indicator);
+
+ return indicator;
+}
+
+/*
+ * Initiates a mouse drag operation on a tiled window.
+ *
+ */
+void tiling_drag(Con *con, xcb_button_press_event_t *event) {
+ DLOG("Start dragging tiled container: con = %p\n", con);
+ bool set_focus = (con == focused);
+ bool set_fs = con->fullscreen_mode != CF_NONE;
+
+ /* Don't change focus while dragging. */
+ x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
+ xcb_flush(conn);
+
+ /* Indicate drop location while dragging. This blocks until the drag is completed. */
+ Con *target = NULL;
+ direction_t direction;
+ drop_type_t drop_type;
+ xcb_window_t indicator = 0;
+ const struct callback_params params = {&indicator, &target, &direction, &drop_type};
+
+ drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP, XCURSOR_CURSOR_MOVE, drag_callback, &params);
+
+ /* Dragging is done. We don't need the indicator window any more. */
+ xcb_destroy_window(conn, indicator);
+
+ if (drag_result == DRAG_REVERT ||
+ target == NULL ||
+ (target == con && drop_type != DT_PARENT) ||
+ !con_exists(target)) {
+ DLOG("drop aborted\n");
+ return;
+ }
+
+ const orientation_t orientation = orientation_from_direction(direction);
+ const position_t position = position_from_direction(direction);
+ const layout_t layout = orientation == VERT ? L_SPLITV : L_SPLITH;
+ con_disable_fullscreen(con);
+ switch (drop_type) {
+ case DT_CENTER:
+ /* Also handles workspaces.*/
+ DLOG("drop to center of %p\n", target);
+ con_move_to_target(con, target);
+ break;
+ case DT_SIBLING:
+ DLOG("drop %s %p\n", position_to_string(position), target);
+ if (con_orientation(target->parent) != orientation) {
+ /* If con and target are the only children of the same parent, we can just change
+ * the parent's layout manually and then move con to the correct position.
+ * tree_split checks for a parent with only one child so it would create a new
+ * parent with the new layout. */
+ if (con->parent == target->parent && con_num_children(target->parent) == 2) {
+ target->parent->layout = layout;
+ } else {
+ tree_split(target, orientation);
+ }
+ }
+
+ insert_con_into(con, target, position);
+
+ ipc_send_window_event("move", con);
+ break;
+ case DT_PARENT: {
+ const bool parent_tabbed_or_stacked = (target->parent->layout == L_TABBED || target->parent->layout == L_STACKED);
+ DLOG("drop %s (%s) of %s%p\n",
+ direction_to_string(direction),
+ position_to_string(position),
+ parent_tabbed_or_stacked ? "tabbed/stacked " : "",
+ target);
+ if (parent_tabbed_or_stacked) {
+ /* When dealing with tabbed/stacked the target can be in the
+ * middle of the container. Thus, after a directional move, con
+ * will still be bound to the tabbed/stacked parent. */
+ if (position == BEFORE) {
+ target = TAILQ_FIRST(&(target->parent->nodes_head));
+ } else {
+ target = TAILQ_LAST(&(target->parent->nodes_head), nodes_head);
+ }
+ }
+ if (con != target) {
+ insert_con_into(con, target, position);
+ }
+ /* tree_move can change the focus */
+ Con *old_focus = focused;
+ tree_move(con, direction);
+ if (focused != old_focus) {
+ con_activate(old_focus);
+ }
+ break;
+ }
+ }
+ /* Warning: target might not exist anymore */
+ target = NULL;
+
+ /* Manage fullscreen status. */
+ if (set_focus || set_fs) {
+ Con *fs = con_get_fullscreen_covering_ws(con_get_workspace(con));
+ if (fs == con) {
+ ELOG("dragged container somehow got fullscreen again.\n");
+ assert(false);
+ } else if (fs && set_focus && set_fs) {
+ /* con was focused & fullscreen, disable other fullscreen container. */
+ con_disable_fullscreen(fs);
+ } else if (fs) {
+ /* con was not focused, prefer other fullscreen container. */
+ set_fs = set_focus = false;
+ } else if (!set_focus) {
+ /* con was not focused. If it was fullscreen and we are moving it to the focused
+ * workspace we must focus it. */
+ set_focus = (set_fs && con_get_workspace(focused) == con_get_workspace(con));
+ }
+ }
+ if (set_fs) {
+ con_enable_fullscreen(con, CF_OUTPUT);
+ }
+ if (set_focus) {
+ workspace_show(con_get_workspace(con));
+ con_focus(con);
+ }
+ tree_render();
+}
diff --git a/src/util.c b/src/util.c
index f2c56222..7b148614 100644
--- a/src/util.c
+++ b/src/util.c
@@ -476,3 +476,35 @@ direction_t direction_from_orientation_position(orientation_t orientation, posit
return position == BEFORE ? D_UP : D_DOWN;
}
}
+
+/*
+ * Converts direction to a string representation.
+ *
+ */
+const char *direction_to_string(direction_t direction) {
+ switch (direction) {
+ case D_LEFT:
+ return "left";
+ case D_RIGHT:
+ return "right";
+ case D_UP:
+ return "up";
+ case D_DOWN:
+ return "down";
+ }
+ return "invalid";
+}
+
+/*
+ * Converts position to a string representation.
+ *
+ */
+const char *position_to_string(position_t position) {
+ switch (position) {
+ case BEFORE:
+ return "before";
+ case AFTER:
+ return "after";
+ }
+ return "invalid";
+}
diff --git a/src/window.c b/src/window.c
index 734a3264..7c65ceea 100644
--- a/src/window.c
+++ b/src/window.c
@@ -415,12 +415,10 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
* it is still in use by popular widget toolkits such as GTK+ and Java AWT.
*
*/
-void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
- /* This implementation simply mirrors Gnome's Metacity. Official
- * documentation of this hint is nowhere to be found.
- * For more information see:
- * https://people.gnome.org/~tthurman/docs/metacity/xprops_8h-source.html
- * https://stackoverflow.com/questions/13787553/detect-if-a-x11-window-has-decorations
+bool window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
+ /* See `man VendorShell' for more info, `XmNmwmDecorations' section:
+ * https://linux.die.net/man/3/vendorshell
+ * The following constants are adapted from <Xm/MwmUtil.h>.
*/
#define MWM_HINTS_FLAGS_FIELD 0
#define MWM_HINTS_DECORATIONS_FIELD 2
@@ -435,7 +433,7 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
FREE(prop);
- return;
+ return false;
}
/* The property consists of an array of 5 uint32_t's. The first value is a
@@ -461,6 +459,7 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
}
FREE(prop);
+ return true;
#undef MWM_HINTS_FLAGS_FIELD
#undef MWM_HINTS_DECORATIONS_FIELD
diff --git a/src/workspace.c b/src/workspace.c
index 739358a8..e1ac49d3 100644
--- a/src/workspace.c
+++ b/src/workspace.c
@@ -299,16 +299,16 @@ Con *create_workspace_on_output(Output *output, Con *content) {
/*
* Returns true if the workspace is currently visible. Especially important for
- * multi-monitor environments, as they can have multiple currenlty active
+ * multi-monitor environments, as they can have multiple currently active
* workspaces.
*
*/
bool workspace_is_visible(Con *ws) {
Con *output = con_get_output(ws);
- if (output == NULL)
+ if (output == NULL) {
return false;
+ }
Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
- LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
return (fs == ws);
}
diff --git a/src/x.c b/src/x.c
index cdc29876..42df9966 100644
--- a/src/x.c
+++ b/src/x.c
@@ -490,14 +490,20 @@ void x_draw_decoration(Con *con) {
struct deco_render_params *p = scalloc(1, sizeof(struct deco_render_params));
/* find out which colors to use */
- if (con->urgent)
+ if (con->urgent) {
p->color = &config.client.urgent;
- else if (con == focused || con_inside_focused(con))
+ } else if (con == focused || con_inside_focused(con)) {
p->color = &config.client.focused;
- else if (con == TAILQ_FIRST(&(parent->focus_head)))
- p->color = &config.client.focused_inactive;
- else
+ } else if (con == TAILQ_FIRST(&(parent->focus_head))) {
+ if (config.client.got_focused_tab_title && !leaf && con_descend_focused(con) == focused) {
+ /* Stacked/tabbed parent of focused container */
+ p->color = &config.client.focused_tab_title;
+ } else {
+ p->color = &config.client.focused_inactive;
+ }
+ } else {
p->color = &config.client.unfocused;
+ }
p->border_style = con_border_style(con);
@@ -875,7 +881,6 @@ void x_push_node(Con *con) {
con_state *state;
Rect rect = con->rect;
- //DLOG("Pushing changes for node %p / %s\n", con, con->name);
state = state_for_frame(con->frame.id);
if (state->name != NULL) {
@@ -988,14 +993,16 @@ void x_push_node(Con *con) {
win_depth = con->window->depth;
/* Ensure we have valid dimensions for our surface. */
- // TODO This is probably a bug in the condition above as we should never enter this path
- // for height == 0. Also, we should probably handle width == 0 the same way.
+ /* TODO: This is probably a bug in the condition above as we should
+ * never enter this path for height == 0. Also, we should probably
+ * handle width == 0 the same way. */
int width = MAX((int32_t)rect.width, 1);
int height = MAX((int32_t)rect.height, 1);
xcb_create_pixmap(conn, win_depth, con->frame_buffer.id, con->frame.id, width, height);
draw_util_surface_init(conn, &(con->frame_buffer), con->frame_buffer.id,
get_visualtype_by_id(get_visualid_by_depth(win_depth)), width, height);
+ draw_util_clear_surface(&(con->frame_buffer), (color_t){.red = 0.0, .green = 0.0, .blue = 0.0});
/* For the graphics context, we disable GraphicsExposure events.
* Those will be sent when a CopyArea request cannot be fulfilled
@@ -1008,8 +1015,8 @@ void x_push_node(Con *con) {
con->pixmap_recreated = true;
/* Don’t render the decoration for windows inside a stack which are
- * not visible right now */
- // TODO Should this work the same way for L_TABBED?
+ * not visible right now
+ * TODO: Should this work the same way for L_TABBED? */
if (!con->parent ||
con->parent->layout != L_STACKED ||
TAILQ_FIRST(&(con->parent->focus_head)) == con)
@@ -1120,7 +1127,6 @@ static void x_push_node_unmaps(Con *con) {
Con *current;
con_state *state;
- //DLOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name);
state = state_for_frame(con->frame.id);
/* map/unmap if map state changed, also ensure that the child window
@@ -1196,7 +1202,6 @@ void x_push_changes(Con *con) {
}
DLOG("-- PUSHING WINDOW STACK --\n");
- //DLOG("Disabling EnterNotify\n");
/* We need to keep SubstructureRedirect around, otherwise clients can send
* ConfigureWindow requests and get them applied directly instead of having
* them become ConfigureRequests that i3 handles. */
@@ -1205,7 +1210,6 @@ void x_push_changes(Con *con) {
if (state->mapped)
xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
}
- //DLOG("Done, EnterNotify disabled\n");
bool order_changed = false;
bool stacking_changed = false;
@@ -1235,14 +1239,12 @@ void x_push_changes(Con *con) {
if (con_has_managed_window(state->con))
memcpy(walk++, &(state->con->window->id), sizeof(xcb_window_t));
- //DLOG("stack: 0x%08x\n", state->id);
con_state *prev = CIRCLEQ_PREV(state, state);
con_state *old_prev = CIRCLEQ_PREV(state, old_state);
if (prev != old_prev)
order_changed = true;
if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) {
stacking_changed = true;
- //DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id);
uint32_t mask = 0;
mask |= XCB_CONFIG_WINDOW_SIBLING;
mask |= XCB_CONFIG_WINDOW_STACK_MODE;
@@ -1295,13 +1297,11 @@ void x_push_changes(Con *con) {
warp_to = NULL;
}
- //DLOG("Re-enabling EnterNotify\n");
values[0] = FRAME_EVENT_MASK;
CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) {
if (state->mapped)
xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
}
- //DLOG("Done, EnterNotify re-enabled\n");
x_deco_recurse(con);
@@ -1387,9 +1387,6 @@ void x_push_changes(Con *con) {
CIRCLEQ_REMOVE(&old_state_head, state, old_state);
CIRCLEQ_INSERT_TAIL(&old_state_head, state, old_state);
}
- //CIRCLEQ_FOREACH(state, &old_state_head, old_state) {
- // DLOG("old stack: 0x%08x\n", state->id);
- //}
xcb_flush(conn);
}
@@ -1402,7 +1399,6 @@ void x_push_changes(Con *con) {
void x_raise_con(Con *con) {
con_state *state;
state = state_for_frame(con->frame.id);
- //DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id);
CIRCLEQ_REMOVE(&state_head, state, state);
CIRCLEQ_INSERT_HEAD(&state_head, state, state);
diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c
index 9c0b73d4..75cbf835 100644
--- a/testcases/inject_randr1.5.c
+++ b/testcases/inject_randr1.5.c
@@ -433,7 +433,7 @@ int main(int argc, char *argv[]) {
bound = true;
/* Let the user know bind() was successful, so that they know the
* error messages can be disregarded. */
- fprintf(stderr, "Successfuly bound to %s\n", addr.sun_path);
+ fprintf(stderr, "Successfully bound to %s\n", addr.sun_path);
sun_path = sstrdup(addr.sun_path);
break;
}
diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in
index 0f5252a6..6d73afca 100644
--- a/testcases/lib/i3test.pm.in
+++ b/testcases/lib/i3test.pm.in
@@ -42,6 +42,7 @@ our @EXPORT = qw(
exit_forcefully
workspace_exists
focused_ws
+ focused_output
get_socket_path
launch_with_config
get_i3_log
@@ -664,6 +665,25 @@ sub workspace_exists {
(scalar grep { $_ eq $name } @{get_workspace_names()}) > 0;
}
+=head2 focused_output
+
+Returns the name of the currently focused output.
+
+ is(focused_output, 'fake-0', 'i3 starts on output 0');
+
+=cut
+sub _focused_output {
+ my $i3 = i3(get_socket_path());
+ my $tree = $i3->get_tree->recv;
+ my $focused = $tree->{focus}->[0];
+ my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
+ return $output;
+}
+
+sub focused_output {
+ return _focused_output->{name}
+}
+
=head2 focused_ws
Returns the name of the currently focused workspace.
@@ -672,11 +692,9 @@ Returns the name of the currently focused workspace.
is($ws, '1', 'i3 starts on workspace 1');
=cut
+
sub focused_ws {
- my $i3 = i3(get_socket_path());
- my $tree = $i3->get_tree->recv;
- my $focused = $tree->{focus}->[0];
- my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
+ my $output = _focused_output;
my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
return $first->{name}
@@ -780,6 +798,8 @@ sub exit_gracefully {
my ($pid, $socketpath) = @_;
$socketpath ||= get_socket_path();
+ $SIG{CHLD} = undef;
+
my $exited = 0;
eval {
say "Exiting i3 cleanly...";
@@ -818,6 +838,8 @@ sub exit_forcefully {
my ($pid, $signal) = @_;
$signal ||= 'TERM';
+ $SIG{CHLD} = undef;
+
# Send the given signal to the i3 instance and wait for up to 10s
# for it to terminate.
kill($signal, $pid)
@@ -941,6 +963,18 @@ sub launch_with_config {
return ${^CHILD_ERROR_NATIVE};
}
+ $SIG{CHLD} = sub {
+ # don't change $! and $? outside handler
+ local ($!, $?);
+
+ my $child = waitpid -1, POSIX::WNOHANG;
+ warn "SIGCHLD, waitpid() = $child";
+ if ($child == $i3_pid) {
+ warn "i3 died, exiting!";
+ exit 1;
+ }
+ };
+
# force update of the cached socket path in lib/i3test
# as soon as i3 has started
$cv->cb(sub { get_socket_path(0) });
@@ -972,6 +1006,8 @@ sub kill_all_windows {
# Sync in case not all windows are managed by i3 just yet.
sync_with_i3;
cmd '[title=".*"] kill';
+ # Sync to make sure x_window_kill() calls have taken effect.
+ sync_with_i3;
}
=head2 events_for($subscribecb, [ $rettype ], [ $eventcbs ])
diff --git a/testcases/t/117-workspace.t b/testcases/t/117-workspace.t
index 48a0dbf6..6345ae68 100644
--- a/testcases/t/117-workspace.t
+++ b/testcases/t/117-workspace.t
@@ -14,7 +14,7 @@
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
-# Tests whether we can switch to a non-existant workspace
+# Tests whether we can switch to a non-existent workspace
# (necessary for further tests)
#
use List::Util qw(first);
diff --git a/testcases/t/171-config-migrate.t b/testcases/t/171-config-migrate.t
index 13633327..99158eab 100644
--- a/testcases/t/171-config-migrate.t
+++ b/testcases/t/171-config-migrate.t
@@ -267,7 +267,7 @@ like($output->[2], qr|^bindsym Mod1\+f resize shrink right 20 px$|, 'resize righ
like($output->[3], qr|^bindsym Mod1\+f resize grow down 23 px$|, 'resize bottom changed');
#####################################################################
-# also resizing, but with indention this time
+# also resizing, but with indentation this time
#####################################################################
like($output->[4], qr|^bindsym Mod1\+f resize grow left 10 px$|, 'resize left changed');
diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t
index a87a7b89..3a21a8a0 100644
--- a/testcases/t/201-config-parser.t
+++ b/testcases/t/201-config-parser.t
@@ -476,16 +476,18 @@ is(parser_calls($config),
################################################################################
$config = <<'EOT';
-client.focused #4c7899 #285577 #ffffff #2e9ef4 #b34d4c
-client.focused_inactive #333333 #5f676a #ffffff #484e50
-client.unfocused #333333 #222222 #888888 #292d2e
-client.urgent #2f343a #900000 #ffffff #900000 #c00000
-client.placeholder #000000 #0c0c0c #ffffff #000000
+client.focused #4c7899 #285577 #ffffff #2e9ef4 #b34d4c
+client.focused_inactive #333333 #5f676a #ffffff #484e50
+client.focused_tab_title #444444 #555555 #ffffff
+client.unfocused #333333 #222222 #888888 #292d2e
+client.urgent #2f343a #900000 #ffffff #900000 #c00000
+client.placeholder #000000 #0c0c0c #ffffff #000000
EOT
$expected = <<'EOT';
cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4, #b34d4c)
cfg_color(client.focused_inactive, #333333, #5f676a, #ffffff, #484e50, NULL)
+cfg_color(client.focused_tab_title, #444444, #555555, #ffffff, NULL, NULL)
cfg_color(client.unfocused, #333333, #222222, #888888, #292d2e, NULL)
cfg_color(client.urgent, #2f343a, #900000, #ffffff, #900000, #c00000)
cfg_color(client.placeholder, #000000, #0c0c0c, #ffffff, #000000, NULL)
@@ -551,6 +553,7 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
exec
client.background
client.focused_inactive
+ client.focused_tab_title
client.focused
client.unfocused
client.urgent
diff --git a/testcases/t/210-mark-unmark.t b/testcases/t/210-mark-unmark.t
index 0d9a073b..166ca23f 100644
--- a/testcases/t/210-mark-unmark.t
+++ b/testcases/t/210-mark-unmark.t
@@ -71,7 +71,7 @@ cmd 'focus left';
cmd 'mark left';
#
-# get_marks replys an array of marks, whose order is undefined,
+# get_marks replies an array of marks, whose order is undefined,
# so we use sort to be able to compare the output
#
diff --git a/testcases/t/219-ipc-window-focus.t b/testcases/t/219-ipc-window-focus.t
index 696fc7b2..770536f3 100644
--- a/testcases/t/219-ipc-window-focus.t
+++ b/testcases/t/219-ipc-window-focus.t
@@ -18,7 +18,7 @@ use i3test i3_config => <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-# fake-1 under fake-0 to not interfere with left/right wraping
+# fake-1 under fake-0 to not interfere with left/right wrapping
fake-outputs 1024x768+0+0,1024x768+0+1024
workspace X output fake-1
EOT
@@ -56,7 +56,11 @@ sub kill_subtest {
my $focus = AnyEvent->condvar;
my @events = events_for(
- sub { cmd $cmd },
+ sub {
+ cmd $cmd;
+ # Sync to make sure x_window_kill() calls have taken effect.
+ sync_with_i3;
+ },
'window');
is(scalar @events, 1, 'Received 1 event');
diff --git a/testcases/t/247-config-line-continuation.t b/testcases/t/247-config-line-continuation.t
index 8098b998..c54f1ac4 100644
--- a/testcases/t/247-config-line-continuation.t
+++ b/testcases/t/247-config-line-continuation.t
@@ -62,7 +62,7 @@ $config = <<'EOT';
font \
-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-# Use line contiuation with too many lines (>4096 characters).
+# Use line continuation with too many lines (>4096 characters).
# This config is invalid. Use it to ensure no buffer overflow.
bindsym Mod1+b \
0001-This is a very very very very very very very very very very very very very very very very very long cmd \
diff --git a/testcases/t/289-ipc-shutdown-event.t b/testcases/t/289-ipc-shutdown-event.t
index 606474e2..a56c4ac7 100644
--- a/testcases/t/289-ipc-shutdown-event.t
+++ b/testcases/t/289-ipc-shutdown-event.t
@@ -21,7 +21,9 @@
#
# Ticket: #2318
# Bug still in: 4.12-46-g2123888
-use i3test;
+use i3test i3_autostart => 0;
+
+my $pid = launch_with_config('-default');
# We cannot use events_for in this test as we cannot send events after
# issuing the restart/shutdown command.
@@ -59,7 +61,7 @@ $i3->subscribe({
}
})->recv;
-cmd 'exit';
+exit_gracefully($pid);
$e = $cv->recv;
diff --git a/testcases/t/294-focus-order.t b/testcases/t/294-focus-order.t
index ce790019..dfadd0af 100644
--- a/testcases/t/294-focus-order.t
+++ b/testcases/t/294-focus-order.t
@@ -14,7 +14,7 @@
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
-# Verify that the corrent focus stack order is preserved after various
+# Verify that the current focus stack order is preserved after various
# operations.
use i3test i3_config => <<EOT;
# i3 config file (v4)
diff --git a/testcases/t/301-shape.t b/testcases/t/301-shape.t
index ac0ec5a7..b18ac0ca 100644
--- a/testcases/t/301-shape.t
+++ b/testcases/t/301-shape.t
@@ -43,9 +43,9 @@ void init_ctx(void *connptr) {
* | C |
* +-------+
*
- * - Zone A is completly opaque.
+ * - Zone A is completely opaque.
* - Zone B is clickable through (input shape).
- * - Zone C is completly transparent (bounding shape).
+ * - Zone C is completely transparent (bounding shape).
*/
void set_shape(long window_id) {
xcb_rectangle_t bounding_rectangle = { 0, 0, 100, 50 };
diff --git a/testcases/t/304-ipc-workspace-init.t b/testcases/t/304-ipc-workspace-init.t
index 0eb838dc..4c181f7c 100644
--- a/testcases/t/304-ipc-workspace-init.t
+++ b/testcases/t/304-ipc-workspace-init.t
@@ -22,7 +22,7 @@ use i3test i3_config => <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-# fake-1 under fake-0 to not interfere with left/right wraping
+# fake-1 under fake-0 to not interfere with left/right wrapping
fake-outputs 1024x768+0+0,1024x768+0+1024
workspace X output fake-1
EOT
diff --git a/testcases/t/314-window-icon-padding.t b/testcases/t/314-window-icon-padding.t
index 5320ab9f..71cf5bd5 100644..100755
--- a/testcases/t/314-window-icon-padding.t
+++ b/testcases/t/314-window-icon-padding.t
@@ -38,6 +38,36 @@ is(window_icon_padding($tmp), -1, 'window_icon_padding defaults to -1');
cmd 'title_window_icon on';
isnt(window_icon_padding($tmp), -1, 'window_icon_padding no longer -1');
+cmd 'title_window_icon toggle';
+is(window_icon_padding($tmp), -1, 'window_icon_padding back to -1');
+
+cmd 'title_window_icon toggle';
+isnt(window_icon_padding($tmp), -1, 'window_icon_padding no longer -1 again');
+
+cmd 'title_window_icon off';
+is(window_icon_padding($tmp), -1, 'window_icon_padding back to -1');
+
+cmd 'title_window_icon padding 3px';
+is(window_icon_padding($tmp), 3, 'window_icon_padding set to 3');
+
+cmd 'title_window_icon toggle';
+ok(window_icon_padding($tmp) < 0, 'window_icon_padding toggled off');
+
+cmd 'title_window_icon toggle';
+is(window_icon_padding($tmp), 3, 'window_icon_padding toggled back to 3');
+
+cmd 'title_window_icon toggle 5px';
+ok(window_icon_padding($tmp) < 0, 'window_icon_padding toggled off');
+
+cmd 'title_window_icon toggle 5px';
+is(window_icon_padding($tmp), 5, 'window_icon_padding toggled on to 5px');
+
+cmd 'title_window_icon toggle 5px';
+ok(window_icon_padding($tmp) < 0, 'window_icon_padding toggled off');
+
+cmd 'title_window_icon toggle 4px';
+is(window_icon_padding($tmp), 4, 'window_icon_padding toggled on to 4px');
+
exit_gracefully($pid);
################################################################################
diff --git a/testcases/t/315-long-commands.t b/testcases/t/315-long-commands.t
new file mode 100644
index 00000000..3db37dab
--- /dev/null
+++ b/testcases/t/315-long-commands.t
@@ -0,0 +1,46 @@
+#!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 commands with more than 10 non-identified words doesn't works
+# 10 is the magic number chosen for the stack size which is why it's used here
+# Ticket: #2968
+# Bug still in: 4.19.2-103-gfc65ca36
+use i3test;
+
+######################################################################
+# 1) run a long command
+######################################################################
+
+my $i3 = i3(get_socket_path());
+my $tmp = fresh_workspace;
+
+my $floatwin = open_floating_window;
+
+
+my ($absolute_before, $top_before) = $floatwin->rect;
+
+cmd 'move window container to window container to window container to left';
+
+sync_with_i3;
+
+my ($absolute, $top) = $floatwin->rect;
+
+is($absolute->x, ($absolute_before->x - 10), 'moved 10 px to the left');
+is($absolute->y, $absolute_before->y, 'y not changed');
+is($absolute->width, $absolute_before->width, 'width not changed');
+is($absolute->height, $absolute_before->height, 'height not changed');
+
+done_testing;
diff --git a/testcases/t/316-drag-container.t b/testcases/t/316-drag-container.t
new file mode 100644
index 00000000..7e2b8494
--- /dev/null
+++ b/testcases/t/316-drag-container.t
@@ -0,0 +1,321 @@
+#!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 dragging containers.
+
+my ($width, $height) = (1000, 500);
+
+my $config = <<"EOT";
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_follows_mouse no
+floating_modifier Mod1
+
+# 2 side by side outputs
+fake-outputs ${width}x${height}+0+0P,${width}x${height}+${width}+0
+
+bar {
+ output primary
+}
+EOT
+use i3test i3_autostart => 0;
+use i3test::XTEST;
+my $pid = launch_with_config($config);
+
+sub start_drag {
+ my ($pos_x, $pos_y) = @_;
+ die "Drag outside of bounds!" unless $pos_x < $width * 2 && $pos_y < $height;
+
+ $x->root->warp_pointer($pos_x, $pos_y);
+ sync_with_i3;
+
+ xtest_key_press(64); # Alt_L
+ xtest_button_press(1, $pos_x, $pos_y);
+ xtest_sync_with_i3;
+}
+
+sub end_drag {
+ my ($pos_x, $pos_y) = @_;
+ die "Drag outside of bounds!" unless $pos_x < $width * 2 && $pos_y < $height;
+
+ $x->root->warp_pointer($pos_x, $pos_y);
+ sync_with_i3;
+
+ xtest_button_release(1, $pos_x, $pos_y);
+ xtest_key_release(64); # Alt_L
+ xtest_sync_with_i3;
+}
+
+my ($ws1, $ws2);
+my ($A, $B, $tmp);
+my ($A_id, $B_id);
+
+sub move_subtest {
+ my ($cb, $win) = @_;
+
+ my @events = events_for($cb, 'window');
+ my @move = grep { $_->{change} eq 'move' } @events;
+
+ is(scalar @move, 1, 'Received 1 window::move event');
+ is($move[0]->{container}->{window}, $A->{id}, "window id matches");
+}
+
+###############################################################################
+# Drag floating container onto an empty workspace.
+###############################################################################
+
+$ws2 = fresh_workspace(output => 1);
+$ws1 = fresh_workspace(output => 0);
+$A = open_floating_window(rect => [ 30, 30, 50, 50 ]);
+
+start_drag(40, 40);
+end_drag(1050, 50);
+
+is($x->input_focus, $A->id, 'Floating window moved to the right workspace');
+is($ws2, focused_ws, 'Empty workspace focused after floating window dragged to it');
+
+###############################################################################
+# Drag tiling container onto an empty workspace.
+###############################################################################
+
+subtest "Draging tiling container onto an empty workspace produces move event", \&move_subtest,
+sub {
+
+$ws2 = fresh_workspace(output => 1);
+$ws1 = fresh_workspace(output => 0);
+$A = open_window;
+
+start_drag(50, 50);
+end_drag(1050, 50);
+
+is($x->input_focus, $A->id, 'Tiling window moved to the right workspace');
+is($ws2, focused_ws, 'Empty workspace focused after tiling window dragged to it');
+
+};
+
+###############################################################################
+# Drag tiling container onto a container that closes before the drag is
+# complete.
+###############################################################################
+
+$ws1 = fresh_workspace(output => 0);
+$A = open_window;
+open_window;
+
+start_drag(600, 300); # Start dragging the second window.
+
+# Try to place it on the first window.
+$x->root->warp_pointer(50, 50);
+sync_with_i3;
+
+cmd '[id=' . $A->id . '] kill';
+sync_with_i3;
+end_drag(50, 50);
+
+is(@{get_ws_content($ws1)}, 1, 'One container left in ws1');
+
+###############################################################################
+# Drag tiling container onto a tiling container on an other workspace.
+###############################################################################
+
+subtest "Draging tiling container onto a tiling container on an other workspace produces move event", \&move_subtest,
+sub {
+
+$ws2 = fresh_workspace(output => 1);
+open_window;
+$B_id = get_focused($ws2);
+$ws1 = fresh_workspace(output => 0);
+$A = open_window;
+$A_id = get_focused($ws1);
+
+start_drag(50, 50);
+end_drag(1500, 250); # Center of right output, inner region.
+
+is($ws2, focused_ws, 'Workspace focused after tiling window dragged to it');
+$ws2 = get_ws($ws2);
+is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
+is($ws2->{focus}[1], $B_id, 'B focused second');
+
+};
+
+###############################################################################
+# Drag tiling container onto a floating container on an other workspace.
+###############################################################################
+
+subtest "Draging tiling container onto a floating container on an other workspace produces move event", \&move_subtest,
+sub {
+
+$ws2 = fresh_workspace(output => 1);
+open_floating_window;
+$B_id = get_focused($ws2);
+$ws1 = fresh_workspace(output => 0);
+$A = open_window;
+$A_id = get_focused($ws1);
+
+start_drag(50, 50);
+end_drag(1500, 250);
+
+is($ws2, focused_ws, 'Workspace with one floating container focused after tiling window dragged to it');
+$ws2 = get_ws($ws2);
+is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
+is($ws2->{floating_nodes}[0]->{nodes}[0]->{id}, $B_id, 'B exists & floating');
+
+};
+
+###############################################################################
+# Drag tiling container onto a bar.
+###############################################################################
+
+subtest "Draging tiling container onto a bar produces move event", \&move_subtest,
+sub {
+
+$ws1 = fresh_workspace(output => 0);
+open_window;
+$B_id = get_focused($ws1);
+$ws2 = fresh_workspace(output => 1);
+$A = open_window;
+$A_id = get_focused($ws2);
+
+start_drag(1500, 250);
+end_drag(1, 498); # Bar on bottom of left output.
+
+is($ws1, focused_ws, 'Workspace focused after tiling window dragged to its bar');
+$ws1 = get_ws($ws1);
+is($ws1->{focus}[0], $A_id, 'B focused first, dragged container kept focus');
+is($ws1->{focus}[1], $B_id, 'A focused second');
+
+};
+
+###############################################################################
+# Drag an unfocused tiling container onto it's self.
+###############################################################################
+
+$ws1 = fresh_workspace(output => 0);
+open_window;
+$A_id = get_focused($ws1);
+open_window;
+$B_id = get_focused($ws1);
+
+start_drag(50, 50);
+end_drag(450, 450);
+
+$ws1 = get_ws($ws1);
+is($ws1->{focus}[0], $B_id, 'B focused first, kept focus');
+is($ws1->{focus}[1], $A_id, 'A focused second, unfocused dragged container didn\'t gain focus');
+
+###############################################################################
+# Drag an unfocused tiling container onto an occupied workspace.
+###############################################################################
+
+subtest "Draging unfocused tiling container onto an occupied workspace produces move event", \&move_subtest,
+sub {
+
+$ws1 = fresh_workspace(output => 0);
+$A = open_window;
+$A_id = get_focused($ws1);
+$ws2 = fresh_workspace(output => 1);
+open_window;
+$B_id = get_focused($ws2);
+
+start_drag(50, 50);
+end_drag(1500, 250); # Center of right output, inner region.
+
+is($ws2, focused_ws, 'Workspace remained focused after dragging unfocused container');
+$ws2 = get_ws($ws2);
+is($ws2->{focus}[0], $B_id, 'B focused first, kept focus');
+is($ws2->{focus}[1], $A_id, 'A focused second, unfocused container didn\'t steal focus');
+
+};
+
+###############################################################################
+# Drag fullscreen container onto window in same workspace.
+###############################################################################
+
+$ws1 = fresh_workspace(output => 0);
+open_window;
+$A = open_window;
+cmd 'fullscreen enable';
+
+start_drag(900, 100); # Second window
+end_drag(50, 50); # To first window
+
+is($ws1, focused_ws, 'Workspace remained focused after dragging fullscreen container');
+is_num_fullscreen($ws1, 1, 'Container still fullscreened');
+is($x->input_focus, $A->id, 'Fullscreen container still focused');
+
+###############################################################################
+# Drag unfocused fullscreen container onto window in other workspace.
+###############################################################################
+
+subtest "Draging unfocused fullscreen container onto window in other workspace produces move event", \&move_subtest,
+sub {
+
+$ws1 = fresh_workspace(output => 0);
+$A = open_window;
+cmd 'fullscreen enable';
+$ws2 = fresh_workspace(output => 1);
+open_window;
+open_window;
+
+start_drag(900, 100);
+end_drag(1000 + 500 * 0.15 + 10, 200); # left of leftmost window
+
+is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it');
+is_num_fullscreen($ws1, 0, 'No fullscreen container in first workspace');
+is_num_fullscreen($ws2, 1, 'Moved container still fullscreened');
+is($x->input_focus, $A->id, 'Fullscreen container now focused');
+$ws2 = get_ws($ws2);
+is($ws2->{nodes}->[0]->{window}, $A->id, 'Fullscreen container now leftmost window in second workspace');
+
+};
+
+###############################################################################
+# Drag unfocused fullscreen container onto left outter region of window in
+# other workspace. The container shouldn't end up in $ws2 because it was
+# dragged onto the outter region of the leftmost window. We must also check
+# that the focus remains on the other window.
+###############################################################################
+
+subtest "Draging unfocused fullscreen container onto left outter region of window in other workspace produces move event", \&move_subtest,
+sub {
+
+$ws1 = fresh_workspace(output => 0);
+open_window for (1..3);
+$A = open_window;
+$tmp = get_focused($ws1);
+cmd 'fullscreen enable';
+$ws2 = fresh_workspace(output => 1);
+$B = open_window;
+
+start_drag(990, 100); # rightmost of $ws1
+end_drag(1004, 100); # outter region of window of $ws2
+
+is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it');
+is_num_fullscreen($ws1, 1, 'Fullscreen container still in first workspace');
+is_num_fullscreen($ws2, 0, 'No fullscreen container in second workspace');
+is($x->input_focus, $B->id, 'Window of second workspace still has focus');
+is(get_focused($ws1), $tmp, 'Fullscreen container still focused in first workspace');
+$ws1 = get_ws($ws1);
+is($ws1->{nodes}->[3]->{window}, $A->id, 'Fullscreen container still rightmost window in first workspace');
+
+};
+
+exit_gracefully($pid);
+
+###############################################################################
+
+done_testing;
diff --git a/testcases/t/316-transient-for-loop.t b/testcases/t/316-transient-for-loop.t
new file mode 100644
index 00000000..336a8d8d
--- /dev/null
+++ b/testcases/t/316-transient-for-loop.t
@@ -0,0 +1,42 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Test that i3 does not get stuck in an endless loop between two windows that
+# set transient_for for each other.
+# Ticket: #4404
+# Bug still in: 4.20-69-g43e805a00
+#
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+popup_during_fullscreen smart;
+EOT
+
+my $fs = open_window;
+cmd 'fullscreen enable';
+
+my $w1 = open_window({ dont_map => 1 });
+my $w2 = open_window({ dont_map => 1 });
+
+$w1->transient_for($w2);
+$w2->transient_for($w1);
+$w1->map;
+$w2->map;
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/317-bar-config-font-order.t b/testcases/t/317-bar-config-font-order.t
new file mode 100644
index 00000000..3c1fdcdd
--- /dev/null
+++ b/testcases/t/317-bar-config-font-order.t
@@ -0,0 +1,39 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Verifies that bar config blocks get the i3-wide font configured,
+# regardless of where the font is configured in the config file
+# (before or after the bar config blocks).
+# Ticket: #5031
+# Bug still in: 4.20-105-g4db383e4
+use i3test i3_config => <<'EOT';
+# i3 config file (v4)
+
+bar {
+ # no font directive here, no i3-wide font configured (yet)
+}
+
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $i3 = i3(get_socket_path(0));
+my $bars = $i3->get_bar_config()->recv;
+
+my $bar_id = shift @$bars;
+my $bar_config = $i3->get_bar_config($bar_id)->recv;
+is($bar_config->{font}, 'fixed', 'font ok');
+
+done_testing;
diff --git a/testcases/t/317-bar-output-trailing-space.t b/testcases/t/317-bar-output-trailing-space.t
new file mode 100644
index 00000000..540d30c9
--- /dev/null
+++ b/testcases/t/317-bar-output-trailing-space.t
@@ -0,0 +1,71 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Verifies that any trailing whitespace in strings (including in
+# “bar { output <output> }” in particular) is stripped.
+# Ticket: #5064
+# Bug still in: 4.20-105-g4db383e4
+use i3test i3_autostart => 0;
+
+# Test with a single output.
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bar {
+ output anything
+}
+EOT
+
+my $pid = launch_with_config($config);
+
+my $i3 = i3(get_socket_path(0));
+my $bars = $i3->get_bar_config()->recv;
+is(@$bars, 1, 'one bar configured');
+my $bar_id = shift @$bars;
+
+my $bar_config = $i3->get_bar_config($bar_id)->recv;
+is_deeply($bar_config->{outputs}, [ 'anything' ], 'outputs do not have trailing whitespace');
+
+exit_gracefully($pid);
+
+# Test with multiple outputs for a single bar.
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bar {
+ output nospace
+ output singlespace
+}
+EOT
+
+$pid = launch_with_config($config);
+
+$i3 = i3(get_socket_path(0));
+$bars = $i3->get_bar_config()->recv;
+is(@$bars, 1, 'one bar configured');
+$bar_id = shift @$bars;
+
+$bar_config = $i3->get_bar_config($bar_id)->recv;
+is_deeply($bar_config->{outputs}, [ 'nospace', 'singlespace' ], 'outputs do not have trailing whitespace');
+
+exit_gracefully($pid);
+
+
+done_testing;
diff --git a/testcases/t/502-focus-output.t b/testcases/t/502-focus-output.t
index 118aba16..5b051de4 100644
--- a/testcases/t/502-focus-output.t
+++ b/testcases/t/502-focus-output.t
@@ -31,13 +31,6 @@ my $i3 = i3(get_socket_path());
# use 'focus output' and verify that focus gets changed appropriately
################################################################################
-sub focused_output {
- my $tree = $i3->get_tree->recv;
- my $focused = $tree->{focus}->[0];
- my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
- return $output->{name};
-}
-
sync_with_i3;
$x->root->warp_pointer(0, 0);
sync_with_i3;
diff --git a/testcases/t/506-focus-right.t b/testcases/t/506-focus-right.t
index 871bc1c8..2d8ed205 100644
--- a/testcases/t/506-focus-right.t
+++ b/testcases/t/506-focus-right.t
@@ -149,32 +149,25 @@ sync_with_i3;
# Ensure that focusing right/left works in the expected order.
############################################################################
-sub get_focused_output {
- my $tree = i3(get_socket_path())->get_tree->recv;
- my ($focused_id) = @{$tree->{focus}};
- my ($output) = grep { $_->{id} == $focused_id } @{$tree->{nodes}};
- return $output->{name};
-}
-
-is(get_focused_output(), 'fake-0', 'focus on fake-0');
+is(focused_output, 'fake-0', 'focus on fake-0');
cmd 'focus output right';
-is(get_focused_output(), 'fake-1', 'focus on fake-1');
+is(focused_output, 'fake-1', 'focus on fake-1');
cmd 'focus output right';
-is(get_focused_output(), 'fake-2', 'focus on fake-2');
+is(focused_output, 'fake-2', 'focus on fake-2');
cmd 'focus output left';
-is(get_focused_output(), 'fake-1', 'focus on fake-1');
+is(focused_output, 'fake-1', 'focus on fake-1');
cmd 'focus output left';
-is(get_focused_output(), 'fake-0', 'focus on fake-0');
+is(focused_output, 'fake-0', 'focus on fake-0');
cmd 'focus output left';
-is(get_focused_output(), 'fake-2', 'focus on fake-2 (wrapping)');
+is(focused_output, 'fake-2', 'focus on fake-2 (wrapping)');
cmd 'focus output right';
-is(get_focused_output(), 'fake-0', 'focus on fake-0 (wrapping)');
+is(focused_output, 'fake-0', 'focus on fake-0 (wrapping)');
exit_gracefully($pid);
diff --git a/testcases/t/531-fullscreen-on-given-output.t b/testcases/t/531-fullscreen-on-given-output.t
index fb14b7a0..2459f06c 100644
--- a/testcases/t/531-fullscreen-on-given-output.t
+++ b/testcases/t/531-fullscreen-on-given-output.t
@@ -55,7 +55,7 @@ sync_with_i3;
# Check that the windows are on the correct output
is_deeply(scalar $win_on_first_output->rect, $orig_rect1, "first window spans the first output");
-is_deeply(scalar $win_on_second_output->rect, $orig_rect2, "second window spans the sencond output");
+is_deeply(scalar $win_on_second_output->rect, $orig_rect2, "second window spans the second output");
# Check that both windows remained fullscreen
my $tree = i3(get_socket_path())->get_tree->recv;
diff --git a/testcases/t/543-move-workspace-to-multiple-outputs.t b/testcases/t/543-move-workspace-to-multiple-outputs.t
index b9bfabec..7b0f8c2b 100644
--- a/testcases/t/543-move-workspace-to-multiple-outputs.t
+++ b/testcases/t/543-move-workspace-to-multiple-outputs.t
@@ -14,7 +14,7 @@
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
-# Test using multiple workspaces for 'move workspace to output …'
+# Test using multiple outputs for 'move workspace to output …'
# Ticket: #4337
use i3test i3_config => <<EOT;
# i3 config file (v4)
@@ -40,6 +40,16 @@ sub is_ws {
}
###############################################################################
+# Test moving workspace to same output
+# See issue #4691
+###############################################################################
+is_ws(1, 0, 'sanity check');
+
+my $reply = cmd '[con_mark=aa] move workspace to output fake-0';
+is_ws(1, 0, 'workspace did not move');
+ok($reply->[0]->{success}, 'reply success');
+
+###############################################################################
# Test using "next" special keyword
###############################################################################
@@ -56,7 +66,7 @@ for (my $i = 1; $i < 9; $i++) {
}
###############################################################################
-# Same as above but explicitely type all the outputs
+# Same as above but explicitly type all the outputs
###############################################################################
is_ws(1, 0, 'sanity check');
diff --git a/testcases/t/544-focus-multiple-outputs.t b/testcases/t/544-focus-multiple-outputs.t
new file mode 100644
index 00000000..504b8d11
--- /dev/null
+++ b/testcases/t/544-focus-multiple-outputs.t
@@ -0,0 +1,67 @@
+#!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 using multiple output for 'focus output …'
+# Ticket: #4619
+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,1024x768+0+768,1024x768+1024+768
+EOT
+
+###############################################################################
+# Test using "next" special keyword
+###############################################################################
+
+is(focused_output, "fake-0", 'sanity check');
+
+for (my $i = 1; $i < 9; $i++) {
+ cmd 'focus output next';
+
+ my $out = $i % 4;
+ is(focused_output, "fake-$out", 'focus output next cycle');
+}
+
+###############################################################################
+# Same as above but explicitly type all the outputs
+###############################################################################
+
+is(focused_output, "fake-0", 'sanity check');
+
+for (my $i = 1; $i < 10; $i++) {
+ cmd 'focus output fake-0 fake-1 fake-2 fake-3';
+
+ my $out = $i % 4;
+ is(focused_output, "fake-$out", 'focus output next cycle');
+}
+
+###############################################################################
+# Use a subset of the outputs plus some non-existing outputs
+###############################################################################
+
+cmd 'focus output fake-1';
+is(focused_output, "fake-1", 'start from fake-1 which is not included in output list');
+
+my @order = (0, 3, 2);
+for (my $i = 0; $i < 10; $i++) {
+ cmd 'focus output doesnotexist fake-0 alsodoesnotexist fake-3 fake-2';
+
+ my $out = $order[$i % 3];
+ is(focused_output, "fake-$out", 'focus output next cycle');
+}
+
+done_testing;
diff --git a/testcases/t/545-i3-registration.t b/testcases/t/545-i3-registration.t
new file mode 100644
index 00000000..6c8d68f4
--- /dev/null
+++ b/testcases/t/545-i3-registration.t
@@ -0,0 +1,11 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests whether our WM registration is done with the correct WM_S0 selection.
+
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+my $reply = $x->get_selection_owner($x->atom(name => 'WM_S0')->id);
+ok($reply, "registration successful");
+done_testing;
diff --git a/testcases/t/546-empty-bindcommand.t b/testcases/t/546-empty-bindcommand.t
new file mode 100644
index 00000000..dae26293
--- /dev/null
+++ b/testcases/t/546-empty-bindcommand.t
@@ -0,0 +1,31 @@
+#!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 doesn't crash if the binding command is empty.
+# Ticket: #5000
+
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+bindsym X
+EOT
+
+my $pid = launch_with_config($config);
+does_i3_live;
+
+exit_gracefully($pid);
+done_testing;
diff --git a/testcases/t/547-explicit-mode-default.t b/testcases/t/547-explicit-mode-default.t
new file mode 100644
index 00000000..a966c7f6
--- /dev/null
+++ b/testcases/t/547-explicit-mode-default.t
@@ -0,0 +1,34 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Test that explicitly defined default mode doesn't cause segfault.
+# Ticket: #5006
+# Bug still in: 4.20-96-gce2665ca
+
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+mode "default" {
+ bindsym X resize
+}
+EOT
+
+my $pid = launch_with_config($config);
+does_i3_live;
+
+exit_gracefully($pid);
+done_testing;
diff --git a/testcases/t/547-nested-variables.t b/testcases/t/547-nested-variables.t
new file mode 100644
index 00000000..990a42e9
--- /dev/null
+++ b/testcases/t/547-nested-variables.t
@@ -0,0 +1,81 @@
+#!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 doesn't crash if the config contains nested variables.
+# Ticket: #5002
+
+use i3test i3_autostart => 0;
+
+#######################################################################
+# Test calloc crash
+#######################################################################
+
+my $config = <<'EOT';
+# i3 config file (v4)
+set $long_variable_name_with_short_value 1
+set $$long_variable_name_with_short_value 2
+set $$$long_variable_name_with_short_value 3
+EOT
+
+my $pid = launch_with_config($config);
+
+# ==2108678==ERROR: AddressSanitizer: requested allocation size 0xffffffffffffffe1 (0x7e8 after adjustments for alignment, red zones etc.) exceeds maximum supported size of 0x10000000000 (thread T0)
+# #0 0x7feaa9cbf411 in __interceptor_calloc /usr/src/debug/gcc/libsanitizer/asan/asan_malloc_linux.cpp:77
+# #1 0x560867c0c13d in scalloc ../libi3/safewrappers.c:29
+# #2 0x560867b7bd63 in parse_file ../src/config_parser.c:1030
+# #3 0x560867b6b1b8 in load_configuration ../src/config.c:261
+# #4 0x560867baf9cf in main ../src/main.c:677
+# #5 0x7feaa95b52cf (/usr/lib/libc.so.6+0x232cf)
+
+does_i3_live;
+
+exit_gracefully($pid);
+
+
+#######################################################################
+# Test buffer overflow
+#######################################################################
+
+$config = <<'EOT';
+# i3 config file (v4)
+set $x 1
+set $$x 2
+EOT
+
+$pid = launch_with_config($config);
+
+# ==2110007==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6070000014f2 at pc 0x558590f680ba bp 0x7ffced72b760 sp 0x7ffced72b750
+# WRITE of size 1 at 0x6070000014f2 thread T0
+# #0 0x558590f680b9 in parse_file ../src/config_parser.c:1051
+# #1 0x558590f571b8 in load_configuration ../src/config.c:261
+# #2 0x558590f9b9cf in main ../src/main.c:677
+# #3 0x7f81c61c82cf (/usr/lib/libc.so.6+0x232cf)
+# #4 0x7f81c61c8389 in __libc_start_main (/usr/lib/libc.so.6+0x23389)
+# #5 0x558590f0bd54 in _start ../sysdeps/x86_64/start.S:115
+
+# 0x6070000014f2 is located 0 bytes to the right of 66-byte region [0x6070000014b0,0x6070000014f2)
+# allocated by thread T0 here:
+# #0 0x7f81c68bf411 in __interceptor_calloc /usr/src/debug/gcc/libsanitizer/asan/asan_malloc_linux.cpp:77
+# #1 0x558590ff813d in scalloc ../libi3/safewrappers.c:29
+# #2 0x558590f67d63 in parse_file ../src/config_parser.c:1030
+# #3 0x558590f571b8 in load_configuration ../src/config.c:261
+# #4 0x558590f9b9cf in main ../src/main.c:677
+# #5 0x7f81c61c82cf (/usr/lib/libc.so.6+0x232cf)
+
+does_i3_live;
+
+exit_gracefully($pid);
+done_testing;
diff --git a/travis/check-formatting.sh b/travis/check-formatting.sh
deleted file mode 100755
index 3424513f..00000000
--- a/travis/check-formatting.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-
-set -e
-set -x
-
-clang-format-9 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
diff --git a/travis/check-spelling.pl b/travis/check-spelling.pl
index 6357e78a..52f84c5b 100755
--- a/travis/check-spelling.pl
+++ b/travis/check-spelling.pl
@@ -26,11 +26,12 @@ my $exitcode = 0;
# Whitelist for spelling errors in manpages, in case the spell checker has
# false-positives.
-my $binary_spelling_exceptions = {
- #'exmaple' => 1, # Example for how to add entries to this whitelist.
- 'betwen' => 1, # asan_flags.inc contains this spelling error.
- 'dissassemble' => 1, # https://reviews.llvm.org/D93902
-};
+my $binary_spelling_exceptions = [
+ #'exmaple', # Example for how to add entries to this whitelist.
+ 'betwen', # asan_flags.inc contains this spelling error.
+ 'dissassemble', # https://reviews.llvm.org/D93902
+ 'oT', # lintian finds this in build/i3bar when built with clang?!
+];
my @binaries = qw(
build/i3
build/i3-config-wizard
@@ -41,7 +42,7 @@ my @binaries = qw(
build/i3bar
);
for my $binary (@binaries) {
- check_spelling($profile, slurp($binary), $binary_spelling_exceptions, sub {
+ check_spelling($profile->data, slurp($binary), $binary_spelling_exceptions, sub {
my ($current, $fixed) = @_;
say STDERR qq|Binary "$binary" contains a spelling error: "$current" should be "$fixed"|;
$exitcode = 1;
@@ -50,13 +51,13 @@ for my $binary (@binaries) {
# Whitelist for spelling errors in manpages, in case the spell checker has
# false-positives.
-my $manpage_spelling_exceptions = {
-};
+my $manpage_spelling_exceptions = [
+];
for my $name (glob('build/man/*.1')) {
for my $line (split(/\n/, slurp($name))) {
next if $line =~ /^\.\\\"/o;
- check_spelling($profile, $line, $manpage_spelling_exceptions, sub {
+ check_spelling($profile->data, $line, $manpage_spelling_exceptions, sub {
my ($current, $fixed) = @_;
say STDERR qq|Manpage "$name" contains a spelling error: "$current" should be "$fixed"|;
$exitcode = 1;
diff --git a/travis/cleanup-bintray.pl b/travis/cleanup-bintray.pl
deleted file mode 100755
index e89efb1b..00000000
--- a/travis/cleanup-bintray.pl
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/usr/bin/env perl
-# vim:ts=4:sw=4:expandtab
-
-use strict;
-use warnings;
-use Data::Dumper;
-use HTTP::Tiny; # in core since v5.13.9
-use JSON::PP; # in core since v5.13.9
-use MIME::Base64; # in core since v5.7
-use v5.13;
-
-my $repo = shift;
-
-my $auth = $ENV{'BINTRAY_USER'} . ':' . $ENV{'BINTRAY_KEY'};
-die "BINTRAY_USER and/or BINTRAY_KEY environment variables not set" if $auth eq ':';
-# TODO(stapelberg): switch to putting $auth into the URL once perl-modules ≥
-# 5.20 is available on travis (Ubuntu Wily or newer).
-my $auth_header = 'Basic ' . MIME::Base64::encode_base64($auth, "");
-my $apiurl = 'https://api.bintray.com/packages/i3/' . $repo . '/i3-wm';
-my $client = HTTP::Tiny->new(
- verify_SSL => 1,
- default_headers => {
- 'authorization' => $auth_header,
- });
-my $resp = $client->get($apiurl);
-die "Getting versions failed: HTTP status $resp->{status} (content: $resp->{content})" unless $resp->{success};
-my $decoded = decode_json($resp->{content});
-my @versions = reverse sort {
- (system("/usr/bin/dpkg", "--compare-versions", "$a", "gt", "$b") == 0) ? 1 : -1
-} @{$decoded->{versions}};
-
-# Keep the most recent 5 versions.
-splice(@versions, 0, 5);
-
-for my $version (@versions) {
- say "Deleting old version $version";
- $resp = $client->request('DELETE', "$apiurl/versions/$version");
- die "Deletion of version $version failed: HTTP status $resp->{status} (content: $resp->{content})" unless $resp->{success};
-}
diff --git a/travis/travis-base-386.Dockerfile b/travis/travis-base-386.Dockerfile
index fc035034..8996e029 100644
--- a/travis/travis-base-386.Dockerfile
+++ b/travis/travis-base-386.Dockerfile
@@ -13,12 +13,11 @@ 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-9 (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 \
- build-essential clang clang-format-9 \
+ build-essential clang \
lintian && \
rm -rf /var/lib/apt/lists/*
@@ -27,3 +26,8 @@ COPY debian/control /usr/src/i3-debian-packaging/control
RUN linux32 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/*
+
+# The user outside of Docker (GitHub Actions CI runner) and inside of Docker
+# (root) are different, and newer versions of git error out in that scenario.
+# To fix this, explicitly configure /usr/src/i3 as a safe directory:
+RUN git config --global --add safe.directory /usr/src/i3
diff --git a/travis/travis-base-ubuntu-386.Dockerfile b/travis/travis-base-ubuntu-386.Dockerfile
index 747330aa..4a41fe3e 100644
--- a/travis/travis-base-ubuntu-386.Dockerfile
+++ b/travis/travis-base-ubuntu-386.Dockerfile
@@ -13,12 +13,11 @@ 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-9 (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 \
- build-essential clang clang-format-9 \
+ build-essential clang \
lintian && \
rm -rf /var/lib/apt/lists/*
@@ -27,3 +26,8 @@ COPY debian/control /usr/src/i3-debian-packaging/control
RUN linux32 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/*
+
+# The user outside of Docker (GitHub Actions CI runner) and inside of Docker
+# (root) are different, and newer versions of git error out in that scenario.
+# To fix this, explicitly configure /usr/src/i3 as a safe directory:
+RUN git config --global --add safe.directory /usr/src/i3
diff --git a/travis/travis-base-ubuntu.Dockerfile b/travis/travis-base-ubuntu.Dockerfile
index 8a728af3..8523b92c 100644
--- a/travis/travis-base-ubuntu.Dockerfile
+++ b/travis/travis-base-ubuntu.Dockerfile
@@ -13,13 +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-9 (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 \
- build-essential clang clang-format-9 \
+ build-essential clang \
lintian && \
rm -rf /var/lib/apt/lists/*
@@ -28,3 +27,8 @@ COPY debian/control /usr/src/i3-debian-packaging/control
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/*
+
+# The user outside of Docker (GitHub Actions CI runner) and inside of Docker
+# (root) are different, and newer versions of git error out in that scenario.
+# To fix this, explicitly configure /usr/src/i3 as a safe directory:
+RUN git config --global --add safe.directory /usr/src/i3
diff --git a/travis/travis-base.Dockerfile b/travis/travis-base.Dockerfile
index e5552c14..52ae0656 100644
--- a/travis/travis-base.Dockerfile
+++ b/travis/travis-base.Dockerfile
@@ -11,13 +11,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-9 (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 \
- build-essential clang clang-format-9 \
+ build-essential clang \
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/*
@@ -28,3 +27,8 @@ 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/*
+
+# The user outside of Docker (GitHub Actions CI runner) and inside of Docker
+# (root) are different, and newer versions of git error out in that scenario.
+# To fix this, explicitly configure /usr/src/i3 as a safe directory:
+RUN git config --global --add safe.directory /usr/src/i3