summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Stapelberg <michael@stapelberg.de>2020-02-17 18:27:46 +0100
committerMichael Stapelberg <michael@stapelberg.de>2020-02-17 18:27:46 +0100
commit53718d354b101d6ade703938ef01658918d5d9a7 (patch)
tree59bae7b114caa0f74d78c59b01a3cefb02b84507
parent225ebb0d588e57d7cdb4d8bd269f7f0b791c9368 (diff)
parent0dd60756af2b156b2b364190957a862715aa16a3 (diff)
downloadi3-53718d354b101d6ade703938ef01658918d5d9a7.tar.gz
i3-53718d354b101d6ade703938ef01658918d5d9a7.zip
Merge branch 'next' into master
-rw-r--r--.travis.yml2
-rw-r--r--I3_VERSION2
-rw-r--r--Makefile.am4
-rw-r--r--RELEASE-NOTES-4.17.129
-rw-r--r--RELEASE-NOTES-4.1871
-rw-r--r--configure.ac14
-rw-r--r--debian/changelog12
-rw-r--r--docs/i3bar-protocol2
-rw-r--r--docs/ipc21
-rw-r--r--docs/layout-saving24
-rw-r--r--docs/userguide58
-rw-r--r--i3-config-wizard/main.c1
-rw-r--r--i3-input/main.c5
-rw-r--r--i3-nagbar/main.c28
-rw-r--r--i3bar/include/common.h6
-rw-r--r--i3bar/include/configuration.h3
-rw-r--r--i3bar/include/mode.h2
-rw-r--r--i3bar/include/outputs.h2
-rw-r--r--i3bar/include/trayclients.h3
-rw-r--r--i3bar/include/workspaces.h1
-rw-r--r--i3bar/include/xcb.h2
-rw-r--r--i3bar/src/config.c7
-rw-r--r--i3bar/src/main.c26
-rw-r--r--i3bar/src/mode.c2
-rw-r--r--i3bar/src/outputs.c1
-rw-r--r--i3bar/src/workspaces.c6
-rw-r--r--i3bar/src/xcb.c343
-rw-r--r--include/all.h1
-rw-r--r--include/commands.h6
-rw-r--r--include/con.h15
-rw-r--r--include/config_directives.h1
-rw-r--r--include/configuration.h3
-rw-r--r--include/data.h13
-rw-r--r--include/drag.h61
-rw-r--r--include/floating.h47
-rw-r--r--include/libi3.h6
-rw-r--r--include/manage.h7
-rw-r--r--include/move.h8
-rw-r--r--include/resize.h4
-rw-r--r--include/startup.h6
-rw-r--r--include/tree.h11
-rw-r--r--include/util.h24
-rw-r--r--include/window.h8
-rw-r--r--include/xcb.h2
-rw-r--r--libi3/draw_util.c2
-rw-r--r--libi3/font.c4
-rw-r--r--libi3/mkdirp.c3
-rw-r--r--libi3/strndup.c4
-rw-r--r--m4/ax_code_coverage.m42
-rw-r--r--parser-specs/commands.spec8
-rw-r--r--parser-specs/config.spec13
-rwxr-xr-xrelease.sh4
-rw-r--r--src/click.c75
-rw-r--r--src/commands.c167
-rw-r--r--src/commands_parser.c4
-rw-r--r--src/con.c106
-rw-r--r--src/config_directives.c6
-rw-r--r--src/config_parser.c4
-rw-r--r--src/drag.c252
-rw-r--r--src/fake_outputs.c2
-rw-r--r--src/floating.c228
-rw-r--r--src/handlers.c35
-rw-r--r--src/ipc.c36
-rw-r--r--src/load_layout.c3
-rw-r--r--src/main.c18
-rw-r--r--src/manage.c107
-rw-r--r--src/move.c11
-rw-r--r--src/randr.c19
-rw-r--r--src/resize.c47
-rw-r--r--src/scratchpad.c6
-rw-r--r--src/sighandler.c2
-rw-r--r--src/startup.c21
-rw-r--r--src/tree.c267
-rw-r--r--src/util.c62
-rw-r--r--src/window.c20
-rw-r--r--src/workspace.c23
-rw-r--r--src/x.c9
-rw-r--r--src/xinerama.c2
-rw-r--r--testcases/lib/i3test.pm.in2
-rw-r--r--testcases/t/101-focus.t16
-rw-r--r--testcases/t/116-nestedcons.t1
-rw-r--r--testcases/t/172-start-on-named-ws.t18
-rw-r--r--testcases/t/176-workspace-baf.t47
-rw-r--r--testcases/t/177-bar-config.t3
-rw-r--r--testcases/t/185-scratchpad.t16
-rw-r--r--testcases/t/195-net-active-window.t16
-rw-r--r--testcases/t/201-config-parser.t2
-rw-r--r--testcases/t/206-fullscreen-scratchpad.t43
-rw-r--r--testcases/t/243-move-to-mark.t25
-rw-r--r--testcases/t/270-config-no-newline-end.t3
-rw-r--r--testcases/t/294-focus-order.t29
-rw-r--r--testcases/t/306-move-to-parent.t37
-rw-r--r--testcases/t/307-focus-next-prev.t72
-rw-r--r--testcases/t/308-focus_wrapping.t345
-rw-r--r--testcases/t/309-crash-move-parent.t43
-rw-r--r--testcases/t/542-layout-restore-remanage.t86
-rwxr-xr-xtravis/check-spelling.pl2
97 files changed, 2324 insertions, 954 deletions
diff --git a/.travis.yml b/.travis.yml
index 0133fc71..b56bf684 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -31,7 +31,7 @@ install:
script:
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-safe-wrappers.sh
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-formatting.sh
- - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'autoreconf -fi && mkdir -p build && cd build && (../configure || (cat config.log; false)) && make -j CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror"'
+ - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'autoreconf -fi && mkdir -p build && cd build && (../configure || (cat config.log; false)) && make -j CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common"'
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-spelling.pl
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} ./travis/run-tests.sh
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/debian-build.sh deb/debian-amd64/DIST
diff --git a/I3_VERSION b/I3_VERSION
index f15477ff..7cf2ba0d 100644
--- a/I3_VERSION
+++ b/I3_VERSION
@@ -1 +1 @@
-4.17.1-non-git
+4.18-non-git
diff --git a/Makefile.am b/Makefile.am
index ee0e038b..59eba84a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -118,7 +118,7 @@ EXTRA_DIST = \
I3_VERSION \
LICENSE \
PACKAGE-MAINTAINER \
- RELEASE-NOTES-4.17.1 \
+ RELEASE-NOTES-4.18 \
generate-command-parser.pl \
parser-specs/commands.spec \
parser-specs/config.spec \
@@ -503,6 +503,7 @@ i3_SOURCES = \
include/con.h \
include/data.h \
include/display_version.h \
+ include/drag.h \
include/ewmh.h \
include/fake_outputs.h \
include/floating.h \
@@ -548,6 +549,7 @@ i3_SOURCES = \
src/config_directives.c \
src/config_parser.c \
src/display_version.c \
+ src/drag.c \
src/ewmh.c \
src/fake_outputs.c \
src/floating.c \
diff --git a/RELEASE-NOTES-4.17.1 b/RELEASE-NOTES-4.17.1
deleted file mode 100644
index 710c4eb8..00000000
--- a/RELEASE-NOTES-4.17.1
+++ /dev/null
@@ -1,29 +0,0 @@
-
- ┌──────────────────────────────┐
- │ Release notes for i3 v4.17.1 │
- └──────────────────────────────┘
-
-This is i3 v4.17.1. This version is considered stable. All users of i3 are
-strongly encouraged to upgrade.
-
-This is a bugfix release for v4.17
-
- ┌────────────────────────────┐
- │ Bugfixes │
- └────────────────────────────┘
-
- • unset _I3_RESTART_FD after restart (fixes crashes on restart)
- • default config: immediately refresh i3status after volume changes
- • default config: add XF86AudioMicMute
- • default config: mention loginctl lock-session alongside xss-lock
- • default config: use workspace number, not just workspace
-
- ┌────────────────────────────┐
- │ Thanks! │
- └────────────────────────────┘
-
-Thanks for testing, bugfixes, discussions and everything I forgot go out to:
-
- David Shen
-
--- Michael Stapelberg, 2019-08-30
diff --git a/RELEASE-NOTES-4.18 b/RELEASE-NOTES-4.18
new file mode 100644
index 00000000..4396ee1b
--- /dev/null
+++ b/RELEASE-NOTES-4.18
@@ -0,0 +1,71 @@
+
+ ┌────────────────────────────┐
+ │ Release notes for i3 v4.18 │
+ └────────────────────────────┘
+
+This is i3 v4.18. This version is considered stable. All users of i3 are
+strongly encouraged to upgrade.
+
+ ┌────────────────────────────┐
+ │ Changes in i3 v4.18 │
+ └────────────────────────────┘
+
+ • docs/ipc: document fullscreen_mode in GET_TREE reply
+ • docs/ipc: document marks field in GET_TREE reply
+ • docs/ipc: document window_type in GET_TREE reply
+ • docs/ipc: improve documentation for window_properties
+ • docs/userguide: clarify commands/config directive wording
+ • layout saving: remanage window after property updates (e.g. titles)
+ • get_first_output: prefer primary output (e.g. when moving disabled outputs)
+ • ipc: add window_type to nodes
+ • ipc: add container id to nodes
+ • allow dragging active titles for all container types (e.g. floating+tabbed)
+ • allow dragging inactive titles after a 10px threshold
+ • make tray icon order deterministic (sorted by class/instance)
+ • implement focus next|prev
+ • implement focus next|prev sibling
+ • implement focus_wrapping workspace
+ • exit with exit code 0 on --help
+ • exec command: respect command criteria
+
+ ┌────────────────────────────┐
+ │ Bugfixes │
+ └────────────────────────────┘
+
+ • build: fix lcov support
+ • build: use AC_REPLACE_FUNCS, drop bundled memmem
+ • build: fix building with -fno-common (for gcc 10)
+ • build: configure: deal with git worktree checkouts, where .git is a file
+ • docs/userguide: fix link to pango markup
+ • docs/userguide: add missing manipulating_layout anchor
+ • docs/userguide: fix IPC socket location
+ • i3-nagbar: make debug log visible
+ • i3-nagbar: fix small memory leaks
+ • i3bar: fix small memory leaks
+ • move workspace to output: don’t create duplicate numbered workspace
+ • correctly select output when pointer query fails
+ • fix moving windows to scratchpad when using marks
+ • fix startup workspace selection when workspace command uses options
+ • do not try to center floating window on itself (fixes xterm placement)
+ • fix “move window to <mark>” when target is a workspace
+ • correctly activate windows behind a fullscreen window
+ • fix back-and-forth after renaming workspaces
+ • keep focus when moving container to marked workspace
+ • do not show scratchpad windows upon move to position command
+ • reparent windows to their current position when unmanaging
+ (fixes dock clients unexpectedly moving to different output)
+ • fix crash when moving containers
+ • scratchpad_move: un-fullscreen correct container
+ • avoid crash when nc->window is NULL
+
+ ┌────────────────────────────┐
+ │ Thanks! │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+ acheronfail, Albert Safin, Antoine, Benjamin Dopplinger, Brian Ashworth,
+ Damien Cassou, Daniele Varrazzo, David Shen, Erwin J. van Eijk, Ingo Bürk,
+ Iskustvo, izzel, Konst Mayer, Orestis Floros, Yury Ignatev
+
+-- Michael Stapelberg, 2020-02-17
diff --git a/configure.ac b/configure.ac
index 22e27f6f..dda93482 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Run autoreconf -fi to generate a configure script from this file.
AC_PREREQ([2.69])
-AC_INIT([i3], [4.17.1], [https://github.com/i3/i3/issues])
+AC_INIT([i3], [4.18], [https://github.com/i3/i3/issues])
# For AX_EXTEND_SRCDIR
AX_ENABLE_BUILDDIR
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
@@ -29,7 +29,7 @@ AS_VAR_IF([_cv_gnu_make_command], [""], [AC_MSG_ERROR([the i3 Makefile.am requir
AX_EXTEND_SRCDIR
-AS_IF([test -d ${srcdir}/.git],
+AS_IF([test -e ${srcdir}/.git],
[
VERSION="$(git -C ${srcdir} describe --tags --abbrev=0)"
I3_VERSION="$(git -C ${srcdir} describe --tags --always) ($(git -C ${srcdir} rev-list --format=%cd --date=short -n1 $(git rev-parse HEAD) | tail -n1), branch \\\"$(git -C ${srcdir} describe --tags --always --all | sed s:heads/::)\\\")"
@@ -72,7 +72,8 @@ AC_CHECK_TYPES([mode_t, off_t, pid_t, size_t, ssize_t], , [AC_MSG_FAILURE([canno
AC_FUNC_FORK
AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK
AC_FUNC_STRNLEN
-AC_CHECK_FUNCS([atexit dup2 ftruncate getcwd gettimeofday localtime_r memchr memset mkdir rmdir setlocale socket strcasecmp strchr strdup strerror strncasecmp strndup strrchr strspn strstr strtol strtoul], , [AC_MSG_FAILURE([cannot find the $ac_func function, which i3 requires])])
+AC_CHECK_FUNCS([atexit dup2 ftruncate getcwd gettimeofday localtime_r memchr memset mkdir rmdir setlocale socket strcasecmp strchr strdup strerror strncasecmp strrchr strspn strstr strtol strtoul], , [AC_MSG_FAILURE([cannot find the $ac_func function, which i3 requires])])
+AC_REPLACE_FUNCS([mkdirp strndup])
# Checks for libraries.
@@ -83,8 +84,11 @@ AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_ru
AC_SEARCH_LIBS([shm_open], [rt], [], [], [-pthread])
-AC_SEARCH_LIBS([iconv_open], [iconv], ,
-AC_SEARCH_LIBS([libiconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])]))
+AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <iconv.h>], [iconv_open(0, 0)])], ,
+ [LIBS="-liconv $LIBS"
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <iconv.h>], [iconv_open(0, 0)])], ,
+ [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])])]
+)
AX_PTHREAD
diff --git a/debian/changelog b/debian/changelog
index 9857496d..c5ea756d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,15 @@
+i3-wm (4.17.2-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org> Fri, 30 Aug 2019 23:06:40 +0200
+
+i3-wm (4.17.1-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org> Fri, 30 Aug 2019 23:06:40 +0200
+
i3-wm (4.17-1) unstable; urgency=medium
* New upstream release.
diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol
index 76c51d80..cadcd8a8 100644
--- a/docs/i3bar-protocol
+++ b/docs/i3bar-protocol
@@ -189,7 +189,7 @@ separator_block_width::
is 9 pixels), since the separator line is drawn in the middle.
markup::
A string that indicates how the text of the block should be parsed. Set to
- +"pango"+ to use https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup].
+ +"pango"+ to use https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup].
Set to +"none"+ to not use any markup (default). Pango markup only works
if you use a pango font.
diff --git a/docs/ipc b/docs/ipc
index ea7a5892..5bc40e26 100644
--- a/docs/ipc
+++ b/docs/ipc
@@ -164,6 +164,10 @@ sending a reply. Expect the socket to be shut down.
The reply consists of a serialized list of workspaces. Each workspace has the
following properties:
+id (integer)::
+ The internal ID (actually a C pointer value) of this container. Do not
+ make any assumptions about it. You can use it to (re-)identify and
+ address containers when talking to i3.
num (integer)::
The logical number of the workspace. Corresponds to the command
to switch to this workspace. For named workspaces, this will be -1.
@@ -344,12 +348,19 @@ window (integer)::
containers. This ID corresponds to what xwininfo(1) and other
X11-related tools display (usually in hex).
window_properties (map)::
- X11 window properties title, instance, class, window_role and transient_for.
+ This optional field contains all available X11 window properties from the
+ following list: *title*, *instance*, *class*, *window_role* 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.
urgent (bool)::
Whether this container (window, split container, floating container or
workspace) has the urgency hint set, directly or indirectly. All parent
containers up until the workspace container will be marked urgent if they
have at least one urgent child.
+marks (array of string)::
+ List of marks assigned to container
focused (bool)::
Whether this container is currently focused.
focus (array of integer)::
@@ -357,6 +368,14 @@ focus (array of integer)::
order. Traversing the tree by following the first entry in this array
will result in eventually reaching the one node with +focused+ set to
true.
+fullscreen_mode (integer)::
+ Whether this container is in fullscreen state or not.
+ Possible values are
+ +0+ (no fullscreen),
+ +1+ (fullscreened on output) or
+ +2+ (fullscreened globally).
+ Note that all workspaces are considered fullscreened on their respective output.
+
nodes (array of node)::
The tiling (i.e. non-floating) child containers of this node.
floating_nodes (array of node)::
diff --git a/docs/layout-saving b/docs/layout-saving
index f31b5e21..4f0ffccf 100644
--- a/docs/layout-saving
+++ b/docs/layout-saving
@@ -261,27 +261,3 @@ container:
]
}
--------------------------------------------------------------------------------
-
-=== Placeholders using window title matches don't swallow the window
-
-If you use the +title+ attribute to match a window and find that it doesn't
-work or only works sometimes, the reason might be that the application sets the
-title only after making the window visible. This will be especially true for
-programs running inside terminal emulators, e.g., +urxvt -e irssi+ when
-matching on +title: "irssi"+.
-
-One way to deal with this is to not rely on the title, but instead use, e.g.,
-the +instance+ attribute and running the program to set this window instance to
-that value:
-
---------------------------------------------------------------------------------
-# Run irssi via
-# urxvt -name "irssi-container" -e irssi
-
-"swallows": [
- {
- "class": "URxvt",
- "instance": "irssi-container"
- }
-]
---------------------------------------------------------------------------------
diff --git a/docs/userguide b/docs/userguide
index 0fda7e80..36c7abb7 100644
--- a/docs/userguide
+++ b/docs/userguide
@@ -680,8 +680,8 @@ for_window [class="urxvt"] border pixel 1
for_window [title="x200: ~/work"] floating enable
------------------------------------------------
-The valid criteria are the same as those for commands, see <<command_criteria>>. Only config
-directives with a command equivalent can be executed at runtime, see <<list_of_commands>>.
+The valid criteria are the same as those for commands, see <<command_criteria>>. Only
+commands can be executed at runtime, not config directives, see <<list_of_commands>>.
[[no_focus]]
=== Don't focus window upon opening
@@ -1005,7 +1005,8 @@ 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
-+/tmp/i3-%u.XXXXXX/ipc-socket.%p+ where +%u+ is your UNIX username, +%p+ is
++$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
filename character set (see mkdtemp(3)).
@@ -1105,9 +1106,14 @@ If you want the focus to *always* wrap and you are aware of using +focus
parent+ to switch to different containers, you can instead set +focus_wrapping+
to the value +force+.
+To restrict focus inside the current workspace set +focus_wrapping+ to the
+value +workspace+. You will need to use +focus parent+ until a workspace is
+selected to switch to a different workspace using the focus commands (the
++workspace+ command will still work as expected).
+
*Syntax*:
---------------------------
-focus_wrapping yes|no|force
+focus_wrapping yes|no|force|workspace
# Legacy syntax, equivalent to "focus_wrapping force"
force_focus_wrapping yes
@@ -1625,6 +1631,35 @@ bar {
}
------------------------
+=== Minimal width for workspace buttons
+
+By default, the width a workspace button is determined by the width of the text
+showing the workspace name. If the name is too short (say, one letter), then the
+workspace button may look too small.
+
+This option specifies the minimum width for workspace buttons. If the name of
+a workspace is too short to cover the button, an additional padding is added on
+both sides of the button so that the text is centered.
+
+The default value of zero means that no additional padding is added.
+
+The setting also applies to the current binding mode indicator.
+
+Note that the specified pixels refer to logical pixels, which may translate
+into more pixels on HiDPI displays.
+
+*Syntax*:
+------------------------
+workspace_min_width <px> [px]
+------------------------
+
+*Example*:
+------------------------
+bar {
+ workspace_min_width 40
+}
+------------------------
+
=== Strip workspace numbers/name
Specifies whether workspace numbers should be displayed within the workspace
@@ -1867,9 +1902,6 @@ The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are
actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
information on how to use them.
-Note that config directives listed under <<configuring>> cannot be changed at runtime
-unless they happen to have a command equivalent.
-
[[exec]]
=== Executing applications (exec)
@@ -1954,6 +1986,7 @@ bindsym $mod+h split horizontal
bindsym $mod+t split toggle
-------------------------------
+[[manipulating_layout]]
=== Manipulating layout
Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+
@@ -2024,6 +2057,12 @@ parent::
child::
The opposite of +focus parent+, sets the focus to the last focused
child container.
+next|prev::
+ Automatically sets focus to the adjacent container. If +sibling+ is
+ specified, the command will focus the exact sibling container,
+ including non-leaf containers like split containers. Otherwise, it is
+ an automatic version of +focus left|right|up|down+ in the orientation
+ of the parent container.
floating::
Sets focus to the last focused floating container.
tiling::
@@ -2039,6 +2078,7 @@ output::
<criteria> focus
focus left|right|down|up
focus parent|child|floating|tiling|mode_toggle
+focus next|prev [sibling]
focus output left|right|up|down|primary|<output>
----------------------------------------------
@@ -2198,7 +2238,7 @@ See <<move_to_outputs>> for how to move a container/workspace to a different
RandR output.
Workspace names are parsed as
-https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup]
+https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup]
by i3bar.
[[back_and_forth]]
@@ -2492,7 +2532,7 @@ unmark irssi
By default, i3 will simply print the X11 window title. Using +title_format+,
this can be customized by setting the format to the desired output. This
directive supports
-https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup]
+https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup]
and the following placeholders which will be replaced:
+%title+::
diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c
index 7c1f00a3..f999de30 100644
--- a/i3-config-wizard/main.c
+++ b/i3-config-wizard/main.c
@@ -68,6 +68,7 @@
} while (0)
#include "xcb.h"
+xcb_visualtype_t *visual_type = NULL;
#include "libi3.h"
#define TEXT_PADDING logical_px(4)
diff --git a/i3-input/main.c b/i3-input/main.c
index f15a74ab..e495fe10 100644
--- a/i3-input/main.c
+++ b/i3-input/main.c
@@ -8,8 +8,6 @@
* to i3.
*
*/
-#include "libi3.h"
-
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
@@ -27,6 +25,9 @@
#include <xcb/xcb_event.h>
#include <xcb/xcb_keysyms.h>
+xcb_visualtype_t *visual_type = NULL;
+#include "libi3.h"
+
#include <X11/keysym.h>
#include "keysym2ucs.h"
diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c
index ec3e25fb..5099cf54 100644
--- a/i3-nagbar/main.c
+++ b/i3-nagbar/main.c
@@ -8,8 +8,6 @@
* when the user has an error in their configuration file.
*
*/
-#include "libi3.h"
-
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -32,6 +30,9 @@
#include <xcb/randr.h>
#include <xcb/xcb_cursor.h>
+xcb_visualtype_t *visual_type = NULL;
+#include "libi3.h"
+
#define SN_API_NOT_YET_FROZEN 1
#include <libsn/sn-launchee.h>
@@ -87,7 +88,7 @@ void verboselog(char *fmt, ...) {
va_list args;
va_start(args, fmt);
- vfprintf(stdout, fmt, args);
+ vfprintf(stderr, fmt, args);
va_end(args);
}
@@ -282,11 +283,12 @@ static xcb_rectangle_t get_window_position(void) {
xcb_randr_get_screen_resources_current_reply_t *res = NULL;
if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL) {
- DLOG("Could not determine the primary output.\n");
+ LOG("Could not determine the primary output.\n");
goto free_resources;
}
if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
+ LOG("Could not query screen resources.\n");
goto free_resources;
}
@@ -294,20 +296,24 @@ static xcb_rectangle_t get_window_position(void) {
xcb_randr_get_output_info_reply(conn,
xcb_randr_get_output_info(conn, primary->output, res->config_timestamp),
NULL);
- if (output == NULL || output->crtc == XCB_NONE)
+ if (output == NULL || output->crtc == XCB_NONE) {
+ LOG("Could not query primary screen.\n");
goto free_resources;
+ }
xcb_randr_get_crtc_info_reply_t *crtc =
xcb_randr_get_crtc_info_reply(conn,
xcb_randr_get_crtc_info(conn, output->crtc, res->config_timestamp),
NULL);
- if (crtc == NULL)
+ if (crtc == NULL) {
+ LOG("Could not get CRTC.\n");
goto free_resources;
+ }
- DLOG("Found primary output on position x = %i / y = %i / w = %i / h = %i.\n",
- crtc->x, crtc->y, crtc->width, crtc->height);
+ LOG("Found primary output on position x = %i / y = %i / w = %i / h = %i.\n",
+ crtc->x, crtc->y, crtc->width, crtc->height);
if (crtc->width == 0 || crtc->height == 0) {
- DLOG("Primary output is not active, ignoring it.\n");
+ LOG("Primary output is not active, ignoring it.\n");
goto free_resources;
}
@@ -379,10 +385,11 @@ int main(int argc, char *argv[]) {
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
switch (o) {
case 'v':
+ free(pattern);
printf("i3-nagbar " I3_VERSION "\n");
return 0;
case 'f':
- FREE(pattern);
+ free(pattern);
pattern = sstrdup(optarg);
break;
case 'm':
@@ -393,6 +400,7 @@ int main(int argc, char *argv[]) {
bar_type = (strcasecmp(optarg, "warning") == 0 ? TYPE_WARNING : TYPE_ERROR);
break;
case 'h':
+ free(pattern);
printf("i3-nagbar " I3_VERSION "\n");
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
return 0;
diff --git a/i3bar/include/common.h b/i3bar/include/common.h
index fe7444a3..222f42dd 100644
--- a/i3bar/include/common.h
+++ b/i3bar/include/common.h
@@ -17,7 +17,7 @@
typedef struct rect_t rect;
-struct ev_loop *main_loop;
+extern struct ev_loop *main_loop;
struct rect_t {
int x;
@@ -82,8 +82,8 @@ struct status_block {
blocks;
};
-TAILQ_HEAD(statusline_head, status_block)
-statusline_head;
+extern TAILQ_HEAD(statusline_head, status_block)
+ statusline_head;
#include "child.h"
#include "ipc.h"
diff --git a/i3bar/include/configuration.h b/i3bar/include/configuration.h
index 3d875e5d..70167247 100644
--- a/i3bar/include/configuration.h
+++ b/i3bar/include/configuration.h
@@ -52,6 +52,7 @@ typedef struct config_t {
struct xcb_color_strings_t colors;
bool disable_binding_mode_indicator;
bool disable_ws;
+ int ws_min_width;
bool strip_ws_numbers;
bool strip_ws_name;
char *bar_id;
@@ -73,7 +74,7 @@ typedef struct config_t {
S_SHOW = 1 } hidden_state;
} config_t;
-config_t config;
+extern config_t config;
/**
* Start parsing the received bar configuration JSON string
diff --git a/i3bar/include/mode.h b/i3bar/include/mode.h
index 5c87d904..e8e4296d 100644
--- a/i3bar/include/mode.h
+++ b/i3bar/include/mode.h
@@ -18,7 +18,7 @@
/* Name of current binding mode and its render width */
struct mode {
i3String *name;
- int width;
+ int name_width;
};
typedef struct mode mode;
diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h
index 29a7bcd3..5bb19c40 100644
--- a/i3bar/include/outputs.h
+++ b/i3bar/include/outputs.h
@@ -19,7 +19,7 @@
typedef struct i3_output i3_output;
SLIST_HEAD(outputs_head, i3_output);
-struct outputs_head* outputs;
+extern struct outputs_head* outputs;
/*
* Start parsing the received JSON string
diff --git a/i3bar/include/trayclients.h b/i3bar/include/trayclients.h
index db954bb1..3f215ce4 100644
--- a/i3bar/include/trayclients.h
+++ b/i3bar/include/trayclients.h
@@ -18,6 +18,9 @@ struct trayclient {
bool mapped; /* Whether this window is mapped */
int xe_version; /* The XEMBED version supported by the client */
+ char *class_class;
+ char *class_instance;
+
TAILQ_ENTRY(trayclient)
tailq; /* Pointer for the TAILQ-Macro */
};
diff --git a/i3bar/include/workspaces.h b/i3bar/include/workspaces.h
index e1f9e887..0ef5c0a9 100644
--- a/i3bar/include/workspaces.h
+++ b/i3bar/include/workspaces.h
@@ -30,6 +30,7 @@ void parse_workspaces_json(char *json);
void free_workspaces(void);
struct i3_ws {
+ uintptr_t id; /* Workspace ID - C pointer to a workspace container */
int num; /* The internal number of the ws */
char *canonical_name; /* The true name of the ws according to the ipc */
i3String *name; /* The name of the ws that is displayed on the bar */
diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h
index 760ebcdb..0e3ca22d 100644
--- a/i3bar/include/xcb.h
+++ b/i3bar/include/xcb.h
@@ -53,7 +53,7 @@ struct xcb_color_strings_t {
typedef struct xcb_colors_t xcb_colors_t;
/* Cached width of the custom separator if one was set */
-int separator_symbol_width;
+extern int separator_symbol_width;
/*
* Early initialization of the connection to X11: Everything which does not
diff --git a/i3bar/src/config.c b/i3bar/src/config.c
index c2325629..29b0908b 100644
--- a/i3bar/src/config.c
+++ b/i3bar/src/config.c
@@ -19,6 +19,7 @@
#include <X11/Xlib.h>
+config_t config;
static char *cur_key;
static bool parsing_bindings;
static bool parsing_tray_outputs;
@@ -345,6 +346,12 @@ static int config_integer_cb(void *params_, long long val) {
return 1;
}
+ if (!strcmp(cur_key, "workspace_min_width")) {
+ DLOG("workspace_min_width = %lld\n", val);
+ config.ws_min_width = val;
+ return 1;
+ }
+
return 0;
}
diff --git a/i3bar/src/main.c b/i3bar/src/main.c
index 26ea0eeb..65cb00ff 100644
--- a/i3bar/src/main.c
+++ b/i3bar/src/main.c
@@ -17,6 +17,8 @@
#include <getopt.h>
#include <glob.h>
+struct ev_loop *main_loop;
+
/*
* Having verboselog(), errorlog() and debuglog() is necessary when using libi3.
*
@@ -92,13 +94,7 @@ static void sig_cb(struct ev_loop *loop, ev_signal *watcher, int revents) {
}
int main(int argc, char **argv) {
- int opt;
- int option_index = 0;
- char *socket_path = getenv("I3SOCK");
- if (socket_path != NULL) {
- socket_path = sstrdup(socket_path);
- }
- char *i3_default_sock_path = "/tmp/i3-ipc.sock";
+ char *socket_path = NULL;
/* Initialize the standard config to use 0 as default */
memset(&config, '\0', sizeof(config_t));
@@ -112,6 +108,8 @@ int main(int argc, char **argv) {
{"verbose", no_argument, 0, 'V'},
{NULL, 0, 0, 0}};
+ int opt;
+ int option_index = 0;
while ((opt = getopt_long(argc, argv, "b:s:thvV", long_opt, &option_index)) != -1) {
switch (opt) {
case 's':
@@ -144,10 +142,17 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE);
}
- main_loop = ev_default_loop(0);
-
+ main_loop = ev_default_loop(0); /* needed in init_xcb_early */
char *atom_sock_path = init_xcb_early();
+ /* Select a socket_path if the user hasn't specified one */
+ if (socket_path == NULL) {
+ socket_path = getenv("I3SOCK");
+ if (socket_path != NULL) {
+ socket_path = sstrdup(socket_path);
+ }
+ }
+
if (socket_path == NULL) {
socket_path = atom_sock_path;
} else {
@@ -155,8 +160,9 @@ int main(int argc, char **argv) {
}
if (socket_path == NULL) {
+ char *i3_default_sock_path = "/tmp/i3-ipc.sock";
ELOG("No socket path specified, default to %s\n", i3_default_sock_path);
- socket_path = expand_path(i3_default_sock_path);
+ socket_path = sstrdup(i3_default_sock_path);
}
init_dpi();
diff --git a/i3bar/src/mode.c b/i3bar/src/mode.c
index 17e7b97e..97087ce0 100644
--- a/i3bar/src/mode.c
+++ b/i3bar/src/mode.c
@@ -81,7 +81,7 @@ static int mode_end_map_cb(void *params_) {
params->mode->name = i3string_from_utf8(params->name);
i3string_set_markup(params->mode->name, params->pango_markup);
/* Save its rendered width */
- params->mode->width = predict_text_width(params->mode->name);
+ params->mode->name_width = predict_text_width(params->mode->name);
DLOG("Got mode change: %s\n", i3string_as_utf8(params->mode->name));
FREE(params->cur_key);
diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c
index 4c9ce651..2fa3195c 100644
--- a/i3bar/src/outputs.c
+++ b/i3bar/src/outputs.c
@@ -252,6 +252,7 @@ static yajl_callbacks outputs_callbacks = {
.yajl_end_map = outputs_end_map_cb,
};
+struct outputs_head *outputs;
/*
* Initiate the outputs list
*
diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c
index 7285d150..9a0b950e 100644
--- a/i3bar/src/workspaces.c
+++ b/i3bar/src/workspaces.c
@@ -61,6 +61,12 @@ static int workspaces_boolean_cb(void *params_, int val) {
static int workspaces_integer_cb(void *params_, long long val) {
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
+ if (!strcmp(params->cur_key, "id")) {
+ params->workspaces_walk->id = val;
+ FREE(params->cur_key);
+ return 1;
+ }
+
if (!strcmp(params->cur_key, "num")) {
params->workspaces_walk->num = (int)val;
FREE(params->cur_key);
diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c
index 412981bc..7b97beea 100644
--- a/i3bar/src/xcb.c
+++ b/i3bar/src/xcb.c
@@ -140,6 +140,9 @@ static const int tray_loff_px = 2;
/* Vertical offset between the bar and a separator */
static const int sep_voff_px = 4;
+/* Cached width of the custom separator if one was set */
+int separator_symbol_width;
+
int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
xcb_generic_error_t *err;
if ((err = xcb_request_check(xcb_connection, cookie)) != NULL) {
@@ -500,6 +503,15 @@ static void child_handle_button(xcb_button_press_event_t *event, i3_output *outp
}
/*
+ * Predict the width of a workspace button or the current binding mode indicator.
+ *
+ */
+static int predict_button_width(int name_width) {
+ return MAX(name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
+ logical_px(config.ws_min_width));
+}
+
+/*
* Handle a button press event (i.e. a mouse click on one of our bars).
* We determine, whether the click occurred on a workspace button or if the scroll-
* wheel was used and change the workspace appropriately
@@ -530,7 +542,7 @@ static void handle_button(xcb_button_press_event_t *event) {
i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
TAILQ_FOREACH(ws_walk, walk->workspaces, tailq) {
- int w = 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + ws_walk->name_width;
+ int w = predict_button_width(ws_walk->name_width);
if (x >= workspace_width && x <= workspace_width + w)
clicked_ws = ws_walk;
if (ws_walk->visible)
@@ -683,6 +695,32 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
}
}
+static int strcasecmp_nullable(const char *a, const char *b) {
+ if (a == b) {
+ return 0;
+ }
+ if (a == NULL) {
+ return -1;
+ }
+ if (b == NULL) {
+ return 1;
+ }
+ return strcasecmp(a, b);
+}
+
+/*
+ * Comparison function to sort trayclients in ascending alphanumeric order
+ * according to their class.
+ *
+ */
+static int reorder_trayclients_cmp(const void *_a, const void *_b) {
+ trayclient *a = *((trayclient **)_a);
+ trayclient *b = *((trayclient **)_b);
+
+ int result = strcasecmp_nullable(a->class_class, b->class_class);
+ return result != 0 ? result : strcasecmp_nullable(a->class_instance, b->class_instance);
+}
+
/*
* Adjusts the size of the tray window and alignment of the tray clients by
* configuring their respective x coordinates. To be called when mapping or
@@ -690,27 +728,106 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
*
*/
static void configure_trayclients(void) {
- trayclient *trayclient;
i3_output *output;
SLIST_FOREACH(output, outputs, slist) {
- if (!output->active)
+ if (!output->active) {
continue;
+ }
- int clients = 0;
- TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
- if (!trayclient->mapped)
- continue;
- clients++;
+ int count = 0;
+ trayclient *client;
+ TAILQ_FOREACH(client, output->trayclients, tailq) {
+ if (client->mapped) {
+ count++;
+ }
+ }
- DLOG("Configuring tray window %08x to x=%d\n",
- trayclient->win, output->rect.w - (clients * (icon_size + logical_px(config.tray_padding))));
- uint32_t x = output->rect.w - (clients * (icon_size + logical_px(config.tray_padding)));
+ int idx = 0;
+ trayclient **trayclients = smalloc(count * sizeof(trayclient *));
+ TAILQ_FOREACH(client, output->trayclients, tailq) {
+ if (client->mapped) {
+ trayclients[idx++] = client;
+ }
+ }
+
+ qsort(trayclients, count, sizeof(trayclient *), reorder_trayclients_cmp);
+
+ uint32_t x = output->rect.w;
+ for (idx = count; idx > 0; idx--) {
+ x -= icon_size + logical_px(config.tray_padding);
+
+ DLOG("Configuring tray window %08x to x=%d\n", trayclients[idx - 1]->win, x);
xcb_configure_window(xcb_connection,
- trayclient->win,
+ trayclients[idx - 1]->win,
XCB_CONFIG_WINDOW_X,
&x);
}
+
+ free(trayclients);
+ }
+}
+
+static trayclient *trayclient_and_output_from_window(xcb_window_t win, i3_output **output) {
+ i3_output *o_walk;
+ SLIST_FOREACH(o_walk, outputs, slist) {
+ if (!o_walk->active) {
+ continue;
+ }
+
+ trayclient *client;
+ TAILQ_FOREACH(client, o_walk->trayclients, tailq) {
+ if (client->win == win) {
+ if (output) {
+ *output = o_walk;
+ }
+ return client;
+ }
+ }
}
+ return NULL;
+}
+
+static trayclient *trayclient_from_window(xcb_window_t win) {
+ return trayclient_and_output_from_window(win, NULL);
+}
+
+static void trayclient_update_class(trayclient *client) {
+ xcb_get_property_reply_t *prop = xcb_get_property_reply(
+ conn,
+ xcb_get_property_unchecked(
+ xcb_connection,
+ false,
+ client->win,
+ XCB_ATOM_WM_CLASS,
+ XCB_ATOM_STRING,
+ 0,
+ 32),
+ NULL);
+ if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+ DLOG("WM_CLASS not set.\n");
+ free(prop);
+ return;
+ }
+
+ /* We cannot use asprintf here since this property contains two
+ * null-terminated strings (for compatibility reasons). Instead, we
+ * use strdup() on both strings */
+ const size_t prop_length = xcb_get_property_value_length(prop);
+ char *new_class = xcb_get_property_value(prop);
+ const size_t class_class_index = strnlen(new_class, prop_length) + 1;
+
+ free(client->class_instance);
+ free(client->class_class);
+
+ client->class_instance = sstrndup(new_class, prop_length);
+ if (class_class_index < prop_length) {
+ client->class_class = sstrndup(new_class + class_class_index, prop_length - class_class_index);
+ } else {
+ client->class_class = NULL;
+ }
+ DLOG("WM_CLASS changed to %s (instance), %s (class)\n", client->class_instance, client->class_class);
+
+ free(prop);
}
/*
@@ -843,11 +960,12 @@ static void handle_client_message(xcb_client_message_event_t *event) {
* exits/crashes. */
xcb_change_save_set(xcb_connection, XCB_SET_MODE_INSERT, client);
- trayclient *tc = smalloc(sizeof(trayclient));
+ trayclient *tc = scalloc(1, sizeof(trayclient));
tc->win = client;
tc->xe_version = xe_version;
tc->mapped = false;
TAILQ_INSERT_TAIL(output_for_tray->trayclients, tc, tailq);
+ trayclient_update_class(tc);
if (map_it) {
DLOG("Mapping dock client\n");
@@ -875,27 +993,22 @@ static void handle_client_message(xcb_client_message_event_t *event) {
static void handle_destroy_notify(xcb_destroy_notify_event_t *event) {
DLOG("DestroyNotify for window = %08x, event = %08x\n", event->window, event->event);
- i3_output *walk;
- SLIST_FOREACH(walk, outputs, slist) {
- if (!walk->active)
- continue;
- DLOG("checking output %s\n", walk->name);
- trayclient *trayclient;
- TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
- if (trayclient->win != event->window) {
- continue;
- }
+ i3_output *output;
+ trayclient *client = trayclient_and_output_from_window(event->window, &output);
+ if (!client) {
+ DLOG("WARNING: Could not find corresponding tray window.\n");
+ return;
+ }
- DLOG("Removing tray client with window ID %08x\n", event->window);
- TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
- FREE(trayclient);
+ DLOG("Removing tray client with window ID %08x\n", event->window);
+ TAILQ_REMOVE(output->trayclients, client, tailq);
+ free(client->class_class);
+ free(client->class_instance);
+ FREE(client);
- /* Trigger an update, we now have more space for the statusline */
- configure_trayclients();
- draw_bars(false);
- return;
- }
- }
+ /* Trigger an update, we now have more space for the statusline */
+ configure_trayclients();
+ draw_bars(false);
}
/*
@@ -906,25 +1019,18 @@ static void handle_destroy_notify(xcb_destroy_notify_event_t *event) {
static void handle_map_notify(xcb_map_notify_event_t *event) {
DLOG("MapNotify for window = %08x, event = %08x\n", event->window, event->event);
- i3_output *walk;
- SLIST_FOREACH(walk, outputs, slist) {
- if (!walk->active)
- continue;
- DLOG("checking output %s\n", walk->name);
- trayclient *trayclient;
- TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
- if (trayclient->win != event->window)
- continue;
+ trayclient *client = trayclient_from_window(event->window);
+ if (!client) {
+ DLOG("WARNING: Could not find corresponding tray window.\n");
+ return;
+ }
- DLOG("Tray client mapped (window ID %08x). Adjusting tray.\n", event->window);
- trayclient->mapped = true;
+ DLOG("Tray client mapped (window ID %08x). Adjusting tray.\n", event->window);
+ client->mapped = true;
- /* Trigger an update, we now have more space for the statusline */
- configure_trayclients();
- draw_bars(false);
- return;
- }
- }
+ /* Trigger an update, we now have one extra tray client. */
+ configure_trayclients();
+ draw_bars(false);
}
/*
* Handles UnmapNotify events. These events happen when a tray client hides its
@@ -934,62 +1040,41 @@ static void handle_map_notify(xcb_map_notify_event_t *event) {
static void handle_unmap_notify(xcb_unmap_notify_event_t *event) {
DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event);
- i3_output *walk;
- SLIST_FOREACH(walk, outputs, slist) {
- if (!walk->active)
- continue;
- DLOG("checking output %s\n", walk->name);
- trayclient *trayclient;
- TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
- if (trayclient->win != event->window)
- continue;
+ trayclient *client = trayclient_from_window(event->window);
+ if (!client) {
+ DLOG("WARNING: Could not find corresponding tray window.\n");
+ return;
+ }
- DLOG("Tray client unmapped (window ID %08x). Adjusting tray.\n", event->window);
- trayclient->mapped = false;
+ DLOG("Tray client unmapped (window ID %08x). Adjusting tray.\n", event->window);
+ client->mapped = false;
- /* Trigger an update, we now have more space for the statusline */
- configure_trayclients();
- draw_bars(false);
- return;
- }
- }
+ /* Trigger an update, we now have more space for the statusline */
+ configure_trayclients();
+ draw_bars(false);
}
/*
- * Handle PropertyNotify messages. Currently only the _XEMBED_INFO property is
- * handled, which tells us whether a dock client should be mapped or unmapped.
+ * Handle PropertyNotify messages.
*
*/
static void handle_property_notify(xcb_property_notify_event_t *event) {
DLOG("PropertyNotify\n");
if (event->atom == atoms[_XEMBED_INFO] &&
event->state == XCB_PROPERTY_NEW_VALUE) {
+ /* _XEMBED_INFO property tells us whether a dock client should be mapped or unmapped. */
DLOG("xembed_info updated\n");
- trayclient *trayclient = NULL, *walk;
- i3_output *o_walk;
- SLIST_FOREACH(o_walk, outputs, slist) {
- if (!o_walk->active)
- continue;
-
- TAILQ_FOREACH(walk, o_walk->trayclients, tailq) {
- if (walk->win != event->window)
- continue;
- trayclient = walk;
- break;
- }
- if (trayclient)
- break;
- }
- if (!trayclient) {
- ELOG("PropertyNotify received for unknown window %08x\n",
- event->window);
+ trayclient *client = trayclient_from_window(event->window);
+ if (!client) {
+ ELOG("PropertyNotify received for unknown window %08x\n", event->window);
return;
}
+
xcb_get_property_cookie_t xembedc;
xembedc = xcb_get_property_unchecked(xcb_connection,
0,
- trayclient->win,
+ client->win,
atoms[_XEMBED_INFO],
XCB_GET_PROPERTY_TYPE_ANY,
0,
@@ -1009,14 +1094,19 @@ static void handle_property_notify(xcb_property_notify_event_t *event) {
DLOG("xembed flags = %d\n", xembed[1]);
bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
DLOG("map state now %d\n", map_it);
- if (trayclient->mapped && !map_it) {
+ if (client->mapped && !map_it) {
/* need to unmap the window */
- xcb_unmap_window(xcb_connection, trayclient->win);
- } else if (!trayclient->mapped && map_it) {
+ xcb_unmap_window(xcb_connection, client->win);
+ } else if (!client->mapped && map_it) {
/* need to map the window */
- xcb_map_window(xcb_connection, trayclient->win);
+ xcb_map_window(xcb_connection, client->win);
}
free(xembedr);
+ } else if (event->atom == XCB_ATOM_WM_CLASS) {
+ trayclient *client = trayclient_from_window(event->window);
+ if (client) {
+ trayclient_update_class(client);
+ }
}
}
@@ -1535,6 +1625,9 @@ void kick_tray_clients(i3_output *output) {
0,
0);
+ free(trayclient->class_class);
+ free(trayclient->class_instance);
+
/* We remove the trayclient right here. We might receive an UnmapNotify
* event afterwards, but better safe than sorry. */
TAILQ_REMOVE(output->trayclients, trayclient, tailq);
@@ -1910,6 +2003,25 @@ void reconfig_windows(bool redraw_bars) {
}
/*
+ * Draw the button for a workspace or the current binding mode indicator.
+ *
+ */
+static void draw_button(surface_t *surface, color_t fg_color, color_t bg_color, color_t border_color,
+ int x, int width, int text_width, i3String *text) {
+ int height = font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1);
+
+ /* Draw the border of the button. */
+ draw_util_rectangle(surface, border_color, x, logical_px(1), width, height);
+
+ /* Draw the inside of the button. */
+ draw_util_rectangle(surface, bg_color, x + logical_px(1), 2 * logical_px(1),
+ width - 2 * logical_px(1), height - 2 * logical_px(1));
+
+ draw_util_text(text, surface, fg_color, bg_color, x + (width - text_width) / 2,
+ logical_px(ws_voff_px), text_width);
+}
+
+/*
* Render the bars, with buttons and statusline
*
*/
@@ -1964,26 +2076,11 @@ void draw_bars(bool unhide) {
unhide = true;
}
- /* Draw the border of the button. */
- draw_util_rectangle(&(outputs_walk->buffer), border_color,
- workspace_width,
- logical_px(1),
- ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
- font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
-
- /* Draw the inside of the button. */
- draw_util_rectangle(&(outputs_walk->buffer), bg_color,
- workspace_width + logical_px(1),
- 2 * logical_px(1),
- ws_walk->name_width + 2 * logical_px(ws_hoff_px),
- font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1));
-
- draw_util_text(ws_walk->name, &(outputs_walk->buffer), fg_color, bg_color,
- workspace_width + logical_px(ws_hoff_px) + logical_px(1),
- logical_px(ws_voff_px),
- ws_walk->name_width);
-
- workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + ws_walk->name_width;
+ int w = predict_button_width(ws_walk->name_width);
+ draw_button(&(outputs_walk->buffer), fg_color, bg_color, border_color,
+ workspace_width, w, ws_walk->name_width, ws_walk->name);
+
+ workspace_width += w;
if (TAILQ_NEXT(ws_walk, tailq) != NULL)
workspace_width += logical_px(ws_spacing_px);
}
@@ -1992,28 +2089,12 @@ void draw_bars(bool unhide) {
if (binding.name && !config.disable_binding_mode_indicator) {
workspace_width += logical_px(ws_spacing_px);
- color_t fg_color = colors.binding_mode_fg;
- color_t bg_color = colors.binding_mode_bg;
-
- draw_util_rectangle(&(outputs_walk->buffer), colors.binding_mode_border,
- workspace_width,
- logical_px(1),
- binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
- font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
-
- draw_util_rectangle(&(outputs_walk->buffer), bg_color,
- workspace_width + logical_px(1),
- 2 * logical_px(1),
- binding.width + 2 * logical_px(ws_hoff_px),
- font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1));
-
- draw_util_text(binding.name, &(outputs_walk->buffer), fg_color, bg_color,
- workspace_width + logical_px(ws_hoff_px) + logical_px(1),
- logical_px(ws_voff_px),
- binding.width);
+ int w = predict_button_width(binding.name_width);
+ draw_button(&(outputs_walk->buffer), colors.binding_mode_fg, colors.binding_mode_bg,
+ colors.binding_mode_border, workspace_width, w, binding.name_width, binding.name);
unhide = true;
- workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + binding.width;
+ workspace_width += w;
}
if (!TAILQ_EMPTY(&statusline_head)) {
diff --git a/include/all.h b/include/all.h
index e93b066b..aa2b5b25 100644
--- a/include/all.h
+++ b/include/all.h
@@ -53,6 +53,7 @@
#include "click.h"
#include "key_press.h"
#include "floating.h"
+#include "drag.h"
#include "configuration.h"
#include "handlers.h"
#include "randr.h"
diff --git a/include/commands.h b/include/commands.h
index 0137460f..27d631a2 100644
--- a/include/commands.h
+++ b/include/commands.h
@@ -183,6 +183,12 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command);
void cmd_focus_direction(I3_CMD, const char *direction);
/**
+ * Implementation of 'focus next|prev sibling'
+ *
+ */
+void cmd_focus_sibling(I3_CMD, const char *direction);
+
+/**
* Implementation of 'focus tiling|floating|mode_toggle'.
*
*/
diff --git a/include/con.h b/include/con.h
index 09ec7b44..d8330098 100644
--- a/include/con.h
+++ b/include/con.h
@@ -46,6 +46,13 @@ void con_focus(Con *con);
void con_activate(Con *con);
/**
+ * Activates the container like in con_activate but removes fullscreen
+ * restrictions and properly warps the pointer if needed.
+ *
+ */
+void con_activate_unblock(Con *con);
+
+/**
* Closes the given container.
*
*/
@@ -533,3 +540,11 @@ bool con_swap(Con *first, Con *second);
*
*/
uint32_t con_rect_size_in_orientation(Con *con);
+
+/**
+ * Merges container specific data that should move with the window (e.g. marks,
+ * title format, and the window itself) into another container, and closes the
+ * old container.
+ *
+ */
+void con_merge_into(Con *old, Con *new);
diff --git a/include/config_directives.h b/include/config_directives.h
index 72b59ea2..06fbd3b0 100644
--- a/include/config_directives.h
+++ b/include/config_directives.h
@@ -97,6 +97,7 @@ CFGFUN(bar_color_single, const char *colorclass, const char *color);
CFGFUN(bar_status_command, const char *command);
CFGFUN(bar_binding_mode_indicator, const char *value);
CFGFUN(bar_workspace_buttons, const char *value);
+CFGFUN(bar_workspace_min_width, const long width);
CFGFUN(bar_strip_workspace_numbers, const char *value);
CFGFUN(bar_strip_workspace_name, const char *value);
CFGFUN(bar_start);
diff --git a/include/configuration.h b/include/configuration.h
index 872f11c8..d7c2ab8b 100644
--- a/include/configuration.h
+++ b/include/configuration.h
@@ -325,6 +325,9 @@ struct Barconfig {
* zero. */
bool hide_workspace_buttons;
+ /** The minimal width for workspace buttons. */
+ int workspace_min_width;
+
/** Strip workspace numbers? Configuration option is
* 'strip_workspace_numbers yes'. */
bool strip_workspace_numbers;
diff --git a/include/data.h b/include/data.h
index c3cada37..c0e34b41 100644
--- a/include/data.h
+++ b/include/data.h
@@ -59,6 +59,8 @@ typedef enum { D_LEFT,
typedef enum { NO_ORIENTATION = 0,
HORIZ,
VERT } orientation_t;
+typedef enum { BEFORE,
+ AFTER } position_t;
typedef enum { BS_NORMAL = 0,
BS_NONE = 1,
BS_PIXEL = 2 } border_style_t;
@@ -139,13 +141,12 @@ typedef enum {
typedef enum {
FOCUS_WRAPPING_OFF = 0,
FOCUS_WRAPPING_ON = 1,
- FOCUS_WRAPPING_FORCE = 2
+ FOCUS_WRAPPING_FORCE = 2,
+ FOCUS_WRAPPING_WORKSPACE = 3
} focus_wrapping_t;
/**
* Stores a rectangle, for example the size of a window, the child window etc.
- * It needs to be packed so that the compiler will not add any padding bytes.
- * (it is used in src/ewmh.c for example)
*
* Note that x and y can contain signed values in some cases (for example when
* used for the coordinates of a window, which can be set outside of the
@@ -159,7 +160,7 @@ struct Rect {
uint32_t y;
uint32_t width;
uint32_t height;
-} __attribute__((packed));
+};
/**
* Stores the reserved pixels on each screen edge read from a
@@ -489,6 +490,10 @@ struct Window {
bool shaped;
/** The window has a nonrectangular input shape. */
bool input_shaped;
+
+ /* Time when the window became managed. Used to determine whether a window
+ * should be swallowed after initial management. */
+ time_t managed_since;
};
/**
diff --git a/include/drag.h b/include/drag.h
new file mode 100644
index 00000000..2027f934
--- /dev/null
+++ b/include/drag.h
@@ -0,0 +1,61 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * drag.c: click and drag.
+ *
+ */
+#pragma once
+
+#include <config.h>
+
+/** Callback for dragging */
+typedef void (*callback_t)(Con *, Rect *, uint32_t, uint32_t,
+ const xcb_button_press_event_t *, const void *);
+
+/** Macro to create a callback function for dragging */
+#define DRAGGING_CB(name) \
+ static void name(Con *con, Rect *old_rect, uint32_t new_x, uint32_t new_y, \
+ const xcb_button_press_event_t *event, const void *extra)
+
+/**
+ * This is the return value of a drag operation like drag_pointer.
+ *
+ * DRAGGING will indicate the drag action is still in progress and can be
+ * continued or resolved.
+ *
+ * DRAG_SUCCESS will indicate the intention of the drag action should be
+ * carried out.
+ *
+ * DRAG_REVERT will indicate an attempt should be made to restore the state of
+ * the involved windows to their condition before the drag.
+ *
+ * DRAG_ABORT will indicate that the intention of the drag action cannot be
+ * carried out (e.g. because the window has been unmapped).
+ *
+ */
+typedef enum {
+ DRAGGING = 0,
+ DRAG_SUCCESS,
+ DRAG_REVERT,
+ DRAG_ABORT
+} drag_result_t;
+
+/**
+ * This function grabs your pointer and keyboard and lets you drag stuff around
+ * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
+ * be received and the given callback will be called with the parameters
+ * specified (client, the original event), the original rect of the client,
+ * and the new coordinates (x, y).
+ *
+ * If use_threshold is set, dragging only starts after the user moves the
+ * pointer past a certain threshold. That is, the cursor will not be set and the
+ * callback will not be called until then.
+ *
+ */
+drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
+ xcb_window_t confine_to, int cursor,
+ bool use_threshold, callback_t callback,
+ const void *extra);
diff --git a/include/floating.h b/include/floating.h
index a7813099..8e60f78c 100644
--- a/include/floating.h
+++ b/include/floating.h
@@ -13,14 +13,6 @@
#include "tree.h"
-/** Callback for dragging */
-typedef void (*callback_t)(Con *, Rect *, uint32_t, uint32_t, const void *);
-
-/** Macro to create a callback function for dragging */
-#define DRAGGING_CB(name) \
- static void name(Con *con, Rect *old_rect, uint32_t new_x, \
- uint32_t new_y, const void *extra)
-
/** On which border was the dragging initiated? */
typedef enum { BORDER_LEFT = (1 << 0),
BORDER_RIGHT = (1 << 1),
@@ -40,7 +32,7 @@ void floating_enable(Con *con, bool automatic);
* to its old parent.
*
*/
-void floating_disable(Con *con, bool automatic);
+void floating_disable(Con *con);
/**
* Calls floating_enable() for tiling containers and floating_disable() for
@@ -83,7 +75,7 @@ void floating_move_to_pointer(Con *con);
* Calls the drag_pointer function with the drag_window callback
*
*/
-void floating_drag_window(Con *con, const xcb_button_press_event_t *event);
+void floating_drag_window(Con *con, const xcb_button_press_event_t *event, bool use_threshold);
/**
* Called when the user clicked on a floating window while holding the
@@ -107,41 +99,6 @@ void floating_resize_window(Con *con, const bool proportional, const xcb_button_
void floating_check_size(Con *floating_con, bool prefer_height);
/**
- * This is the return value of a drag operation like drag_pointer.
- *
- * DRAGGING will indicate the drag action is still in progress and can be
- * continued or resolved.
- *
- * DRAG_SUCCESS will indicate the intention of the drag action should be
- * carried out.
- *
- * DRAG_REVERT will indicate an attempt should be made to restore the state of
- * the involved windows to their condition before the drag.
- *
- * DRAG_ABORT will indicate that the intention of the drag action cannot be
- * carried out (e.g. because the window has been unmapped).
- *
- */
-typedef enum {
- DRAGGING = 0,
- DRAG_SUCCESS,
- DRAG_REVERT,
- DRAG_ABORT
-} drag_result_t;
-
-/**
- * This function grabs your pointer and keyboard and lets you drag stuff around
- * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
- * be received and the given callback will be called with the parameters
- * specified (client, border on which the click originally was), the original
- * rect of the client, the event and the new coordinates (x, y).
- *
- */
-drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
- xcb_window_t confine_to, border_t border, int cursor,
- callback_t callback, const void *extra);
-
-/**
* Repositions the CT_FLOATING_CON to have the coordinates specified by
* newrect, but only if the coordinates are not out-of-bounds. Also reassigns
* the floating con to a different workspace if this move was across different
diff --git a/include/libi3.h b/include/libi3.h
index 790baba9..15d1bc76 100644
--- a/include/libi3.h
+++ b/include/libi3.h
@@ -341,8 +341,7 @@ gchar *g_utf8_make_valid(const gchar *str, gssize len);
*/
uint32_t get_colorpixel(const char *hex) __attribute__((const));
-#if defined(__APPLE__)
-
+#ifndef HAVE_strndup
/**
* Taken from FreeBSD
* Returns a pointer to a new string which is a duplicate of the
@@ -350,7 +349,6 @@ uint32_t get_colorpixel(const char *hex) __attribute__((const));
*
*/
char *strndup(const char *str, size_t n);
-
#endif
/**
@@ -528,7 +526,7 @@ char *resolve_tilde(const char *path);
*/
char *get_config_path(const char *override_configpath, bool use_system_paths);
-#if !defined(__sun)
+#ifndef HAVE_mkdirp
/**
* Emulates mkdir -p (creates any missing folders)
*
diff --git a/include/manage.h b/include/manage.h
index 55b0a85b..22fbe527 100644
--- a/include/manage.h
+++ b/include/manage.h
@@ -37,3 +37,10 @@ void restore_geometry(void);
void manage_window(xcb_window_t window,
xcb_get_window_attributes_cookie_t cookie,
bool needs_to_be_mapped);
+
+/**
+ * Remanages a window: performs a swallow check and runs assignments.
+ * Returns con for the window regardless if it updated.
+ *
+ */
+Con *remanage_window(Con *con);
diff --git a/include/move.h b/include/move.h
index df644a6b..830488b0 100644
--- a/include/move.h
+++ b/include/move.h
@@ -12,14 +12,10 @@
#include <config.h>
/**
- * Moves the given container in the given direction (TOK_LEFT, TOK_RIGHT,
- * TOK_UP, TOK_DOWN from cmdparse.l)
+ * Moves the given container in the given direction
*
*/
-void tree_move(Con *con, int direction);
-
-typedef enum { BEFORE,
- AFTER } position_t;
+void tree_move(Con *con, direction_t direction);
/**
* This function detaches 'con' from its parent and inserts it either before or
diff --git a/include/resize.h b/include/resize.h
index 162d8f6b..5439fab5 100644
--- a/include/resize.h
+++ b/include/resize.h
@@ -13,7 +13,9 @@
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides);
-void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
+void resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
+ const xcb_button_press_event_t *event,
+ bool use_threshold);
/**
* Resize the two given containers using the given amount of pixels or
diff --git a/include/startup.h b/include/startup.h
index feece575..0001a77d 100644
--- a/include/startup.h
+++ b/include/startup.h
@@ -69,3 +69,9 @@ struct Startup_Sequence *startup_sequence_get(i3Window *cwindow,
*
*/
char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply);
+
+/**
+ * Deletes the startup sequence for a window if it exists.
+ *
+ */
+void startup_sequence_delete_by_window(i3Window *win);
diff --git a/include/tree.h b/include/tree.h
index 12170f94..0b758d53 100644
--- a/include/tree.h
+++ b/include/tree.h
@@ -59,11 +59,16 @@ bool level_down(void);
void tree_render(void);
/**
- * Changes focus in the given way (next/previous) and given orientation
- * (horizontal/vertical).
+ * Changes focus in the given direction
*
*/
-void tree_next(char way, orientation_t orientation);
+void tree_next(Con *con, direction_t direction);
+
+/**
+ * Get the previous / next sibling
+ *
+ */
+Con *get_tree_next_sibling(Con *con, position_t direction);
/**
* Closes the given container including all children.
diff --git a/include/util.h b/include/util.h
index d08ac69d..6d525626 100644
--- a/include/util.h
+++ b/include/util.h
@@ -64,6 +64,7 @@ int max(int a, int b);
bool rect_contains(Rect rect, uint32_t x, uint32_t y);
Rect rect_add(Rect a, Rect b);
Rect rect_sub(Rect a, Rect b);
+bool rect_equals(Rect a, Rect b);
/**
* Returns true if the name consists of only digits.
@@ -123,17 +124,6 @@ bool path_exists(const char *path);
*/
void i3_restart(bool forget_layout);
-#if defined(__OpenBSD__) || defined(__APPLE__)
-
-/**
- * Taken from FreeBSD
- * Find the first occurrence of the byte string s in byte string l.
- *
- */
-void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);
-
-#endif
-
/**
* Escapes the given string if a pango font is currently used.
* If the string has to be escaped, the input string will be free'd.
@@ -180,3 +170,15 @@ ssize_t slurp(const char *path, char **buf);
*
*/
orientation_t orientation_from_direction(direction_t direction);
+
+/**
+ * Convert a direction to its corresponding position.
+ *
+ */
+position_t position_from_direction(direction_t direction);
+
+/**
+ * Convert orientation and position to the corresponding direction.
+ *
+ */
+direction_t direction_from_orientation_position(orientation_t orientation, position_t position);
diff --git a/include/window.h b/include/window.h
index b03f9c14..6673e835 100644
--- a/include/window.h
+++ b/include/window.h
@@ -22,14 +22,14 @@ void window_free(i3Window *win);
* given window.
*
*/
-void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
+void window_update_class(i3Window *win, xcb_get_property_reply_t *prop);
/**
* Updates the name by using _NET_WM_NAME (encoded in UTF-8) for the given
* window. Further updates using window_update_name_legacy will be ignored.
*
*/
-void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
+void window_update_name(i3Window *win, xcb_get_property_reply_t *prop);
/**
* Updates the name by using WM_NAME (encoded in COMPOUND_TEXT). We do not
@@ -38,7 +38,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
* window_update_name()).
*
*/
-void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
+void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop);
/**
* Updates the CLIENT_LEADER (logical parent window).
@@ -62,7 +62,7 @@ void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop);
* Updates the WM_WINDOW_ROLE
*
*/
-void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
+void window_update_role(i3Window *win, xcb_get_property_reply_t *prop);
/**
* Updates the _NET_WM_WINDOW_TYPE property.
diff --git a/include/xcb.h b/include/xcb.h
index 53c932bf..32110c7d 100644
--- a/include/xcb.h
+++ b/include/xcb.h
@@ -56,7 +56,7 @@
XCB_EVENT_MASK_FOCUS_CHANGE | \
XCB_EVENT_MASK_ENTER_WINDOW)
-#define xmacro(atom) xcb_atom_t A_##atom;
+#define xmacro(atom) extern xcb_atom_t A_##atom;
#include "atoms.xmacro"
#undef xmacro
diff --git a/libi3/draw_util.c b/libi3/draw_util.c
index f88360dc..092ac967 100644
--- a/libi3/draw_util.c
+++ b/libi3/draw_util.c
@@ -16,7 +16,7 @@
#include <cairo/cairo-xcb.h>
/* The default visual_type to use if none is specified when creating the surface. Must be defined globally. */
-xcb_visualtype_t *visual_type;
+extern xcb_visualtype_t *visual_type;
/* Forward declarations */
static void draw_util_set_source_color(surface_t *surface, color_t color);
diff --git a/libi3/font.c b/libi3/font.c
index 32744c0b..7b10edf0 100644
--- a/libi3/font.c
+++ b/libi3/font.c
@@ -323,7 +323,7 @@ bool font_is_pango(void) {
static int predict_text_width_xcb(const xcb_char2b_t *text, size_t text_len);
static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawable_t drawable,
- xcb_gcontext_t gc, int x, int y, int max_width) {
+ xcb_gcontext_t gc, int x, int y) {
/* X11 coordinates for fonts start at the baseline */
int pos_y = y + savedFont->specific.xcb.info->font_ascent;
@@ -372,7 +372,7 @@ void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc,
return;
case FONT_TYPE_XCB:
draw_text_xcb(i3string_as_ucs2(text), i3string_get_num_glyphs(text),
- drawable, gc, x, y, max_width);
+ drawable, gc, x, y);
break;
case FONT_TYPE_PANGO:
/* Render the text using Pango */
diff --git a/libi3/mkdirp.c b/libi3/mkdirp.c
index f5281bd7..35a30475 100644
--- a/libi3/mkdirp.c
+++ b/libi3/mkdirp.c
@@ -12,12 +12,11 @@
#include <string.h>
#include <sys/stat.h>
+#ifndef HAVE_mkdirp
/*
* Emulates mkdir -p (creates any missing folders)
*
*/
-
-#if !defined(__sun)
int mkdirp(const char *path, mode_t mode) {
if (mkdir(path, mode) == 0)
return 0;
diff --git a/libi3/strndup.c b/libi3/strndup.c
index e17f843c..e215a76f 100644
--- a/libi3/strndup.c
+++ b/libi3/strndup.c
@@ -10,8 +10,7 @@
#include <sys/types.h>
#include <string.h>
-#if defined(__APPLE__)
-
+#ifndef HAVE_strndup
/*
* Taken from FreeBSD
* Returns a pointer to a new string which is a duplicate of the
@@ -30,5 +29,4 @@ char *strndup(const char *str, size_t n) {
copy[len] = '\0';
return (copy);
}
-
#endif
diff --git a/m4/ax_code_coverage.m4 b/m4/ax_code_coverage.m4
index 6c985ebc..1f1bc702 100644
--- a/m4/ax_code_coverage.m4
+++ b/m4/ax_code_coverage.m4
@@ -107,7 +107,7 @@ AC_DEFUN([AX_CODE_COVERAGE],[
])
# List of supported lcov versions.
- lcov_version_list="1.6 1.7 1.8 1.9 1.10 1.11 1.12"
+ lcov_version_list="1.6 1.7 1.8 1.9 1.10 1.11 1.12 1.13"
AC_CHECK_PROG([LCOV], [lcov], [lcov])
AC_CHECK_PROG([GENHTML], [genhtml], [genhtml])
diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec
index 6b015188..ed9cf0f2 100644
--- a/parser-specs/commands.spec
+++ b/parser-specs/commands.spec
@@ -146,6 +146,8 @@ state WORKSPACE_NUMBER:
state FOCUS:
direction = 'left', 'right', 'up', 'down'
-> call cmd_focus_direction($direction)
+ direction = 'prev', 'next'
+ -> FOCUS_AUTO
'output'
-> FOCUS_OUTPUT
window_mode = 'tiling', 'floating', 'mode_toggle'
@@ -155,6 +157,12 @@ state FOCUS:
end
-> call cmd_focus()
+state FOCUS_AUTO:
+ 'sibling'
+ -> call cmd_focus_sibling($direction)
+ end
+ -> call cmd_focus_direction($direction)
+
state FOCUS_OUTPUT:
output = string
-> call cmd_focus_output($output)
diff --git a/parser-specs/config.spec b/parser-specs/config.spec
index a256d8b0..93901fd9 100644
--- a/parser-specs/config.spec
+++ b/parser-specs/config.spec
@@ -215,7 +215,7 @@ state MOUSE_WARPING:
# focus_wrapping
state FOCUS_WRAPPING:
- value = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive', 'force'
+ value = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive', 'force', 'workspace'
-> call cfg_focus_wrapping($value)
# force_focus_wrapping
@@ -468,6 +468,7 @@ state BAR:
'separator_symbol' -> BAR_SEPARATOR_SYMBOL
'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR
'workspace_buttons' -> BAR_WORKSPACE_BUTTONS
+ 'workspace_min_width' -> BAR_WORKSPACE_MIN_WIDTH
'strip_workspace_numbers' -> BAR_STRIP_WORKSPACE_NUMBERS
'strip_workspace_name' -> BAR_STRIP_WORKSPACE_NAME
'verbose' -> BAR_VERBOSE
@@ -572,6 +573,16 @@ state BAR_WORKSPACE_BUTTONS:
value = word
-> call cfg_bar_workspace_buttons($value); BAR
+state BAR_WORKSPACE_MIN_WIDTH:
+ width = number
+ -> BAR_WORKSPACE_MIN_WIDTH_PX
+
+state BAR_WORKSPACE_MIN_WIDTH_PX:
+ 'px'
+ ->
+ end
+ -> call cfg_bar_workspace_min_width(&width); BAR
+
state BAR_STRIP_WORKSPACE_NUMBERS:
value = word
-> call cfg_bar_strip_workspace_numbers($value); BAR
diff --git a/release.sh b/release.sh
index 97d91868..095bc50b 100755
--- a/release.sh
+++ b/release.sh
@@ -1,8 +1,8 @@
#!/bin/zsh
# This script is used to prepare a new release of i3.
-export RELEASE_VERSION="4.16"
-export PREVIOUS_VERSION="4.15"
+export RELEASE_VERSION="4.17"
+export PREVIOUS_VERSION="4.16"
export RELEASE_BRANCH="next"
if [ ! -e "../i3.github.io" ]
diff --git a/src/click.c b/src/click.c
index 58ebbf3d..75710a82 100644
--- a/src/click.c
+++ b/src/click.c
@@ -26,7 +26,7 @@ typedef enum { CLICK_BORDER = 0,
* then calls resize_graphical_handler().
*
*/
-static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) {
+static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event, bool use_threshold) {
DLOG("border = %d, con = %p\n", border, con);
Con *second = NULL;
Con *first = con;
@@ -64,7 +64,7 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
const orientation_t orientation = ((border == BORDER_LEFT || border == BORDER_RIGHT) ? HORIZ : VERT);
- resize_graphical_handler(first, second, orientation, event);
+ resize_graphical_handler(first, second, orientation, event, use_threshold);
DLOG("After resize handler, rendering\n");
tree_render();
@@ -94,22 +94,22 @@ static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *eve
if (to_right < to_left &&
to_right < to_top &&
to_right < to_bottom)
- return tiling_resize_for_border(con, BORDER_RIGHT, event);
+ return tiling_resize_for_border(con, BORDER_RIGHT, event, false);
if (to_left < to_right &&
to_left < to_top &&
to_left < to_bottom)
- return tiling_resize_for_border(con, BORDER_LEFT, event);
+ return tiling_resize_for_border(con, BORDER_LEFT, event, false);
if (to_top < to_right &&
to_top < to_left &&
to_top < to_bottom)
- return tiling_resize_for_border(con, BORDER_TOP, event);
+ return tiling_resize_for_border(con, BORDER_TOP, event, false);
if (to_bottom < to_right &&
to_bottom < to_left &&
to_bottom < to_top)
- return tiling_resize_for_border(con, BORDER_BOTTOM, event);
+ return tiling_resize_for_border(con, BORDER_BOTTOM, event, false);
return false;
}
@@ -118,45 +118,26 @@ static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *eve
* Finds out which border was clicked on and calls tiling_resize_for_border().
*
*/
-static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click_destination_t dest) {
+static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click_destination_t dest, bool use_threshold) {
/* check if this was a click on the window border (and on which one) */
Rect bsr = con_border_style_rect(con);
DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n",
event->event_x, event->event_y, con, event->event);
DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width);
if (dest == CLICK_DECORATION) {
- /* The user clicked on a window decoration. We ignore the following case:
- * The container is a h-split, tabbed or stacked container with > 1
- * window. Decorations will end up next to each other and the user
- * expects to switch to a window by clicking on its decoration. */
-
- /* Since the container might either be the child *or* already a split
- * container (in the case of a nested split container), we need to make
- * sure that we are dealing with the split container here. */
- Con *check_con = con;
- if (con_is_leaf(check_con) && check_con->parent->type == CT_CON)
- check_con = check_con->parent;
-
- if ((check_con->layout == L_STACKED ||
- check_con->layout == L_TABBED ||
- con_orientation(check_con) == HORIZ) &&
- con_num_children(check_con) > 1) {
- DLOG("Not handling this resize, this container has > 1 child.\n");
- return false;
- }
- return tiling_resize_for_border(con, BORDER_TOP, event);
+ return tiling_resize_for_border(con, BORDER_TOP, event, use_threshold);
}
if (event->event_x >= 0 && event->event_x <= (int32_t)bsr.x &&
event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height))
- return tiling_resize_for_border(con, BORDER_LEFT, event);
+ return tiling_resize_for_border(con, BORDER_LEFT, event, false);
if (event->event_x >= (int32_t)(con->window_rect.x + con->window_rect.width) &&
event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height))
- return tiling_resize_for_border(con, BORDER_RIGHT, event);
+ return tiling_resize_for_border(con, BORDER_RIGHT, event, false);
if (event->event_y >= (int32_t)(con->window_rect.y + con->window_rect.height))
- return tiling_resize_for_border(con, BORDER_BOTTOM, event);
+ return tiling_resize_for_border(con, BORDER_BOTTOM, event, false);
return false;
}
@@ -221,6 +202,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
Con *floatingcon = con_inside_floating(con);
const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT;
const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED);
+ const bool was_focused = focused == con;
/* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
if (in_stacked &&
@@ -230,21 +212,13 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
event->detail == XCB_BUTTON_SCROLL_LEFT ||
event->detail == XCB_BUTTON_SCROLL_RIGHT)) {
DLOG("Scrolling on a window decoration\n");
- orientation_t orientation = con_orientation(con->parent);
/* Use the focused child of the tabbed / stacked container, not the
* container the user scrolled on. */
- Con *focused = con->parent;
- focused = TAILQ_FIRST(&(focused->focus_head));
- con_activate(con_descend_focused(focused));
- /* To prevent scrolling from going outside the container (see ticket
- * #557), we first check if scrolling is possible at all. */
- bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
- bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL);
- if ((event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) && scroll_prev_possible) {
- tree_next('p', orientation);
- } else if ((event->detail == XCB_BUTTON_SCROLL_DOWN || event->detail == XCB_BUTTON_SCROLL_RIGHT) && scroll_next_possible) {
- tree_next('n', orientation);
- }
+ Con *current = TAILQ_FIRST(&(con->parent->focus_head));
+ const position_t direction =
+ (event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) ? BEFORE : AFTER;
+ Con *next = get_tree_next_sibling(current, direction);
+ con_activate(con_descend_focused(next ? next : current));
goto done;
}
@@ -258,7 +232,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
if (floatingcon != NULL && fs != con) {
/* 4: floating_modifier plus left mouse button drags */
if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) {
- floating_drag_window(floatingcon, event);
+ floating_drag_window(floatingcon, event, false);
return 1;
}
@@ -275,7 +249,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
is_left_or_right_click) {
/* try tiling resize, but continue if it doesn’t work */
DLOG("tiling resize with fallback\n");
- if (tiling_resize(con, event, dest))
+ if (tiling_resize(con, event, dest, !was_focused))
goto done;
}
@@ -293,9 +267,8 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
/* 6: dragging, if this was a click on a decoration (which did not lead
* to a resize) */
- if (!in_stacked && dest == CLICK_DECORATION &&
- (event->detail == XCB_BUTTON_CLICK_LEFT)) {
- floating_drag_window(floatingcon, event);
+ if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_CLICK_LEFT) {
+ floating_drag_window(floatingcon, event, !was_focused);
return 1;
}
@@ -311,7 +284,11 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
else if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
is_left_or_right_click) {
DLOG("Trying to resize (tiling)\n");
- tiling_resize(con, event, dest);
+ /* Since we updated the tree (con_activate() above), we need to
+ * re-render the tree before returning to the event loop (drag_pointer()
+ * inside tiling_resize() runs its own event-loop). */
+ tree_render();
+ tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused);
}
done:
diff --git a/src/commands.c b/src/commands.c
index aadf204f..6a6ea5a3 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -426,7 +426,7 @@ static direction_t parse_direction(const char *str) {
}
}
-static void cmd_resize_floating(I3_CMD, const char *way, const char *direction_str, Con *floating_con, int px) {
+static void cmd_resize_floating(I3_CMD, const char *direction_str, Con *floating_con, int px) {
Rect old_rect = floating_con->rect;
Con *focused_con = con_descend_focused(floating_con);
@@ -469,7 +469,7 @@ static void cmd_resize_floating(I3_CMD, const char *way, const char *direction_s
/* Did we actually resize anything or did the size constraints prevent us?
* If we could not resize, exit now to not move the window. */
- if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0) {
+ if (rect_equals(old_rect, floating_con->rect)) {
return;
}
@@ -596,7 +596,7 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
Con *floating_con;
if ((floating_con = con_inside_floating(current->con))) {
- cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, resize_px);
+ cmd_resize_floating(current_match, cmd_output, direction, floating_con, resize_px);
} else {
if (strcmp(direction, "width") == 0 ||
strcmp(direction, "height") == 0) {
@@ -1089,7 +1089,7 @@ void cmd_floating(I3_CMD, const char *floating_mode) {
if (strcmp(floating_mode, "enable") == 0) {
floating_enable(current->con, false);
} else {
- floating_disable(current->con, false);
+ floating_disable(current->con);
}
}
}
@@ -1207,30 +1207,74 @@ void cmd_kill(I3_CMD, const char *kill_mode_str) {
void cmd_exec(I3_CMD, const char *nosn, const char *command) {
bool no_startup_id = (nosn != NULL);
- DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
- start_application(command, no_startup_id);
+ HANDLE_EMPTY_MATCH;
+
+ int count = 0;
+ owindow *current;
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ count++;
+ }
+
+ if (count > 1) {
+ LOG("WARNING: Your criteria for the exec command match %d containers, "
+ "so the command will execute this many times.\n",
+ count);
+ }
+
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
+ start_application(command, no_startup_id);
+ }
ysuccess(true);
}
+#define CMD_FOCUS_WARN_CHILDREN \
+ do { \
+ int count = 0; \
+ owindow *current; \
+ TAILQ_FOREACH(current, &owindows, owindows) { \
+ count++; \
+ } \
+ \
+ if (count > 1) { \
+ LOG("WARNING: Your criteria for the focus command matches %d containers, " \
+ "while only exactly one container can be focused at a time.\n", \
+ count); \
+ } \
+ } while (0)
+
/*
- * Implementation of 'focus left|right|up|down'.
+ * Implementation of 'focus left|right|up|down|next|prev'.
*
*/
-void cmd_focus_direction(I3_CMD, const char *direction) {
- switch (parse_direction(direction)) {
- case D_LEFT:
- tree_next('p', HORIZ);
- break;
- case D_RIGHT:
- tree_next('n', HORIZ);
- break;
- case D_UP:
- tree_next('p', VERT);
- break;
- case D_DOWN:
- tree_next('n', VERT);
- break;
+void cmd_focus_direction(I3_CMD, const char *direction_str) {
+ HANDLE_EMPTY_MATCH;
+ CMD_FOCUS_WARN_CHILDREN;
+
+ direction_t direction;
+ position_t position;
+ bool auto_direction = true;
+ if (strcmp(direction_str, "prev") == 0) {
+ position = BEFORE;
+ } else if (strcmp(direction_str, "next") == 0) {
+ position = AFTER;
+ } else {
+ auto_direction = false;
+ direction = parse_direction(direction_str);
+ }
+
+ owindow *current;
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ Con *ws = con_get_workspace(current->con);
+ if (!ws || con_is_internal(ws)) {
+ continue;
+ }
+ if (auto_direction) {
+ orientation_t o = con_orientation(current->con->parent);
+ direction = direction_from_orientation_position(o, position);
+ }
+ tree_next(current->con, direction);
}
cmd_output->needs_tree_render = true;
@@ -1239,17 +1283,29 @@ void cmd_focus_direction(I3_CMD, const char *direction) {
}
/*
- * Focus a container and disable any other fullscreen container not permitting the focus.
+ * Implementation of 'focus next|prev sibling'
*
*/
-static void cmd_focus_force_focus(Con *con) {
- /* Disable fullscreen container in workspace with container to be focused. */
- Con *ws = con_get_workspace(con);
- Con *fullscreen_on_ws = con_get_fullscreen_covering_ws(ws);
- if (fullscreen_on_ws && fullscreen_on_ws != con && !con_has_parent(con, fullscreen_on_ws)) {
- con_disable_fullscreen(fullscreen_on_ws);
+void cmd_focus_sibling(I3_CMD, const char *direction_str) {
+ HANDLE_EMPTY_MATCH;
+ CMD_FOCUS_WARN_CHILDREN;
+
+ const position_t direction = (STARTS_WITH(direction_str, "prev")) ? BEFORE : AFTER;
+ owindow *current;
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ Con *ws = con_get_workspace(current->con);
+ if (!ws || con_is_internal(ws)) {
+ continue;
+ }
+ Con *next = get_tree_next_sibling(current->con, direction);
+ if (next) {
+ con_activate(next);
+ }
}
- con_activate(con);
+
+ cmd_output->needs_tree_render = true;
+ // XXX: default reply for now, make this a better reply
+ ysuccess(true);
}
/*
@@ -1276,7 +1332,7 @@ void cmd_focus_window_mode(I3_CMD, const char *window_mode) {
(!to_floating && current->type == CT_FLOATING_CON))
continue;
- cmd_focus_force_focus(con_descend_focused(current));
+ con_activate_unblock(con_descend_focused(current));
success = true;
break;
}
@@ -1329,12 +1385,15 @@ void cmd_focus(I3_CMD) {
ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
yerror("You have to specify which window/container should be focused");
-
+ return;
+ } else if (TAILQ_EMPTY(&owindows)) {
+ yerror("No window matches given criteria");
return;
}
+ CMD_FOCUS_WARN_CHILDREN;
+
Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
- int count = 0;
owindow *current;
TAILQ_FOREACH(current, &owindows, owindows) {
Con *ws = con_get_workspace(current->con);
@@ -1346,43 +1405,18 @@ void cmd_focus(I3_CMD) {
/* In case this is a scratchpad window, call scratchpad_show(). */
if (ws == __i3_scratch) {
scratchpad_show(current->con);
- count++;
/* While for the normal focus case we can change focus multiple
* times and only a single window ends up focused, we could show
* multiple scratchpad windows. So, rather break here. */
break;
}
- /* If the container is not on the current workspace,
- * workspace_show() will switch to a different workspace and (if
- * enabled) trigger a mouse pointer warp to the currently focused
- * container (!) on the target workspace.
- *
- * Therefore, before calling workspace_show(), we make sure that
- * 'current' will be focused on the workspace. However, we cannot
- * just con_focus(current) because then the pointer will not be
- * warped at all (the code thinks we are already there).
- *
- * So we focus 'current' to make it the currently focused window of
- * the target workspace, then revert focus. */
- Con *currently_focused = focused;
- cmd_focus_force_focus(current->con);
- con_activate(currently_focused);
-
- /* Now switch to the workspace, then focus */
- workspace_show(ws);
LOG("focusing %p / %s\n", current->con, current->con->name);
- con_activate(current->con);
- count++;
+ con_activate_unblock(current->con);
}
- if (count > 1)
- LOG("WARNING: Your criteria for the focus command matches %d containers, "
- "while only exactly one container can be focused at a time.\n",
- count);
-
cmd_output->needs_tree_render = true;
- ysuccess(count > 0);
+ ysuccess(true);
}
/*
@@ -1490,9 +1524,11 @@ void cmd_move_direction(I3_CMD, const char *direction_str, long move_px) {
}
}
- /* the move command should not disturb focus */
- if (focused != initially_focused)
+ /* The move command should not disturb focus. con_exists is called because
+ * tree_move calls tree_flatten. */
+ if (focused != initially_focused && con_exists(initially_focused)) {
con_activate(initially_focused);
+ }
// XXX: default reply for now, make this a better reply
ysuccess(true);
@@ -1562,7 +1598,7 @@ void cmd_layout_toggle(I3_CMD, const char *toggle_mode) {
*/
void cmd_exit(I3_CMD) {
LOG("Exiting due to user command.\n");
- exit(0);
+ exit(EXIT_SUCCESS);
/* unreached */
}
@@ -2013,6 +2049,13 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
con_focus(previously_focused);
}
+ /* Let back-and-forth work after renaming the previous workspace.
+ * See #3694. */
+ if (previous_workspace_name && !strcmp(previous_workspace_name, old_name_copy)) {
+ FREE(previous_workspace_name);
+ previous_workspace_name = sstrdup(new_name);
+ }
+
cmd_output->needs_tree_render = true;
ysuccess(true);
diff --git a/src/commands_parser.c b/src/commands_parser.c
index d4ae430b..f734a7aa 100644
--- a/src/commands_parser.c
+++ b/src/commands_parser.c
@@ -106,7 +106,7 @@ static void push_string(const char *identifier, char *str) {
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
// TODO move to a common util
@@ -128,7 +128,7 @@ static void push_long(const char *identifier, long num) {
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
// TODO move to a common util
diff --git a/src/con.c b/src/con.c
index 10afb4cc..4d1c6167 100644
--- a/src/con.c
+++ b/src/con.c
@@ -266,6 +266,41 @@ void con_activate(Con *con) {
}
/*
+ * Activates the container like in con_activate but removes fullscreen
+ * restrictions and properly warps the pointer if needed.
+ *
+ */
+void con_activate_unblock(Con *con) {
+ Con *ws = con_get_workspace(con);
+ Con *previous_focus = focused;
+ Con *fullscreen_on_ws = con_get_fullscreen_covering_ws(ws);
+
+ if (fullscreen_on_ws && fullscreen_on_ws != con && !con_has_parent(con, fullscreen_on_ws)) {
+ con_disable_fullscreen(fullscreen_on_ws);
+ }
+
+ con_activate(con);
+
+ /* If the container is not on the current workspace, workspace_show() will
+ * switch to a different workspace and (if enabled) trigger a mouse pointer
+ * warp to the currently focused container (!) on the target workspace.
+ *
+ * Therefore, before calling workspace_show(), we make sure that 'con' will
+ * be focused on the workspace. However, we cannot just con_focus(con)
+ * because then the pointer will not be warped at all (the code thinks we
+ * are already there).
+ *
+ * So we focus 'con' to make it the currently focused window of the target
+ * workspace, then revert focus. */
+ if (ws != con_get_workspace(previous_focus)) {
+ con_activate(previous_focus);
+ /* Now switch to the workspace, then focus */
+ workspace_show(ws);
+ con_activate(con);
+ }
+}
+
+/*
* Closes the given container.
*
*/
@@ -1160,7 +1195,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
/* 1: save the container which is going to be focused after the current
* container is moved away */
Con *focus_next = NULL;
- if (!ignore_focus && source_ws == current_ws) {
+ if (!ignore_focus && source_ws == current_ws && target_ws != source_ws) {
focus_next = con_descend_focused(source_ws);
if (focus_next == con || con_has_parent(focus_next, con)) {
focus_next = con_next_focused(con);
@@ -1258,34 +1293,17 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
/* 8. If anything within the container is associated with a startup sequence,
* delete it so child windows won't be created on the old workspace. */
- struct Startup_Sequence *sequence;
- xcb_get_property_cookie_t cookie;
- xcb_get_property_reply_t *startup_id_reply;
-
if (!con_is_leaf(con)) {
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
if (!child->window)
continue;
-
- cookie = xcb_get_property(conn, false, child->window->id,
- A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
- startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
-
- sequence = startup_sequence_get(child->window, startup_id_reply, true);
- if (sequence != NULL)
- startup_sequence_delete(sequence);
+ startup_sequence_delete_by_window(child->window);
}
}
if (con->window) {
- cookie = xcb_get_property(conn, false, con->window->id,
- A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
- startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
-
- sequence = startup_sequence_get(con->window, startup_id_reply, true);
- if (sequence != NULL)
- startup_sequence_delete(sequence);
+ startup_sequence_delete_by_window(con->window);
}
/* 9. If the container was marked urgent, move the urgency hint. */
@@ -1315,6 +1333,13 @@ bool con_move_to_mark(Con *con, const char *mark) {
return false;
}
+ /* For target containers in the scratchpad, we just send the window to the scratchpad. */
+ if (con_get_workspace(target) == workspace_get("__i3_scratch", NULL)) {
+ DLOG("target container is in the scratchpad, moving container to scratchpad.\n");
+ scratchpad_move(con);
+ return true;
+ }
+
/* For floating target containers, we just send the window to the same workspace. */
if (con_is_floating(target)) {
DLOG("target container is floating, moving container to target's workspace.\n");
@@ -1322,8 +1347,8 @@ bool con_move_to_mark(Con *con, const char *mark) {
return true;
}
- if (target->type == CT_WORKSPACE) {
- DLOG("target container is a workspace, simply moving the container there.\n");
+ if (target->type == CT_WORKSPACE && con_is_leaf(target)) {
+ DLOG("target container is an empty workspace, simply moving the container there.\n");
con_move_to_workspace(con, target, true, false, false);
return true;
}
@@ -2401,3 +2426,40 @@ bool con_swap(Con *first, Con *second) {
uint32_t con_rect_size_in_orientation(Con *con) {
return (con_orientation(con) == HORIZ ? con->rect.width : con->rect.height);
}
+
+/*
+ * Merges container specific data that should move with the window (e.g. marks,
+ * title format, and the window itself) into another container, and closes the
+ * old container.
+ *
+ */
+void con_merge_into(Con *old, Con *new) {
+ new->window = old->window;
+ old->window = NULL;
+
+ if (old->title_format) {
+ FREE(new->title_format);
+ new->title_format = old->title_format;
+ old->title_format = NULL;
+ }
+
+ if (old->sticky_group) {
+ FREE(new->sticky_group);
+ new->sticky_group = old->sticky_group;
+ old->sticky_group = NULL;
+ }
+
+ new->sticky = old->sticky;
+
+ con_set_urgency(new, old->urgent);
+
+ mark_t *mark;
+ TAILQ_FOREACH(mark, &(old->marks_head), marks) {
+ TAILQ_INSERT_TAIL(&(new->marks_head), mark, marks);
+ ipc_send_window_event("mark", new);
+ }
+ new->mark_changed = (TAILQ_FIRST(&(old->marks_head)) != NULL);
+ TAILQ_INIT(&(old->marks_head));
+
+ tree_close_internal(old, DONT_KILL_WINDOW, false);
+}
diff --git a/src/config_directives.c b/src/config_directives.c
index fb3fba41..4712296c 100644
--- a/src/config_directives.c
+++ b/src/config_directives.c
@@ -268,6 +268,8 @@ CFGFUN(disable_randr15, const char *value) {
CFGFUN(focus_wrapping, const char *value) {
if (strcmp(value, "force") == 0) {
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
+ } else if (strcmp(value, "workspace") == 0) {
+ config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE;
} else if (eval_boolstr(value)) {
config.focus_wrapping = FOCUS_WRAPPING_ON;
} else {
@@ -646,6 +648,10 @@ CFGFUN(bar_workspace_buttons, const char *value) {
current_bar->hide_workspace_buttons = !eval_boolstr(value);
}
+CFGFUN(bar_workspace_min_width, const long width) {
+ current_bar->workspace_min_width = width;
+}
+
CFGFUN(bar_strip_workspace_numbers, const char *value) {
current_bar->strip_workspace_numbers = eval_boolstr(value);
}
diff --git a/src/config_parser.c b/src/config_parser.c
index c238f1b9..bbeca576 100644
--- a/src/config_parser.c
+++ b/src/config_parser.c
@@ -126,7 +126,7 @@ static void push_string(const char *identifier, const char *str) {
fprintf(stderr, "BUG: config_parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
static void push_long(const char *identifier, long num) {
@@ -146,7 +146,7 @@ static void push_long(const char *identifier, long num) {
fprintf(stderr, "BUG: config_parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
static const char *get_string(const char *identifier) {
diff --git a/src/drag.c b/src/drag.c
new file mode 100644
index 00000000..6b05311a
--- /dev/null
+++ b/src/drag.c
@@ -0,0 +1,252 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * drag.c: click and drag.
+ *
+ */
+#include "all.h"
+
+/* Custom data structure used to track dragging-related events. */
+struct drag_x11_cb {
+ ev_prepare prepare;
+
+ /* Whether this modal event loop should be exited and with which result. */
+ drag_result_t result;
+
+ /* The container that is being dragged or resized, or NULL if this is a
+ * drag of the resize handle. */
+ Con *con;
+
+ /* The original event that initiated the drag. */
+ const xcb_button_press_event_t *event;
+
+ /* The dimensions of con when the loop was started. */
+ Rect old_rect;
+
+ /* The callback to invoke after every pointer movement. */
+ callback_t callback;
+
+ /* Drag distance threshold exceeded. If use_threshold is not set, then
+ * threshold_exceeded is always true. */
+ bool threshold_exceeded;
+
+ /* Cursor to set after the threshold is exceeded. */
+ xcb_cursor_t xcursor;
+
+ /* User data pointer for callback. */
+ const void *extra;
+};
+
+static bool threshold_exceeded(uint32_t x1, uint32_t y1,
+ uint32_t x2, uint32_t y2) {
+ const uint32_t threshold = 9;
+ return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) > threshold * threshold;
+}
+
+static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
+ xcb_motion_notify_event_t *last_motion_notify = NULL;
+ xcb_generic_event_t *event;
+
+ while ((event = xcb_poll_for_event(conn)) != NULL) {
+ if (event->response_type == 0) {
+ xcb_generic_error_t *error = (xcb_generic_error_t *)event;
+ DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n",
+ error->sequence, error->error_code);
+ free(event);
+ continue;
+ }
+
+ /* Strip off the highest bit (set if the event is generated) */
+ int type = (event->response_type & 0x7F);
+
+ switch (type) {
+ case XCB_BUTTON_RELEASE:
+ dragloop->result = DRAG_SUCCESS;
+ break;
+
+ case XCB_KEY_PRESS:
+ DLOG("A key was pressed during drag, reverting changes.\n");
+ dragloop->result = DRAG_REVERT;
+ handle_event(type, event);
+ break;
+
+ case XCB_UNMAP_NOTIFY: {
+ xcb_unmap_notify_event_t *unmap_event = (xcb_unmap_notify_event_t *)event;
+ Con *con = con_by_window_id(unmap_event->window);
+
+ if (con != NULL) {
+ DLOG("UnmapNotify for window 0x%08x (container %p)\n", unmap_event->window, con);
+
+ if (con_get_workspace(con) == con_get_workspace(focused)) {
+ DLOG("UnmapNotify for a managed window on the current workspace, aborting\n");
+ dragloop->result = DRAG_ABORT;
+ }
+ }
+
+ handle_event(type, event);
+ break;
+ }
+
+ case XCB_MOTION_NOTIFY:
+ /* motion_notify events are saved for later */
+ FREE(last_motion_notify);
+ last_motion_notify = (xcb_motion_notify_event_t *)event;
+ break;
+
+ default:
+ DLOG("Passing to original handler\n");
+ handle_event(type, event);
+ break;
+ }
+
+ if (last_motion_notify != (xcb_motion_notify_event_t *)event)
+ free(event);
+
+ if (dragloop->result != DRAGGING) {
+ ev_break(EV_A_ EVBREAK_ONE);
+ if (dragloop->result == DRAG_SUCCESS) {
+ /* Ensure motion notify events are handled. */
+ break;
+ } else {
+ free(last_motion_notify);
+ return true;
+ }
+ }
+ }
+
+ if (last_motion_notify == NULL) {
+ return true;
+ }
+
+ if (!dragloop->threshold_exceeded &&
+ threshold_exceeded(last_motion_notify->root_x, last_motion_notify->root_y,
+ dragloop->event->root_x, dragloop->event->root_y)) {
+ if (dragloop->xcursor != XCB_NONE) {
+ xcb_change_active_pointer_grab(
+ conn,
+ dragloop->xcursor,
+ XCB_CURRENT_TIME,
+ XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION);
+ }
+ dragloop->threshold_exceeded = true;
+ }
+
+ /* Ensure that we are either dragging the resize handle (con is NULL) or that the
+ * container still exists. The latter might not be true, e.g., if the window closed
+ * for any reason while the user was dragging it. */
+ if (dragloop->threshold_exceeded && (!dragloop->con || con_exists(dragloop->con))) {
+ dragloop->callback(
+ dragloop->con,
+ &(dragloop->old_rect),
+ last_motion_notify->root_x,
+ last_motion_notify->root_y,
+ dragloop->event,
+ dragloop->extra);
+ }
+ FREE(last_motion_notify);
+
+ xcb_flush(conn);
+ return dragloop->result != DRAGGING;
+}
+
+static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
+ struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
+ while (!drain_drag_events(EV_A, dragloop)) {
+ /* repeatedly drain events: draining might produce additional ones */
+ }
+}
+
+/*
+ * This function grabs your pointer and keyboard and lets you drag stuff around
+ * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
+ * be received and the given callback will be called with the parameters
+ * specified (client, the original event), the original rect of the client,
+ * and the new coordinates (x, y).
+ *
+ * If use_threshold is set, dragging only starts after the user moves the
+ * pointer past a certain threshold. That is, the cursor will not be set and the
+ * callback will not be called until then.
+ *
+ */
+drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
+ xcb_window_t confine_to, int cursor,
+ bool use_threshold, callback_t callback,
+ const void *extra) {
+ xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE;
+
+ /* Grab the pointer */
+ xcb_grab_pointer_cookie_t cookie;
+ xcb_grab_pointer_reply_t *reply;
+ xcb_generic_error_t *error;
+
+ cookie = xcb_grab_pointer(conn,
+ false, /* get all pointer events specified by the following mask */
+ root, /* grab the root window */
+ XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
+ XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
+ XCB_GRAB_MODE_ASYNC, /* keyboard mode */
+ confine_to, /* confine_to = in which window should the cursor stay */
+ use_threshold ? XCB_NONE : xcursor, /* possibly display a special cursor */
+ XCB_CURRENT_TIME);
+
+ if ((reply = xcb_grab_pointer_reply(conn, cookie, &error)) == NULL) {
+ ELOG("Could not grab pointer (error_code = %d)\n", error->error_code);
+ free(error);
+ return DRAG_ABORT;
+ }
+
+ free(reply);
+
+ /* Grab the keyboard */
+ xcb_grab_keyboard_cookie_t keyb_cookie;
+ xcb_grab_keyboard_reply_t *keyb_reply;
+
+ keyb_cookie = xcb_grab_keyboard(conn,
+ false, /* get all keyboard events */
+ root, /* grab the root window */
+ XCB_CURRENT_TIME,
+ XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */
+ XCB_GRAB_MODE_ASYNC /* keyboard mode */
+ );
+
+ if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, &error)) == NULL) {
+ ELOG("Could not grab keyboard (error_code = %d)\n", error->error_code);
+ free(error);
+ xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
+ return DRAG_ABORT;
+ }
+
+ free(keyb_reply);
+
+ /* Go into our own event loop */
+ struct drag_x11_cb loop = {
+ .result = DRAGGING,
+ .con = con,
+ .event = event,
+ .callback = callback,
+ .threshold_exceeded = !use_threshold,
+ .xcursor = xcursor,
+ .extra = extra,
+ };
+ ev_prepare *prepare = &loop.prepare;
+ if (con)
+ loop.old_rect = con->rect;
+ ev_prepare_init(prepare, xcb_drag_prepare_cb);
+ prepare->data = &loop;
+ main_set_x11_cb(false);
+ ev_prepare_start(main_loop, prepare);
+
+ ev_loop(main_loop, 0);
+
+ ev_prepare_stop(main_loop, prepare);
+ main_set_x11_cb(true);
+
+ xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
+ xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
+ xcb_flush(conn);
+
+ return loop.result;
+}
diff --git a/src/fake_outputs.c b/src/fake_outputs.c
index 5f3622d4..e69acc8a 100644
--- a/src/fake_outputs.c
+++ b/src/fake_outputs.c
@@ -82,6 +82,6 @@ void fake_outputs_init(const char *output_spec) {
if (num_screens == 0) {
ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
- exit(0);
+ exit(EXIT_FAILURE);
}
}
diff --git a/src/floating.c b/src/floating.c
index 79f1d3d3..69a4f54c 100644
--- a/src/floating.c
+++ b/src/floating.c
@@ -322,11 +322,10 @@ void floating_enable(Con *con, bool automatic) {
DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height);
DLOG("Geometry = (%d, %d) with %d x %d\n", con->geometry.x, con->geometry.y, con->geometry.width, con->geometry.height);
- Rect zero = {0, 0, 0, 0};
nc->rect = con->geometry;
/* If the geometry was not set (split containers), we need to determine a
* sensible one by combining the geometry of all children */
- if (memcmp(&(nc->rect), &zero, sizeof(Rect)) == 0) {
+ if (rect_equals(nc->rect, (Rect){0, 0, 0, 0})) {
DLOG("Geometry not set, combining children\n");
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
@@ -372,6 +371,7 @@ void floating_enable(Con *con, bool automatic) {
if (nc->rect.x == 0 && nc->rect.y == 0) {
Con *leader;
if (con->window && con->window->leader != XCB_NONE &&
+ con->window->id != con->window->leader &&
(leader = con_by_window_id(con->window->leader)) != NULL) {
DLOG("Centering above leader\n");
floating_center(nc, leader->rect);
@@ -421,7 +421,7 @@ void floating_enable(Con *con, bool automatic) {
ipc_send_window_event("floating", con);
}
-void floating_disable(Con *con, bool automatic) {
+void floating_disable(Con *con) {
if (!con_is_floating(con)) {
LOG("Container isn't floating, not doing anything.\n");
return;
@@ -469,7 +469,7 @@ void toggle_floating_mode(Con *con, bool automatic) {
if (con_is_floating(con)) {
LOG("already floating, re-setting to tiling\n");
- floating_disable(con, automatic);
+ floating_disable(con);
return;
}
@@ -492,6 +492,11 @@ void floating_raise_con(Con *con) {
*
*/
bool floating_maybe_reassign_ws(Con *con) {
+ if (con_is_internal(con_get_workspace(con))) {
+ DLOG("Con in an internal workspace\n");
+ return false;
+ }
+
Output *output = get_output_from_rect(con->rect);
if (!output) {
@@ -561,8 +566,6 @@ void floating_move_to_pointer(Con *con) {
}
DRAGGING_CB(drag_window_callback) {
- const struct xcb_button_press_event_t *event = extra;
-
/* Reposition the client correctly while moving */
con->rect.x = old_rect->x + (new_x - event->root_x);
con->rect.y = old_rect->y + (new_y - event->root_y);
@@ -584,7 +587,7 @@ DRAGGING_CB(drag_window_callback) {
* Calls the drag_pointer function with the drag_window callback
*
*/
-void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
+void floating_drag_window(Con *con, const xcb_button_press_event_t *event, bool use_threshold) {
DLOG("floating_drag_window\n");
/* Push changes before dragging, so that the window gets raised now and not
@@ -595,7 +598,7 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
Rect initial_rect = con->rect;
/* Drag the window */
- drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
+ drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, use_threshold, drag_window_callback, NULL);
if (!con_exists(con)) {
DLOG("The container has been closed in the meantime.\n");
@@ -625,12 +628,10 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
struct resize_window_callback_params {
const border_t corner;
const bool proportional;
- const xcb_button_press_event_t *event;
};
DRAGGING_CB(resize_window_callback) {
const struct resize_window_callback_params *params = extra;
- const xcb_button_press_event_t *event = params->event;
border_t corner = params->corner;
int32_t dest_x = con->rect.x;
@@ -706,12 +707,12 @@ void floating_resize_window(Con *con, const bool proportional,
cursor = (corner & BORDER_LEFT) ? XCURSOR_CURSOR_BOTTOM_LEFT_CORNER : XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER;
}
- struct resize_window_callback_params params = {corner, proportional, event};
+ struct resize_window_callback_params params = {corner, proportional};
/* get the initial rect in case of revert/cancel */
Rect initial_rect = con->rect;
- drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, &params);
+ drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, cursor, false, resize_window_callback, &params);
if (!con_exists(con)) {
DLOG("The container has been closed in the meantime.\n");
@@ -727,209 +728,6 @@ void floating_resize_window(Con *con, const bool proportional,
con->scratchpad_state = SCRATCHPAD_CHANGED;
}
-/* Custom data structure used to track dragging-related events. */
-struct drag_x11_cb {
- ev_prepare prepare;
-
- /* Whether this modal event loop should be exited and with which result. */
- drag_result_t result;
-
- /* The container that is being dragged or resized, or NULL if this is a
- * drag of the resize handle. */
- Con *con;
-
- /* The dimensions of con when the loop was started. */
- Rect old_rect;
-
- /* The callback to invoke after every pointer movement. */
- callback_t callback;
-
- /* User data pointer for callback. */
- const void *extra;
-};
-
-static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
- xcb_motion_notify_event_t *last_motion_notify = NULL;
- xcb_generic_event_t *event;
-
- while ((event = xcb_poll_for_event(conn)) != NULL) {
- if (event->response_type == 0) {
- xcb_generic_error_t *error = (xcb_generic_error_t *)event;
- DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n",
- error->sequence, error->error_code);
- free(event);
- continue;
- }
-
- /* Strip off the highest bit (set if the event is generated) */
- int type = (event->response_type & 0x7F);
-
- switch (type) {
- case XCB_BUTTON_RELEASE:
- dragloop->result = DRAG_SUCCESS;
- break;
-
- case XCB_KEY_PRESS:
- DLOG("A key was pressed during drag, reverting changes.\n");
- dragloop->result = DRAG_REVERT;
- handle_event(type, event);
- break;
-
- case XCB_UNMAP_NOTIFY: {
- xcb_unmap_notify_event_t *unmap_event = (xcb_unmap_notify_event_t *)event;
- Con *con = con_by_window_id(unmap_event->window);
-
- if (con != NULL) {
- DLOG("UnmapNotify for window 0x%08x (container %p)\n", unmap_event->window, con);
-
- if (con_get_workspace(con) == con_get_workspace(focused)) {
- DLOG("UnmapNotify for a managed window on the current workspace, aborting\n");
- dragloop->result = DRAG_ABORT;
- }
- }
-
- handle_event(type, event);
- break;
- }
-
- case XCB_MOTION_NOTIFY:
- /* motion_notify events are saved for later */
- FREE(last_motion_notify);
- last_motion_notify = (xcb_motion_notify_event_t *)event;
- break;
-
- default:
- DLOG("Passing to original handler\n");
- handle_event(type, event);
- break;
- }
-
- if (last_motion_notify != (xcb_motion_notify_event_t *)event)
- free(event);
-
- if (dragloop->result != DRAGGING) {
- ev_break(EV_A_ EVBREAK_ONE);
- if (dragloop->result == DRAG_SUCCESS) {
- /* Ensure motion notify events are handled. */
- break;
- } else {
- free(last_motion_notify);
- return true;
- }
- }
- }
-
- if (last_motion_notify == NULL) {
- return true;
- }
-
- /* Ensure that we are either dragging the resize handle (con is NULL) or that the
- * container still exists. The latter might not be true, e.g., if the window closed
- * for any reason while the user was dragging it. */
- if (!dragloop->con || con_exists(dragloop->con)) {
- dragloop->callback(
- dragloop->con,
- &(dragloop->old_rect),
- last_motion_notify->root_x,
- last_motion_notify->root_y,
- dragloop->extra);
- }
- FREE(last_motion_notify);
-
- xcb_flush(conn);
- return dragloop->result != DRAGGING;
-}
-
-static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
- struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
- while (!drain_drag_events(EV_A, dragloop)) {
- /* repeatedly drain events: draining might produce additional ones */
- }
-}
-
-/*
- * This function grabs your pointer and keyboard and lets you drag stuff around
- * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
- * be received and the given callback will be called with the parameters
- * specified (client, border on which the click originally was), the original
- * rect of the client, the event and the new coordinates (x, y).
- *
- */
-drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to,
- border_t border, int cursor, callback_t callback, const void *extra) {
- xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE;
-
- /* Grab the pointer */
- xcb_grab_pointer_cookie_t cookie;
- xcb_grab_pointer_reply_t *reply;
- xcb_generic_error_t *error;
-
- cookie = xcb_grab_pointer(conn,
- false, /* get all pointer events specified by the following mask */
- root, /* grab the root window */
- XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
- XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
- XCB_GRAB_MODE_ASYNC, /* keyboard mode */
- confine_to, /* confine_to = in which window should the cursor stay */
- xcursor, /* possibly display a special cursor */
- XCB_CURRENT_TIME);
-
- if ((reply = xcb_grab_pointer_reply(conn, cookie, &error)) == NULL) {
- ELOG("Could not grab pointer (error_code = %d)\n", error->error_code);
- free(error);
- return DRAG_ABORT;
- }
-
- free(reply);
-
- /* Grab the keyboard */
- xcb_grab_keyboard_cookie_t keyb_cookie;
- xcb_grab_keyboard_reply_t *keyb_reply;
-
- keyb_cookie = xcb_grab_keyboard(conn,
- false, /* get all keyboard events */
- root, /* grab the root window */
- XCB_CURRENT_TIME,
- XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */
- XCB_GRAB_MODE_ASYNC /* keyboard mode */
- );
-
- if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, &error)) == NULL) {
- ELOG("Could not grab keyboard (error_code = %d)\n", error->error_code);
- free(error);
- xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
- return DRAG_ABORT;
- }
-
- free(keyb_reply);
-
- /* Go into our own event loop */
- struct drag_x11_cb loop = {
- .result = DRAGGING,
- .con = con,
- .callback = callback,
- .extra = extra,
- };
- ev_prepare *prepare = &loop.prepare;
- if (con)
- loop.old_rect = con->rect;
- ev_prepare_init(prepare, xcb_drag_prepare_cb);
- prepare->data = &loop;
- main_set_x11_cb(false);
- ev_prepare_start(main_loop, prepare);
-
- ev_loop(main_loop, 0);
-
- ev_prepare_stop(main_loop, prepare);
- main_set_x11_cb(true);
-
- xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
- xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
- xcb_flush(conn);
-
- return loop.result;
-}
-
/*
* Repositions the CT_FLOATING_CON to have the coordinates specified by
* newrect, but only if the coordinates are not out-of-bounds. Also reassigns
diff --git a/src/handlers.c b/src/handlers.c
index ae42b82e..24919b28 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -412,7 +412,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(workspace))) {
DLOG("Focusing con = %p\n", con);
workspace_show(workspace);
- con_activate(con);
+ con_activate_unblock(con);
tree_render();
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(workspace))) {
DLOG("Marking con = %p urgent\n", con);
@@ -441,7 +441,7 @@ static void handle_screen_change(xcb_generic_event_t *e) {
xcb_get_geometry_reply_t *reply = xcb_get_geometry_reply(conn, cookie, NULL);
if (reply == NULL) {
ELOG("Could not get geometry of the root window, exiting\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
DLOG("root geometry reply: (%d, %d) %d x %d\n", reply->x, reply->y, reply->width, reply->height);
@@ -565,7 +565,9 @@ static bool handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t
char *old_name = (con->window->name != NULL ? sstrdup(i3string_as_utf8(con->window->name)) : NULL);
- window_update_name(con->window, prop, false);
+ window_update_name(con->window, prop);
+
+ con = remanage_window(con);
x_push_changes(croot);
@@ -590,7 +592,9 @@ static bool handle_windowname_change_legacy(void *data, xcb_connection_t *conn,
char *old_name = (con->window->name != NULL ? sstrdup(i3string_as_utf8(con->window->name)) : NULL);
- window_update_name_legacy(con->window, prop, false);
+ window_update_name_legacy(con->window, prop);
+
+ con = remanage_window(con);
x_push_changes(croot);
@@ -612,7 +616,9 @@ static bool handle_windowrole_change(void *data, xcb_connection_t *conn, uint8_t
if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
return false;
- window_update_role(con->window, prop, false);
+ window_update_role(con->window, prop);
+
+ con = remanage_window(con);
return true;
}
@@ -752,7 +758,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
workspace_show(ws);
/* Re-set focus, even if unchanged from i3’s perspective. */
focused_id = XCB_NONE;
- con_activate(con);
+ con_activate_unblock(con);
}
} else {
/* Request is from an application. */
@@ -763,8 +769,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
DLOG("Focusing con = %p\n", con);
- workspace_show(ws);
- con_activate(con);
+ con_activate_unblock(con);
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
DLOG("Marking con = %p urgent\n", con);
con_set_urgency(con, true);
@@ -907,7 +912,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
.event_y = y_root - (con->rect.y)};
switch (direction) {
case _NET_WM_MOVERESIZE_MOVE:
- floating_drag_window(con->parent, &fake);
+ floating_drag_window(con->parent, &fake, false);
break;
case _NET_WM_MOVERESIZE_SIZE_TOPLEFT ... _NET_WM_MOVERESIZE_SIZE_LEFT:
floating_resize_window(con->parent, false, &fake);
@@ -1109,14 +1114,8 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
DLOG("focus is different / refocusing floating window: updating decorations\n");
- /* Get the currently focused workspace to check if the focus change also
- * involves changing workspaces. If so, we need to call workspace_show() to
- * correctly update state and send the IPC event. */
- Con *ws = con_get_workspace(con);
- if (ws != con_get_workspace(focused))
- workspace_show(ws);
+ con_activate_unblock(con);
- con_activate(con);
/* We update focused_id because we don’t need to set focus again */
focused_id = event->event;
tree_render();
@@ -1158,7 +1157,9 @@ static bool handle_class_change(void *data, xcb_connection_t *conn, uint8_t stat
return false;
}
- window_update_class(con->window, prop, false);
+ window_update_class(con->window, prop);
+
+ con = remanage_window(con);
return true;
}
diff --git a/src/ipc.c b/src/ipc.c
index 0ffdfebf..c844409c 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -528,6 +528,36 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
else
y(null);
+ ystr("window_type");
+ if (con->window) {
+ if (con->window->window_type == A__NET_WM_WINDOW_TYPE_NORMAL) {
+ ystr("normal");
+ } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_DOCK) {
+ ystr("dock");
+ } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_DIALOG) {
+ ystr("dialog");
+ } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_UTILITY) {
+ ystr("utility");
+ } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_TOOLBAR) {
+ ystr("toolbar");
+ } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_SPLASH) {
+ ystr("splash");
+ } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_MENU) {
+ ystr("menu");
+ } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU) {
+ ystr("dropdown_menu");
+ } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_POPUP_MENU) {
+ ystr("popup_menu");
+ } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_TOOLTIP) {
+ ystr("tooltip");
+ } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_NOTIFICATION) {
+ ystr("notification");
+ } else {
+ ystr("unknown");
+ }
+ } else
+ y(null);
+
if (con->window && !inplace_restart) {
/* Window properties are useless to preserve when restarting because
* they will be queried again anyway. However, for i3-save-tree(1),
@@ -789,6 +819,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
ystr("workspace_buttons");
y(bool, !config->hide_workspace_buttons);
+ ystr("workspace_min_width");
+ y(integer, config->workspace_min_width);
+
ystr("strip_workspace_numbers");
y(bool, config->strip_workspace_numbers);
@@ -873,6 +906,9 @@ IPC_HANDLER(get_workspaces) {
assert(ws->type == CT_WORKSPACE);
y(map_open);
+ ystr("id");
+ y(integer, (uintptr_t)ws);
+
ystr("num");
y(integer, ws->num);
diff --git a/src/load_layout.c b/src/load_layout.c
index 47daada1..4f107cd6 100644
--- a/src/load_layout.c
+++ b/src/load_layout.c
@@ -141,8 +141,7 @@ static int json_end_map(void *ctx) {
// Also set a size if none was supplied, otherwise the placeholder
// window cannot be created as X11 requests with width=0 or
// height=0 are invalid.
- const Rect zero = {0, 0, 0, 0};
- if (memcmp(&(json_node->rect), &zero, sizeof(Rect)) == 0) {
+ if (rect_equals(json_node->rect, (Rect){0, 0, 0, 0})) {
DLOG("Geometry not set, combining children\n");
Con *child;
TAILQ_FOREACH(child, &(json_node->nodes_head), nodes) {
diff --git a/src/main.c b/src/main.c
index c76f9ecc..e4c6b2c9 100644
--- a/src/main.c
+++ b/src/main.c
@@ -93,6 +93,11 @@ bool shape_supported = true;
bool force_xinerama = false;
+/* Define all atoms as global variables */
+#define xmacro(atom) xcb_atom_t A_##atom;
+#include "atoms.xmacro"
+#undef xmacro
+
/*
* This callback is only a dummy, see xcb_prepare_cb.
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
@@ -429,12 +434,12 @@ int main(int argc, char *argv[]) {
"\ti3 floating toggle\n"
"\ti3 kill window\n"
"\n");
- exit(EXIT_FAILURE);
+ exit(opt == 'h' ? EXIT_SUCCESS : EXIT_FAILURE);
}
}
if (only_check_config) {
- exit(load_configuration(override_configpath, C_VALIDATE) ? 0 : 1);
+ exit(load_configuration(override_configpath, C_VALIDATE) ? EXIT_SUCCESS : EXIT_FAILURE);
}
/* If the user passes more arguments, we act like i3-msg would: Just send
@@ -813,12 +818,13 @@ int main(int argc, char *argv[]) {
if (!output) {
ELOG("ERROR: No screen at (%d, %d), starting on the first screen\n",
pointerreply->root_x, pointerreply->root_y);
- output = get_first_output();
}
-
- con_activate(con_descend_focused(output_get_content(output->con)));
- free(pointerreply);
}
+ if (!output) {
+ output = get_first_output();
+ }
+ con_activate(con_descend_focused(output_get_content(output->con)));
+ free(pointerreply);
tree_render();
diff --git a/src/manage.c b/src/manage.c
index 80faa167..8b56fd6e 100644
--- a/src/manage.c
+++ b/src/manage.c
@@ -14,6 +14,34 @@
#include <yajl/yajl_gen.h>
/*
+ * Match frame and window depth. This is needed because X will refuse to reparent a
+ * window whose background is ParentRelative under a window with a different depth.
+ *
+ */
+static xcb_window_t _match_depth(i3Window *win, Con *con) {
+ xcb_window_t old_frame = XCB_NONE;
+ if (con->depth != win->depth) {
+ old_frame = con->frame.id;
+ con->depth = win->depth;
+ x_con_reframe(con);
+ }
+ return old_frame;
+}
+
+/*
+ * Remove all match criteria, the first swallowed window wins.
+ *
+ */
+static void _remove_matches(Con *con) {
+ while (!TAILQ_EMPTY(&(con->swallow_head))) {
+ Match *first = TAILQ_FIRST(&(con->swallow_head));
+ TAILQ_REMOVE(&(con->swallow_head), first, matches);
+ match_free(first);
+ free(first);
+ }
+}
+
+/*
* Go through all existing windows (if the window manager is restarted) and manage them
*
*/
@@ -174,13 +202,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
FREE(buttons);
/* update as much information as possible so far (some replies may be NULL) */
- window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true);
- window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL), true);
- window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL), true);
+ window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL));
+ window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL));
+ window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL));
window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
- window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL), true);
+ 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;
@@ -341,24 +369,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
DLOG("Uh?! Container without a placeholder, but with a window, has swallowed this to-be-managed window?!\n");
} else {
/* Remove remaining criteria, the first swallowed window wins. */
- while (!TAILQ_EMPTY(&(nc->swallow_head))) {
- Match *first = TAILQ_FIRST(&(nc->swallow_head));
- TAILQ_REMOVE(&(nc->swallow_head), first, matches);
- match_free(first);
- free(first);
- }
+ _remove_matches(nc);
}
}
xcb_window_t old_frame = XCB_NONE;
if (nc->window != cwindow && nc->window != NULL) {
window_free(nc->window);
- /* Match frame and window depth. This is needed because X will refuse to reparent a
- * window whose background is ParentRelative under a window with a different depth. */
- if (nc->depth != cwindow->depth) {
- old_frame = nc->frame.id;
- nc->depth = cwindow->depth;
- x_con_reframe(nc);
- }
+ old_frame = _match_depth(cwindow, nc);
}
nc->window = cwindow;
x_reinit(nc);
@@ -594,6 +611,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
}
render_con(croot);
+ cwindow->managed_since = time(NULL);
+
/* Send an event about window creation */
ipc_send_window_event("new", nc);
@@ -670,3 +689,57 @@ geom_out:
out:
free(attr);
}
+
+/*
+ * Remanages a window: performs a swallow check and runs assignments.
+ * Returns con for the window regardless if it updated.
+ *
+ */
+Con *remanage_window(Con *con) {
+ Match *match;
+ Con *nc = con_for_window(croot, con->window, &match);
+ if (nc == NULL || nc->window == NULL || nc->window == con->window) {
+ run_assignments(con->window);
+ return con;
+ }
+ /* Make sure the placeholder that wants to swallow this window didn't spawn
+ * after the window to follow current behavior: adding a placeholder won't
+ * swallow windows currently managed. */
+ if (nc->window->managed_since > con->window->managed_since) {
+ run_assignments(con->window);
+ return con;
+ }
+
+ if (!restore_kill_placeholder(nc->window->id)) {
+ DLOG("Uh?! Container without a placeholder, but with a window, has swallowed this managed window?!\n");
+ } else {
+ _remove_matches(nc);
+ }
+ window_free(nc->window);
+
+ xcb_window_t old_frame = _match_depth(con->window, nc);
+
+ x_reparent_child(nc, con);
+
+ bool moved_workpaces = (con_get_workspace(nc) != con_get_workspace(con));
+
+ con_merge_into(con, nc);
+
+ /* Destroy the old frame if we had to reframe the container. This needs to be done
+ * after rendering in order to prevent the background from flickering in its place. */
+ if (old_frame != XCB_NONE) {
+ xcb_destroy_window(conn, old_frame);
+ }
+
+ run_assignments(nc->window);
+
+ if (moved_workpaces) {
+ /* If the window is associated with a startup sequence, delete it so
+ * child windows won't be created on the old workspace. */
+ startup_sequence_delete_by_window(nc->window);
+
+ ewmh_update_wm_desktop();
+ }
+
+ return nc;
+}
diff --git a/src/move.c b/src/move.c
index e28a91c6..298e1a06 100644
--- a/src/move.c
+++ b/src/move.c
@@ -237,17 +237,16 @@ static void move_to_output_directed(Con *con, direction_t direction) {
/* force re-painting the indicators */
FREE(con->deco_render_params);
- tree_flatten(croot);
ipc_send_window_event("move", con);
+ tree_flatten(croot);
ewmh_update_wm_desktop();
}
/*
- * Moves the given container in the given direction (D_LEFT, D_RIGHT,
- * D_UP, D_DOWN).
+ * Moves the given container in the given direction
*
*/
-void tree_move(Con *con, int direction) {
+void tree_move(Con *con, direction_t direction) {
position_t position;
Con *target;
@@ -283,7 +282,7 @@ void tree_move(Con *con, int direction) {
if (!same_orientation) {
if (con_is_floating(con)) {
/* this is a floating con, we just disable floating */
- floating_disable(con, true);
+ floating_disable(con);
return;
}
if (con_inside_floating(con)) {
@@ -385,7 +384,7 @@ end:
/* force re-painting the indicators */
FREE(con->deco_render_params);
- tree_flatten(croot);
ipc_send_window_event("move", con);
+ tree_flatten(croot);
ewmh_update_wm_desktop();
}
diff --git a/src/randr.c b/src/randr.c
index fb127ab5..9a0bf5cc 100644
--- a/src/randr.c
+++ b/src/randr.c
@@ -70,11 +70,22 @@ Output *get_output_by_name(const char *name, const bool require_active) {
*
*/
Output *get_first_output(void) {
- Output *output;
+ Output *output, *result = NULL;
- TAILQ_FOREACH(output, &outputs, outputs)
- if (output->active)
- return output;
+ TAILQ_FOREACH(output, &outputs, outputs) {
+ if (output->active) {
+ if (output->primary) {
+ return output;
+ }
+ if (!result) {
+ result = output;
+ }
+ }
+ }
+
+ if (result) {
+ return result;
+ }
die("No usable outputs available.\n");
}
diff --git a/src/resize.c b/src/resize.c
index 5ddee5c1..97a0f946 100644
--- a/src/resize.c
+++ b/src/resize.c
@@ -21,12 +21,32 @@ struct callback_params {
Con *output;
xcb_window_t helpwin;
uint32_t *new_position;
+ bool *threshold_exceeded;
};
DRAGGING_CB(resize_callback) {
const struct callback_params *params = extra;
Con *output = params->output;
DLOG("new x = %d, y = %d\n", new_x, new_y);
+
+ if (!*params->threshold_exceeded) {
+ xcb_map_window(conn, params->helpwin);
+ /* Warp pointer in the same way as resize_graphical_handler() would do
+ * if threshold wasn't enabled, but also take into account travelled
+ * distance. */
+ if (params->orientation == HORIZ) {
+ xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
+ *params->new_position + new_x - event->root_x,
+ new_y);
+ } else {
+ xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
+ new_x,
+ *params->new_position + new_y - event->root_y);
+ }
+ *params->threshold_exceeded = true;
+ return;
+ }
+
if (params->orientation == HORIZ) {
/* Check if the new coordinates are within screen boundaries */
if (new_x > (output->rect.x + output->rect.width - 25) ||
@@ -148,7 +168,9 @@ bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt) {
return true;
}
-void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
+void resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
+ const xcb_button_press_event_t *event,
+ bool use_threshold) {
Con *output = con_get_output(first);
DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width);
@@ -179,14 +201,10 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
helprect.width = logical_px(2);
helprect.height = second->rect.height;
initial_position = second->rect.x;
- xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
- second->rect.x, event->root_y);
} else {
helprect.width = second->rect.width;
helprect.height = logical_px(2);
initial_position = second->rect.y;
- xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
- event->root_x, second->rect.y);
}
mask = XCB_CW_BACK_PIXEL;
@@ -196,7 +214,18 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
values[1] = 1;
xcb_window_t helpwin = create_window(conn, helprect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
- XCB_WINDOW_CLASS_INPUT_OUTPUT, (orientation == HORIZ ? XCURSOR_CURSOR_RESIZE_HORIZONTAL : XCURSOR_CURSOR_RESIZE_VERTICAL), true, mask, values);
+ XCB_WINDOW_CLASS_INPUT_OUTPUT, (orientation == HORIZ ? XCURSOR_CURSOR_RESIZE_HORIZONTAL : XCURSOR_CURSOR_RESIZE_VERTICAL), false, mask, values);
+
+ if (!use_threshold) {
+ xcb_map_window(conn, helpwin);
+ if (orientation == HORIZ) {
+ xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
+ second->rect.x, event->root_y);
+ } else {
+ xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
+ event->root_x, second->rect.y);
+ }
+ }
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
@@ -205,10 +234,12 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
/* `new_position' will be updated by the `resize_callback'. */
new_position = initial_position;
- const struct callback_params params = {orientation, output, helpwin, &new_position};
+ bool threshold_exceeded = !use_threshold;
+
+ const struct callback_params params = {orientation, output, helpwin, &new_position, &threshold_exceeded};
/* `drag_pointer' blocks until the drag is completed. */
- drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, &params);
+ drag_result_t drag_result = drag_pointer(NULL, event, grabwin, 0, use_threshold, resize_callback, &params);
xcb_destroy_window(conn, helpwin);
xcb_destroy_window(conn, grabwin);
diff --git a/src/scratchpad.c b/src/scratchpad.c
index b7fbcc92..261a8bfd 100644
--- a/src/scratchpad.c
+++ b/src/scratchpad.c
@@ -40,9 +40,7 @@ void scratchpad_move(Con *con) {
/* If the current con is in fullscreen mode, we need to disable that,
* as a scratchpad window should never be in fullscreen mode */
- if (focused && focused->type != CT_WORKSPACE && focused->fullscreen_mode != CF_NONE) {
- con_toggle_fullscreen(focused, CF_OUTPUT);
- }
+ con_disable_fullscreen(con);
/* 1: Ensure the window or any parent is floating. From now on, we deal
* with the CT_FLOATING_CON. We use automatic == false because the user
@@ -279,7 +277,7 @@ void scratchpad_fix_resolution(void) {
Rect new_rect = __i3_output->rect;
- if (memcmp(&old_rect, &new_rect, sizeof(Rect)) == 0) {
+ if (rect_equals(new_rect, old_rect)) {
DLOG("Scratchpad size unchanged.\n");
return;
}
diff --git a/src/sighandler.c b/src/sighandler.c
index e49c30ba..9f40e7d1 100644
--- a/src/sighandler.c
+++ b/src/sighandler.c
@@ -119,7 +119,7 @@ static int sighandler_backtrace(void) {
NULL};
execvp(args[0], args);
DLOG("Failed to exec GDB\n");
- exit(1);
+ exit(EXIT_FAILURE);
}
int status = 0;
diff --git a/src/startup.c b/src/startup.c
index c5b7ad5d..0646d7af 100644
--- a/src/startup.c
+++ b/src/startup.c
@@ -195,7 +195,7 @@ void start_application(const char *command, bool no_startup_id) {
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL);
/* not reached */
}
- _exit(0);
+ _exit(EXIT_SUCCESS);
}
wait(0);
@@ -365,3 +365,22 @@ char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *
return sequence->workspace;
}
+
+/*
+ * Deletes the startup sequence for a window if it exists.
+ *
+ */
+void startup_sequence_delete_by_window(i3Window *win) {
+ struct Startup_Sequence *sequence;
+ xcb_get_property_cookie_t cookie;
+ xcb_get_property_reply_t *startup_id_reply;
+
+ cookie = xcb_get_property(conn, false, win->id, A__NET_STARTUP_ID,
+ XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
+ startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
+
+ sequence = startup_sequence_get(win, startup_id_reply, true);
+ if (sequence != NULL) {
+ startup_sequence_delete(sequence);
+ }
+}
diff --git a/src/tree.c b/src/tree.c
index 4057d177..408afddd 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -229,7 +229,7 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
xcb_change_window_attributes(conn, con->window->id,
XCB_CW_EVENT_MASK, (uint32_t[]){XCB_NONE});
xcb_unmap_window(conn, con->window->id);
- cookie = xcb_reparent_window(conn, con->window->id, root, 0, 0);
+ cookie = xcb_reparent_window(conn, con->window->id, root, con->rect.x, con->rect.y);
/* Ignore X11 errors for the ReparentWindow request.
* X11 Errors are returned when the window was already destroyed */
@@ -462,170 +462,175 @@ void tree_render(void) {
DLOG("-- END RENDERING --\n");
}
+static Con *get_tree_next_workspace(Con *con, direction_t direction) {
+ if (con_get_fullscreen_con(con, CF_GLOBAL)) {
+ DLOG("Cannot change workspace while in global fullscreen mode.\n");
+ return NULL;
+ }
+
+ Output *current_output = get_output_containing(con->rect.x, con->rect.y);
+ if (!current_output) {
+ return NULL;
+ }
+ DLOG("Current output is %s\n", output_primary_name(current_output));
+
+ Output *next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
+ if (!next_output) {
+ return NULL;
+ }
+ DLOG("Next output is %s\n", output_primary_name(next_output));
+
+ /* Find visible workspace on next output */
+ Con *workspace = NULL;
+ GREP_FIRST(workspace, output_get_content(next_output->con), workspace_is_visible(child));
+ return workspace;
+}
+
/*
- * Recursive function to walk the tree until a con can be found to focus.
+ * Returns the next / previous container to focus in the given direction. Does
+ * not modify focus and ensures focus restrictions for fullscreen containers
+ * are respected.
*
*/
-static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) {
- /* When dealing with fullscreen containers, it's necessary to go up to the
- * workspace level, because 'focus $dir' will start at the con's real
- * position in the tree, and it may not be possible to get to the edge
- * normally due to fullscreen focusing restrictions. */
- if (con->fullscreen_mode == CF_OUTPUT && con->type != CT_WORKSPACE)
- con = con_get_workspace(con);
-
- /* Stop recursing at workspaces after attempting to switch to next
- * workspace if possible. */
+static Con *get_tree_next(Con *con, direction_t direction) {
+ const bool previous = position_from_direction(direction) == BEFORE;
+ const orientation_t orientation = orientation_from_direction(direction);
+
+ Con *first_wrap = NULL;
+
if (con->type == CT_WORKSPACE) {
- if (con_get_fullscreen_con(con, CF_GLOBAL)) {
- DLOG("Cannot change workspace while in global fullscreen mode.\n");
- return false;
+ /* Special case for FOCUS_WRAPPING_WORKSPACE: allow the focus to leave
+ * the workspace only when a workspace is selected. */
+ goto handle_workspace;
+ }
+
+ while (con->type != CT_WORKSPACE) {
+ if (con->fullscreen_mode == CF_OUTPUT) {
+ /* We've reached a fullscreen container. Directional focus should
+ * now operate on the workspace level. */
+ con = con_get_workspace(con);
+ break;
+ } else if (con->fullscreen_mode == CF_GLOBAL) {
+ /* Focus changes should happen only inside the children of a global
+ * fullscreen container. */
+ return first_wrap;
}
- Output *current_output = get_output_containing(con->rect.x, con->rect.y);
- Output *next_output;
- if (!current_output)
- return false;
- DLOG("Current output is %s\n", output_primary_name(current_output));
-
- /* Try to find next output */
- direction_t direction;
- if (way == 'n' && orientation == HORIZ)
- direction = D_RIGHT;
- else if (way == 'p' && orientation == HORIZ)
- direction = D_LEFT;
- else if (way == 'n' && orientation == VERT)
- direction = D_DOWN;
- else if (way == 'p' && orientation == VERT)
- direction = D_UP;
- else
- return false;
+ Con *const parent = con->parent;
+ if (con->type == CT_FLOATING_CON) {
+ if (orientation != HORIZ) {
+ /* up/down does not change floating containers */
+ return NULL;
+ }
- next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
- if (!next_output)
- return false;
- DLOG("Next output is %s\n", output_primary_name(next_output));
+ /* left/right focuses the previous/next floating container */
+ Con *next = previous ? TAILQ_PREV(con, floating_head, floating_windows)
+ : TAILQ_NEXT(con, floating_windows);
+ /* If there is no next/previous container, wrap */
+ if (!next) {
+ next = previous ? TAILQ_LAST(&(parent->floating_head), floating_head)
+ : TAILQ_FIRST(&(parent->floating_head));
+ }
+ /* Our parent does not list us in floating heads? */
+ assert(next);
- /* Find visible workspace on next output */
- Con *workspace = NULL;
- GREP_FIRST(workspace, output_get_content(next_output->con), workspace_is_visible(child));
+ return next;
+ }
- /* Show next workspace and focus appropriate container if possible. */
- if (!workspace)
- return false;
+ if (con_num_children(parent) > 1 && con_orientation(parent) == orientation) {
+ Con *const next = previous ? TAILQ_PREV(con, nodes_head, nodes)
+ : TAILQ_NEXT(con, nodes);
+ if (next && con_fullscreen_permits_focusing(next)) {
+ return next;
+ }
+
+ Con *const wrap = previous ? TAILQ_LAST(&(parent->nodes_head), nodes_head)
+ : TAILQ_FIRST(&(parent->nodes_head));
+ switch (config.focus_wrapping) {
+ case FOCUS_WRAPPING_OFF:
+ break;
+ case FOCUS_WRAPPING_WORKSPACE:
+ case FOCUS_WRAPPING_ON:
+ if (!first_wrap && con_fullscreen_permits_focusing(wrap)) {
+ first_wrap = wrap;
+ }
+ break;
+ case FOCUS_WRAPPING_FORCE:
+ /* 'force' should always return to ensure focus doesn't
+ * leave the parent. */
+ if (next) {
+ return NULL; /* blocked by fullscreen */
+ }
+ return con_fullscreen_permits_focusing(wrap) ? wrap : NULL;
+ }
+ }
+ con = parent;
+ }
+
+ assert(con->type == CT_WORKSPACE);
+ if (config.focus_wrapping == FOCUS_WRAPPING_WORKSPACE) {
+ return first_wrap;
+ }
+
+handle_workspace:;
+ Con *workspace = get_tree_next_workspace(con, direction);
+ return workspace ? workspace : first_wrap;
+}
+
+/*
+ * Changes focus in the given direction
+ *
+ */
+void tree_next(Con *con, direction_t direction) {
+ Con *next = get_tree_next(con, direction);
+ if (!next) {
+ return;
+ }
+ if (next->type == CT_WORKSPACE) {
+ /* Show next workspace and focus appropriate container if possible. */
/* Use descend_focused first to give higher priority to floating or
* tiling fullscreen containers. */
- Con *focus = con_descend_focused(workspace);
+ Con *focus = con_descend_focused(next);
if (focus->fullscreen_mode == CF_NONE) {
- Con *focus_tiling = con_descend_tiling_focused(workspace);
+ Con *focus_tiling = con_descend_tiling_focused(next);
/* If descend_tiling returned a workspace then focus is either a
* floating container or the same workspace. */
- if (focus_tiling != workspace) {
+ if (focus_tiling != next) {
focus = focus_tiling;
}
}
- workspace_show(workspace);
+ workspace_show(next);
con_activate(focus);
x_set_warp_to(&(focus->rect));
- return true;
- }
-
- Con *parent = con->parent;
-
- if (con->type == CT_FLOATING_CON) {
- if (orientation != HORIZ)
- return false;
-
- /* left/right focuses the previous/next floating container */
- Con *next;
- if (way == 'n')
- next = TAILQ_NEXT(con, floating_windows);
- else
- next = TAILQ_PREV(con, floating_head, floating_windows);
-
- /* If there is no next/previous container, wrap */
- if (!next) {
- if (way == 'n')
- next = TAILQ_FIRST(&(parent->floating_head));
- else
- next = TAILQ_LAST(&(parent->floating_head), floating_head);
- }
-
- /* Still no next/previous container? bail out */
- if (!next)
- return false;
-
- /* Raise the floating window on top of other windows preserving
- * relative stack order */
+ return;
+ } else if (next->type == CT_FLOATING_CON) {
+ /* Raise the floating window on top of other windows preserving relative
+ * stack order */
+ Con *parent = next->parent;
while (TAILQ_LAST(&(parent->floating_head), floating_head) != next) {
Con *last = TAILQ_LAST(&(parent->floating_head), floating_head);
TAILQ_REMOVE(&(parent->floating_head), last, floating_windows);
TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows);
}
-
- con_activate(con_descend_focused(next));
- return true;
}
- /* If the orientation does not match or there is no other con to focus, we
- * need to go higher in the hierarchy */
- if (con_orientation(parent) != orientation ||
- con_num_children(parent) == 1)
- return _tree_next(parent, way, orientation, wrap);
-
- Con *current = TAILQ_FIRST(&(parent->focus_head));
- /* TODO: when can the following happen (except for floating windows, which
- * are handled above)? */
- if (TAILQ_EMPTY(&(parent->nodes_head))) {
- DLOG("nothing to focus\n");
- return false;
- }
-
- Con *next;
- if (way == 'n')
- next = TAILQ_NEXT(current, nodes);
- else
- next = TAILQ_PREV(current, nodes_head, nodes);
-
- if (!next) {
- if (config.focus_wrapping != FOCUS_WRAPPING_FORCE) {
- /* If there is no next/previous container, we check if we can focus one
- * when going higher (without wrapping, though). If so, we are done, if
- * not, we wrap */
- if (_tree_next(parent, way, orientation, false))
- return true;
-
- if (!wrap)
- return false;
- }
-
- if (way == 'n')
- next = TAILQ_FIRST(&(parent->nodes_head));
- else
- next = TAILQ_LAST(&(parent->nodes_head), nodes_head);
- }
-
- /* Don't violate fullscreen focus restrictions. */
- if (!con_fullscreen_permits_focusing(next))
- return false;
-
- /* 3: focus choice comes in here. at the moment we will go down
- * until we find a window */
- /* TODO: check for window, atm we only go down as far as possible */
+ workspace_show(con_get_workspace(next));
con_activate(con_descend_focused(next));
- return true;
}
/*
- * Changes focus in the given way (next/previous) and given orientation
- * (horizontal/vertical).
+ * Get the previous / next sibling
*
*/
-void tree_next(char way, orientation_t orientation) {
- _tree_next(focused, way, orientation,
- config.focus_wrapping != FOCUS_WRAPPING_OFF);
+Con *get_tree_next_sibling(Con *con, position_t direction) {
+ Con *to_focus = (direction == BEFORE ? TAILQ_PREV(con, nodes_head, nodes)
+ : TAILQ_NEXT(con, nodes));
+ if (to_focus && con_fullscreen_permits_focusing(to_focus)) {
+ return to_focus;
+ }
+ return NULL;
}
/*
diff --git a/src/util.c b/src/util.c
index 9fe3fa44..e9df9575 100644
--- a/src/util.c
+++ b/src/util.c
@@ -53,6 +53,10 @@ Rect rect_sub(Rect a, Rect b) {
a.height - b.height};
}
+bool rect_equals(Rect a, Rect b) {
+ return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height;
+}
+
/*
* Returns true if the name consists of only digits.
*
@@ -159,7 +163,7 @@ void exec_i3_utility(char *name, char *argv[]) {
char buffer[BUFSIZ];
if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
warn("could not read /proc/self/exe");
- _exit(1);
+ _exit(EXIT_FAILURE);
}
dir = dirname(buffer);
sasprintf(&migratepath, "%s/%s", dir, name);
@@ -308,42 +312,6 @@ void i3_restart(bool forget_layout) {
/* not reached */
}
-#if defined(__OpenBSD__) || defined(__APPLE__)
-
-/*
- * Taken from FreeBSD
- * Find the first occurrence of the byte string s in byte string l.
- *
- */
-void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) {
- register char *cur, *last;
- const char *cl = (const char *)l;
- const char *cs = (const char *)s;
-
- /* we need something to compare */
- if (l_len == 0 || s_len == 0)
- return NULL;
-
- /* "s" must be smaller or equal to "l" */
- if (l_len < s_len)
- return NULL;
-
- /* special case where s_len == 1 */
- if (s_len == 1)
- return memchr(l, (int)*cs, l_len);
-
- /* the last position where its possible to find "s" in "l" */
- last = (char *)cl + l_len - s_len;
-
- for (cur = (char *)cl; cur <= last; cur++)
- if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
- return cur;
-
- return NULL;
-}
-
-#endif
-
/*
* Escapes the given string if a pango font is currently used.
* If the string has to be escaped, the input string will be free'd.
@@ -514,3 +482,23 @@ ssize_t slurp(const char *path, char **buf) {
orientation_t orientation_from_direction(direction_t direction) {
return (direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT;
}
+
+/*
+ * Convert a direction to its corresponding position.
+ *
+ */
+position_t position_from_direction(direction_t direction) {
+ return (direction == D_LEFT || direction == D_UP) ? BEFORE : AFTER;
+}
+
+/*
+ * Convert orientation and position to the corresponding direction.
+ *
+ */
+direction_t direction_from_orientation_position(orientation_t orientation, position_t position) {
+ if (orientation == HORIZ) {
+ return position == BEFORE ? D_LEFT : D_RIGHT;
+ } else {
+ return position == BEFORE ? D_UP : D_DOWN;
+ }
+}
diff --git a/src/window.c b/src/window.c
index 8c3ae850..369aaa96 100644
--- a/src/window.c
+++ b/src/window.c
@@ -26,7 +26,7 @@ void window_free(i3Window *win) {
* given window.
*
*/
-void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
+void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
DLOG("WM_CLASS not set.\n");
FREE(prop);
@@ -52,9 +52,6 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool bef
win->class_instance, win->class_class);
free(prop);
- if (!before_mgmt) {
- run_assignments(win);
- }
}
/*
@@ -62,7 +59,7 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool bef
* window. Further updates using window_update_name_legacy will be ignored.
*
*/
-void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
+void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
DLOG("_NET_WM_NAME not specified, not changing\n");
FREE(prop);
@@ -89,9 +86,6 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
win->uses_net_wm_name = true;
free(prop);
- if (!before_mgmt) {
- run_assignments(win);
- }
}
/*
@@ -101,7 +95,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
* window_update_name()).
*
*/
-void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
+void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
DLOG("WM_NAME not set (_NET_WM_NAME is what you want anyways).\n");
FREE(prop);
@@ -134,9 +128,6 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo
win->name_x_changed = true;
free(prop);
- if (!before_mgmt) {
- run_assignments(win);
- }
}
/*
@@ -218,7 +209,7 @@ void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop)
* Updates the WM_WINDOW_ROLE
*
*/
-void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
+void window_update_role(i3Window *win, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
DLOG("WM_WINDOW_ROLE not set.\n");
FREE(prop);
@@ -233,9 +224,6 @@ void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool befo
LOG("WM_WINDOW_ROLE changed to \"%s\"\n", win->role);
free(prop);
- if (!before_mgmt) {
- run_assignments(win);
- }
}
/*
diff --git a/src/workspace.c b/src/workspace.c
index 59705798..4a1c4de8 100644
--- a/src/workspace.c
+++ b/src/workspace.c
@@ -197,18 +197,27 @@ void extract_workspace_names_from_bindings(void) {
while (*target == ' ' || *target == '\t')
target++;
/* We check if this is the workspace
- * next/prev/next_on_output/prev_on_output/back_and_forth/number command.
+ * next/prev/next_on_output/prev_on_output/back_and_forth command.
* Beware: The workspace names "next", "prev", "next_on_output",
- * "prev_on_output", "number", "back_and_forth" and "current" are OK,
+ * "prev_on_output", "back_and_forth" and "current" are OK,
* so we check before stripping the double quotes */
if (strncasecmp(target, "next", strlen("next")) == 0 ||
strncasecmp(target, "prev", strlen("prev")) == 0 ||
strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 ||
- strncasecmp(target, "number", strlen("number")) == 0 ||
strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 ||
strncasecmp(target, "current", strlen("current")) == 0)
continue;
+ if (strncasecmp(target, "--no-auto-back-and-forth", strlen("--no-auto-back-and-forth")) == 0) {
+ target += strlen("--no-auto-back-and-forth");
+ while (*target == ' ' || *target == '\t')
+ target++;
+ }
+ if (strncasecmp(target, "number", strlen("number")) == 0) {
+ target += strlen("number");
+ while (*target == ' ' || *target == '\t')
+ target++;
+ }
char *target_name = parse_string(&target, false);
if (target_name == NULL)
continue;
@@ -983,11 +992,15 @@ void workspace_move_to_output(Con *ws, Output *output) {
bool used_assignment = false;
struct Workspace_Assignment *assignment;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
+ bool attached;
+ int num;
if (!output_triggers_assignment(current_output, assignment)) {
continue;
}
- /* check if this workspace is already attached to the tree */
- if (get_existing_workspace_by_name(assignment->name) != NULL) {
+ /* check if this workspace's name or num is already attached to the tree */
+ num = ws_name_to_number(assignment->name);
+ attached = ((num == -1) ? get_existing_workspace_by_name(assignment->name) : get_existing_workspace_by_num(num)) != NULL;
+ if (attached) {
continue;
}
diff --git a/src/x.c b/src/x.c
index e6d875e5..8d89eafd 100644
--- a/src/x.c
+++ b/src/x.c
@@ -14,8 +14,6 @@
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#endif
-xcb_window_t ewmh_window;
-
/* Stores the X11 window ID of the currently focused window */
xcb_window_t focused_id = XCB_NONE;
@@ -251,8 +249,7 @@ void x_move_win(Con *src, Con *dest) {
state_dest->con = state_src->con;
state_src->con = NULL;
- Rect zero = {0, 0, 0, 0};
- if (memcmp(&(state_dest->window_rect), &(zero), sizeof(Rect)) == 0) {
+ if (rect_equals(state_dest->window_rect, (Rect){0, 0, 0, 0})) {
memcpy(&(state_dest->window_rect), &(state_src->window_rect), sizeof(Rect));
DLOG("COPYING RECT\n");
}
@@ -929,7 +926,7 @@ void x_push_node(Con *con) {
bool fake_notify = false;
/* Set new position if rect changed (and if height > 0) or if the pixmap
* needs to be recreated */
- if ((is_pixmap_needed && con->frame_buffer.id == XCB_NONE) || (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0 &&
+ if ((is_pixmap_needed && con->frame_buffer.id == XCB_NONE) || (!rect_equals(state->rect, rect) &&
rect.height > 0)) {
/* We first create the new pixmap, then render to it, set it as the
* background and only afterwards change the window size. This reduces
@@ -1008,7 +1005,7 @@ void x_push_node(Con *con) {
/* dito, but for child windows */
if (con->window != NULL &&
- memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) {
+ !rect_equals(state->window_rect, con->window_rect)) {
DLOG("setting window rect (%d, %d, %d, %d)\n",
con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height);
xcb_set_window_rect(conn, con->window->id, con->window_rect);
diff --git a/src/xinerama.c b/src/xinerama.c
index 4acfd3cb..efae10dc 100644
--- a/src/xinerama.c
+++ b/src/xinerama.c
@@ -84,7 +84,7 @@ static void query_screens(xcb_connection_t *conn) {
if (num_screens == 0) {
ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
- exit(0);
+ exit(EXIT_SUCCESS);
}
}
diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in
index 740e13e9..161ddf79 100644
--- a/testcases/lib/i3test.pm.in
+++ b/testcases/lib/i3test.pm.in
@@ -1230,7 +1230,7 @@ sub create_layout {
$r = $r . '{"swallows": [{';
$r = $r . '"class": "^' . "$char" . '$"';
- $r = $r . '}]},';
+ $r = $r . '}]}' . ($depth == 0 ? "\n" : ',');
} else {
die "Could not understand $char";
}
diff --git a/testcases/t/101-focus.t b/testcases/t/101-focus.t
index 9d42345d..1e87a544 100644
--- a/testcases/t/101-focus.t
+++ b/testcases/t/101-focus.t
@@ -35,9 +35,13 @@ my $bottom = open_window;
# end sleeping for half a second to make sure i3 reacted
#
sub focus_after {
- my $msg = shift;
+ my ($msg, $win_id) = @_;
- cmd $msg;
+ if (defined($win_id)) {
+ cmd "[id=$win_id] $msg";
+ } else {
+ cmd $msg;
+ }
return $x->input_focus;
}
@@ -50,6 +54,14 @@ is($focus, $mid->id, "Middle window focused");
$focus = focus_after('focus up');
is($focus, $top->id, "Top window focused");
+# Same using command criteria
+$focus = focus_after('focus up', $bottom->id);
+is($focus, $mid->id, "Middle window focused");
+
+cmd 'focus down';
+$focus = focus_after('focus up', $mid->id);
+is($focus, $top->id, "Top window focused");
+
#####################################################################
# Test focus wrapping
#####################################################################
diff --git a/testcases/t/116-nestedcons.t b/testcases/t/116-nestedcons.t
index 40ad6bb3..4f13b1e5 100644
--- a/testcases/t/116-nestedcons.t
+++ b/testcases/t/116-nestedcons.t
@@ -53,6 +53,7 @@ my $expected = {
name => 'root',
orientation => $ignore,
type => 'root',
+ window_type => undef,
id => $ignore,
rect => $ignore,
deco_rect => $ignore,
diff --git a/testcases/t/172-start-on-named-ws.t b/testcases/t/172-start-on-named-ws.t
index 778eb23b..d1af6c07 100644
--- a/testcases/t/172-start-on-named-ws.t
+++ b/testcases/t/172-start-on-named-ws.t
@@ -125,4 +125,22 @@ is_deeply(\@names, [ '3' ], 'i3 starts on workspace 3');
exit_gracefully($pid);
+##############################################################
+# 7: verify optional flags do not affect startup workspace
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bindsym Mod1+1 workspace --no-auto-back-and-forth number 3:three
+EOT
+
+$pid = launch_with_config($config);
+
+@names = @{get_workspace_names()};
+is_deeply(\@names, [ '3:three' ], 'i3 starts on named workspace 3:three');
+
+exit_gracefully($pid);
+
done_testing;
diff --git a/testcases/t/176-workspace-baf.t b/testcases/t/176-workspace-baf.t
index 133e00fc..16738429 100644
--- a/testcases/t/176-workspace-baf.t
+++ b/testcases/t/176-workspace-baf.t
@@ -171,6 +171,53 @@ cmd 'restart';
cmd 'workspace back_and_forth';
is(focused_ws, '5: foo', 'workspace 5 focused after restart');
+################################################################################
+# Check BAF switching to renamed workspace.
+# Issue: #3694
+################################################################################
+
+kill_all_windows;
+cmd 'workspace --no-auto-back-and-forth 1';
+open_window;
+cmd 'workspace --no-auto-back-and-forth 2';
+
+cmd 'rename workspace 1 to 3';
+cmd 'workspace back_and_forth';
+is(focused_ws, '3', 'workspace 3 focused after rename');
+
+################################################################################
+# Check BAF switching to renamed and then closed workspace.
+# Issue: #3694
+################################################################################
+
+kill_all_windows;
+cmd 'workspace --no-auto-back-and-forth 1';
+$first_win = open_window;
+cmd 'workspace --no-auto-back-and-forth 2';
+
+cmd 'rename workspace 1 to 3';
+cmd '[id="' . $first_win->id . '"] kill';
+
+cmd 'workspace back_and_forth';
+is(focused_ws, '3', 'workspace 3 focused after renaming and destroying');
+
+################################################################################
+# See if renaming current workspace doesn't affect BAF switching to another
+# renamed workspace.
+# Issue: #3694
+################################################################################
+
+kill_all_windows;
+cmd 'workspace --no-auto-back-and-forth 1';
+$first_win = open_window;
+cmd 'workspace --no-auto-back-and-forth 2';
+
+cmd 'rename workspace 1 to 3';
+cmd 'rename workspace 2 to 4';
+
+cmd 'workspace back_and_forth';
+is(focused_ws, '3', 'workspace 3 focused after renaming');
+
exit_gracefully($pid);
done_testing;
diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t
index f35b7120..1a59c6b7 100644
--- a/testcases/t/177-bar-config.t
+++ b/testcases/t/177-bar-config.t
@@ -63,6 +63,7 @@ my $bar_config = $i3->get_bar_config($bar_id)->recv;
is($bar_config->{status_command}, 'i3status --foo', 'status_command correct');
ok(!$bar_config->{verbose}, 'verbose off by default');
ok($bar_config->{workspace_buttons}, 'workspace buttons enabled per default');
+is($bar_config->{workspace_min_width}, 0, 'workspace_min_width ok');
ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled per default');
is($bar_config->{mode}, 'dock', 'dock mode by default');
is($bar_config->{position}, 'bottom', 'position bottom by default');
@@ -102,6 +103,7 @@ bar {
mode dock
font Terminus
workspace_buttons no
+ workspace_min_width 30
binding_mode_indicator no
verbose yes
socket_path /tmp/foobar
@@ -134,6 +136,7 @@ $bar_config = $i3->get_bar_config($bar_id)->recv;
is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
ok($bar_config->{verbose}, 'verbose on');
ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled');
+is($bar_config->{workspace_min_width}, 30, 'workspace_min_width ok');
ok(!$bar_config->{binding_mode_indicator}, 'mode indicator disabled');
is($bar_config->{mode}, 'dock', 'dock mode');
is($bar_config->{position}, 'top', 'position top');
diff --git a/testcases/t/185-scratchpad.t b/testcases/t/185-scratchpad.t
index fd3827f7..bffdf74a 100644
--- a/testcases/t/185-scratchpad.t
+++ b/testcases/t/185-scratchpad.t
@@ -532,5 +532,21 @@ is(scalar @{get_ws_content($ws)}, 0, 'no window in workspace');
cmd 'scratchpad show';
is($x->input_focus, $window->id, 'scratchpad window shown');
+################################################################################
+# 19: move position commands do not show scratchpad window
+# See issue #3832
+################################################################################
+
+kill_all_windows;
+
+fresh_workspace;
+$first = open_window;
+$second = open_window;
+
+cmd '[id=' . $first->id . '] move to scratchpad, move position 100 100';
+is ($x->input_focus, $second->id, 'moving scratchpad window does not show it');
+cmd '[id=' . $first->id . '] move position center';
+is ($x->input_focus, $second->id, 'centering scratchpad window does not show it');
+
done_testing;
diff --git a/testcases/t/195-net-active-window.t b/testcases/t/195-net-active-window.t
index f9f883cb..4ce12089 100644
--- a/testcases/t/195-net-active-window.t
+++ b/testcases/t/195-net-active-window.t
@@ -150,6 +150,22 @@ send_net_active_window($scratch->id, 'pager');
is($x->input_focus, $scratch->id, 'scratchpad window is shown');
################################################################################
+# Send a _NET_ACTIVE_WINDOW ClientMessage for a window behind a fullscreen
+# window
+################################################################################
+
+$ws1 = fresh_workspace;
+$win1 = open_window;
+$win2 = open_window;
+cmd 'fullscreen enable';
+is_num_fullscreen($ws1, 1, '1 fullscreen window in workspace');
+
+send_net_active_window($win1->id);
+
+is($x->input_focus, $win1->id, 'window behind fullscreen window is now focused');
+is_num_fullscreen($ws1, 0, 'no fullscreen windows in workspace');
+
+################################################################################
# Verify that the _NET_ACTIVE_WINDOW property is updated on the root window
# correctly.
################################################################################
diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t
index 65997bc7..a8d45325 100644
--- a/testcases/t/201-config-parser.t
+++ b/testcases/t/201-config-parser.t
@@ -733,7 +733,7 @@ EOT
$expected = <<'EOT';
cfg_bar_start()
cfg_bar_output(LVDS-1)
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'colors', '}'
+ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'workspace_min_width', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'colors', '}'
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: bar {
ERROR: CONFIG: Line 2: output LVDS-1
diff --git a/testcases/t/206-fullscreen-scratchpad.t b/testcases/t/206-fullscreen-scratchpad.t
index 95245099..cd7c4919 100644
--- a/testcases/t/206-fullscreen-scratchpad.t
+++ b/testcases/t/206-fullscreen-scratchpad.t
@@ -21,9 +21,10 @@ use i3test;
my $tmp = fresh_workspace;
-##########################################################################################
-# map two windows in one container, fullscreen one of them and then move it to scratchpad
-##########################################################################################
+###############################################################################
+# map two windows in one container, fullscreen one of them and then move it to
+# scratchpad
+###############################################################################
my $first_win = open_window;
my $second_win = open_window;
@@ -50,9 +51,10 @@ cmd 'floating toggle';
# see if no window is in fullscreen mode
is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after showing previously fullscreened scratchpad window');
-########################################################################################
-# move a window to scratchpad, focus parent container, make it fullscreen, focus a child
-########################################################################################
+###############################################################################
+# move a window to scratchpad, focus parent container, make it fullscreen,
+# focus a child
+###############################################################################
# make layout tabbed
cmd 'layout tabbed';
@@ -72,9 +74,9 @@ cmd 'focus child';
# see if the window really is in fullscreen mode
is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after enabling fullscreen on parent');
-##########################################################################
+###############################################################################
# show a scratchpad window; no window should be in fullscreen mode anymore
-##########################################################################
+###############################################################################
# show the scratchpad window
cmd 'scratchpad show';
@@ -82,4 +84,29 @@ cmd 'scratchpad show';
# see if no window is in fullscreen mode
is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after showing a scratchpad window while a parent container was in fullscreen mode');
+###############################################################################
+# Moving window to scratchpad with command criteria does not unfullscreen
+# currently focused container
+# See https://github.com/i3/i3/issues/2857#issuecomment-496264445
+###############################################################################
+
+kill_all_windows;
+$tmp = fresh_workspace;
+
+$first_win = open_window;
+$second_win = open_window;
+cmd 'fullscreen';
+cmd '[id=' . $first_win->id . '] move scratchpad';
+
+is_num_fullscreen($tmp, 1, 'second window still fullscreen');
+my $__i3_scratch = get_ws('__i3_scratch');
+my @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
+is(scalar @scratch_nodes, 1, 'one window in scratchpad');
+
+cmd '[id=' . $first_win->id . '] scratchpad show';
+is_num_fullscreen($tmp, 0, 'second window not fullscreen');
+$__i3_scratch = get_ws('__i3_scratch');
+@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
+is(scalar @scratch_nodes, 0, 'windows in scratchpad');
+
done_testing;
diff --git a/testcases/t/243-move-to-mark.t b/testcases/t/243-move-to-mark.t
index 5e806cd4..390da04d 100644
--- a/testcases/t/243-move-to-mark.t
+++ b/testcases/t/243-move-to-mark.t
@@ -24,6 +24,7 @@ use i3test;
my ($A, $B, $S, $M, $F, $source_ws, $target_ws, $ws);
my ($nodes, $focus);
+my $__i3_scratch;
my $cmd_result;
my $_NET_WM_STATE_REMOVE = 0;
@@ -360,6 +361,7 @@ does_i3_live;
###############################################################################
# Given 'S' and 'M' where 'M' is a workspace and 'S' is on a different
# workspace, then 'S' ends up as a tiling container on 'M'.
+# See issue: #2003
###############################################################################
fresh_workspace;
@@ -402,5 +404,28 @@ is($nodes->[0]->{window}, $M->{id}, 'M remains the first window');
is(@{get_ws($target_ws)->{floating_nodes}}, 1, 'target workspace has the floating container');
###############################################################################
+# Given 'S' and 'M', where 'S' is a container and 'M' is a container hidden in
+# the scratchpad, then move 'S' to the scratchpad
+###############################################################################
+
+$ws = fresh_workspace;
+$S = open_window;
+cmd 'mark S';
+$M = open_window;
+cmd 'mark target';
+cmd 'move container to scratchpad';
+
+cmd '[con_mark=S] move container to mark target';
+sync_with_i3;
+
+($nodes, $focus) = get_ws_content($ws);
+is(@{$nodes}, 0, 'there are no tiling windows on the workspace');
+is(@{get_ws($ws)->{floating_nodes}}, 0, 'there are no floating containers on the workspace');
+
+$__i3_scratch = get_ws('__i3_scratch');
+is(@{$__i3_scratch->{nodes}}, 0, 'there are no tiling windows on the scratchpad workspace');
+is(@{$__i3_scratch->{floating_nodes}}, 2, 'there are two floating containers in the scratchpad');
+
+###############################################################################
done_testing;
diff --git a/testcases/t/270-config-no-newline-end.t b/testcases/t/270-config-no-newline-end.t
index 1faa6051..24c017f2 100644
--- a/testcases/t/270-config-no-newline-end.t
+++ b/testcases/t/270-config-no-newline-end.t
@@ -19,10 +19,9 @@
use i3test i3_autostart => 0;
my $first_lines = <<'EOT';
-set $workspace1 workspace number 1
set $workspace0 workspace eggs
-bindsym Mod4+1 $workspace1
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
EOT
# Intentionally don't add a trailing newline for the last line since this is
diff --git a/testcases/t/294-focus-order.t b/testcases/t/294-focus-order.t
index 86adb819..ce790019 100644
--- a/testcases/t/294-focus-order.t
+++ b/testcases/t/294-focus-order.t
@@ -280,4 +280,33 @@ cmd '[id=' . $windows[0]->id . '] kill';
kill_and_confirm_focus($windows[2]->id, 'window 2 focused after tiling killed');
kill_and_confirm_focus($windows[3]->id, 'window 3 focused after tiling killed');
+######################################################################
+# cmp_tree tests
+######################################################################
+
+cmp_tree(
+ msg => 'Basic test',
+ layout_before => 'S[a b] V[c d T[e f g*]]',
+ layout_after => ' ',
+ cb => sub {
+ @windows = reverse @{$_[0]};
+ confirm_focus('focus order');
+ });
+
+cmp_tree(
+ msg => 'Focused container that is moved to mark keeps focus',
+ layout_before => 'S[a b] V[2 3 T[4 5* 6]]',
+ layout_after => 'S[a b*]',
+ cb => sub {
+ cmd '[class=' . $_[0][3]->id . '] mark 3';
+ cmd 'move to mark 3';
+
+ $windows[0] = $_[0][5];
+ $windows[1] = $_[0][6];
+ $windows[2] = $_[0][4];
+ $windows[3] = $_[0][3];
+ $windows[4] = $_[0][2];
+ confirm_focus('focus order');
+ });
+
done_testing;
diff --git a/testcases/t/306-move-to-parent.t b/testcases/t/306-move-to-parent.t
new file mode 100644
index 00000000..8610cff9
--- /dev/null
+++ b/testcases/t/306-move-to-parent.t
@@ -0,0 +1,37 @@
+#!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)
+#
+# Make sure the trick used to move the container to its parent works.
+# https://github.com/i3/i3/issues/1326#issuecomment-349082811
+use i3test;
+
+cmp_tree(
+ msg => 'Move to parent when the parent is a workspace',
+ layout_before => 'a H[b*] c',
+ layout_after => 'a b* c',
+ cb => sub {
+ cmd 'mark _a, focus parent, focus parent, mark _b, [con_mark=_a] move window to mark _b, [con_mark=_a] focus';
+ });
+
+cmp_tree(
+ msg => 'Move to parent when the parent is a split',
+ layout_before => 'V[a H[b*] c]',
+ layout_after => 'V[a b* c]',
+ cb => sub {
+ cmd 'mark _a, focus parent, focus parent, mark _b, [con_mark=_a] move window to mark _b, [con_mark=_a] focus';
+ });
+
+done_testing;
diff --git a/testcases/t/307-focus-next-prev.t b/testcases/t/307-focus-next-prev.t
new file mode 100644
index 00000000..c7f06589
--- /dev/null
+++ b/testcases/t/307-focus-next-prev.t
@@ -0,0 +1,72 @@
+#!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 focus next|prev
+# Ticket: #2587
+use i3test;
+
+cmp_tree(
+ msg => "cmd 'prev' selects leaf 1/2",
+ layout_before => 'S[a b] V[c d* T[e f g]]',
+ layout_after => 'S[a b] V[c* d T[e f g]]',
+ cb => sub {
+ cmd 'focus prev';
+ });
+
+cmp_tree(
+ msg => "cmd 'prev' selects leaf 2/2",
+ layout_before => 'S[a b] V[c* d T[e f g]]',
+ layout_after => 'S[a b*] V[c d T[e f g]]',
+ cb => sub {
+ # c* -> V -> b*
+ cmd 'focus parent, focus prev';
+ });
+
+cmp_tree(
+ msg => "cmd 'prev sibling' selects leaf again",
+ layout_before => 'S[a b] V[c d* T[e f g]]',
+ layout_after => 'S[a b] V[c* d T[e f g]]',
+ cb => sub {
+ cmd 'focus prev sibling';
+ });
+
+cmp_tree(
+ msg => "cmd 'next' selects leaf",
+ # Notice that g is the last to open before focus moves to d*
+ layout_before => 'S[a b] V[c d* T[e f g]]',
+ layout_after => 'S[a b] V[c d T[e f g*]]',
+ cb => sub {
+ cmd 'focus next';
+ });
+
+cmp_tree(
+ msg => "cmd 'next sibling' selects parent 1/2",
+ layout_before => 'S[a b] V[c d* T[e f g]]',
+ layout_after => 'S[a b] V[c d T*[e f g]]',
+ cb => sub {
+ cmd 'focus next sibling';
+ });
+
+cmp_tree(
+ msg => "cmd 'next sibling' selects parent 2/2",
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a b] V*[c d T[e f g]]',
+ cb => sub {
+ # b* -> S* -> V*
+ cmd 'focus parent, focus next sibling';
+ });
+
+done_testing;
diff --git a/testcases/t/308-focus_wrapping.t b/testcases/t/308-focus_wrapping.t
new file mode 100644
index 00000000..9fa5858c
--- /dev/null
+++ b/testcases/t/308-focus_wrapping.t
@@ -0,0 +1,345 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Tests focus_wrapping yes|no|force|workspace with cmp_tree
+# Tickets: #2180 #2352
+use i3test i3_autostart => 0;
+
+my $pid = 0;
+
+sub focus_wrapping {
+ my ($setting) = @_;
+
+ print "--------------------------------------------------------------------------------\n";
+ print " focus_wrapping $setting\n";
+ print "--------------------------------------------------------------------------------\n";
+ exit_gracefully($pid) if $pid > 0;
+
+ my $config = <<"EOT";
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
+workspace left-top output fake-0
+workspace right-top output fake-1
+workspace right-bottom output fake-2
+workspace left-bottom output fake-3
+
+focus_wrapping $setting
+EOT
+ $pid = launch_with_config($config);
+}
+
+###############################################################################
+focus_wrapping('yes');
+###############################################################################
+
+cmp_tree(
+ msg => 'Normal focus up - should work for all options',
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a* b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus up';
+ });
+cmp_tree(
+ msg => 'Normal focus right - should work for all options',
+ layout_before => 'S[a b] V[c d T[e* f g]]',
+ layout_after => 'S[a b] V[c d T[e f* g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus right';
+ });
+cmp_tree(
+ msg => 'Focus leaves workspace vertically',
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus down';
+ is(focused_ws, 'left-bottom', 'Correct workspace focused');
+ });
+cmp_tree(
+ msg => 'Focus wraps vertically',
+ layout_before => 'S[a* b] V[c d T[e f g]]',
+ layout_after => 'S[a b*] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus up';
+ });
+cmp_tree(
+ msg => 'Focus wraps horizontally',
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a b] V[c d T[e f g*]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus left';
+ });
+cmp_tree(
+ msg => 'Directional focus in the orientation of the parent does not wrap',
+ layout_before => 'S[a b] V[c d T[e* f g]]',
+ layout_after => 'S[a b*] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus left';
+ });
+cmp_tree(
+ msg => 'Focus leaves workspace horizontally',
+ layout_before => 'S[a b] V[c d* T[e f g*]]',
+ layout_after => 'S[a b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus right';
+ is(focused_ws, 'right-top', 'Correct workspace focused');
+ });
+
+###############################################################################
+focus_wrapping('no');
+# See issue #2352
+###############################################################################
+
+cmp_tree(
+ msg => 'Normal focus up - should work for all options',
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a* b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus up';
+ });
+cmp_tree(
+ msg => 'Normal focus right - should work for all options',
+ layout_before => 'S[a b] V[c d T[e* f g]]',
+ layout_after => 'S[a b] V[c d T[e f* g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus right';
+ });
+cmp_tree(
+ msg => 'Focus leaves workspace vertically',
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus down';
+ is(focused_ws, 'left-bottom', 'Correct workspace focused');
+ });
+cmp_tree(
+ msg => 'Focus does not wrap vertically',
+ layout_before => 'S[a* b] V[c d T[e f g]]',
+ layout_after => 'S[a* b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus up';
+ });
+cmp_tree(
+ msg => 'Focus does not wrap horizontally',
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a b*] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus left';
+ });
+cmp_tree(
+ msg => 'Directional focus in the orientation of the parent does not wrap',
+ layout_before => 'S[a b] V[c d T[e* f g]]',
+ layout_after => 'S[a b*] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus left';
+ });
+cmp_tree(
+ msg => 'Focus leaves workspace horizontally',
+ layout_before => 'S[a b] V[c d* T[e f g]]',
+ layout_after => 'S[a b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus right';
+ is(focused_ws, 'right-top', 'Correct workspace focused');
+ });
+
+###############################################################################
+focus_wrapping('force');
+###############################################################################
+
+cmp_tree(
+ msg => 'Normal focus up - should work for all options',
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a* b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus up';
+ });
+cmp_tree(
+ msg => 'Normal focus right - should work for all options',
+ layout_before => 'S[a b] V[c d T[e* f g]]',
+ layout_after => 'S[a b] V[c d T[e f* g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus right';
+ });
+cmp_tree(
+ msg => 'Focus does not leave workspace vertically',
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a* b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus down';
+ is(focused_ws, 'left-top', 'Correct workspace focused');
+ });
+cmp_tree(
+ msg => 'Focus wraps vertically',
+ layout_before => 'S[a* b] V[c d T[e f g]]',
+ layout_after => 'S[a b*] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus up';
+ });
+cmp_tree(
+ msg => 'Focus wraps horizontally (focus direction different than parent\'s orientation)',
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a b] V[c d T[e f g*]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus left';
+ });
+cmp_tree(
+ msg => 'Directional focus in the orientation of the parent wraps',
+ layout_before => 'S[a b] V[c d T[e* f g]]',
+ layout_after => 'S[a b] V[c d T[e f g*]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus left';
+ });
+cmp_tree( # 'focus_wrapping force' exclusive test
+ msg => 'But leaves when selecting parent',
+ layout_before => 'S[a b] V[c d T[e* f g]]',
+ layout_after => 'S[a b*] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus parent, focus right';
+ });
+cmp_tree(
+ msg => 'Focus does not leave workspace horizontally',
+ layout_before => 'S[a b] V[c d* T[e f g]]',
+ layout_after => 'S[a b*] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus right';
+ is(focused_ws, 'left-top', 'Correct workspace focused');
+ });
+cmp_tree( # 'focus_wrapping force|workspace' exclusive test
+ msg => 'But leaves when selecting parent x2',
+ layout_before => 'S[a b] V[c d* T[e f g]]',
+ layout_after => 'S[a b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus parent, focus parent, focus right';
+ is(focused_ws, 'right-top', 'Correct workspace focused');
+ });
+
+###############################################################################
+focus_wrapping('workspace');
+# See issue #2180
+###############################################################################
+
+cmp_tree(
+ msg => 'Normal focus up - should work for all options',
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a* b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus up';
+ });
+cmp_tree(
+ msg => 'Normal focus right - should work for all options',
+ layout_before => 'S[a b] V[c d T[e* f g]]',
+ layout_after => 'S[a b] V[c d T[e f* g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus right';
+ });
+cmp_tree(
+ msg => 'Focus does not leave workspace vertically',
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a* b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus down';
+ is(focused_ws, 'left-top', 'Correct workspace focused');
+ });
+cmp_tree(
+ msg => 'Focus wraps vertically',
+ layout_before => 'S[a* b] V[c d T[e f g]]',
+ layout_after => 'S[a b*] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus up';
+ });
+cmp_tree(
+ msg => 'Focus wraps horizontally',
+ layout_before => 'S[a b*] V[c d T[e f g]]',
+ layout_after => 'S[a b] V[c d T[e f g*]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus left';
+ });
+cmp_tree(
+ msg => 'Directional focus in the orientation of the parent does not wrap',
+ layout_before => 'S[a b] V[c d T[e* f g]]',
+ layout_after => 'S[a b*] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus left';
+ });
+cmp_tree(
+ msg => 'Focus does not leave workspace horizontally',
+ layout_before => 'S[a b] V[c d* T[e f g]]',
+ layout_after => 'S[a b*] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus right';
+ is(focused_ws, 'left-top', 'Correct workspace focused');
+ });
+cmp_tree( # 'focus_wrapping force|workspace' exclusive test
+ msg => 'But leaves when selecting parent x2',
+ layout_before => 'S[a b] V[c d* T[e f g]]',
+ layout_after => 'S[a b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ cmd 'focus parent, focus parent, focus right';
+ is(focused_ws, 'right-top', 'Correct workspace focused');
+ });
+
+cmp_tree( # 'focus_wrapping workspace' exclusive test
+ msg => 'x',
+ layout_before => 'S[a* b] V[c d T[e f g]]',
+ layout_after => 'S[a b] V[c d T[e f g]]',
+ ws => 'left-top',
+ cb => sub {
+ subtest 'random tests' => sub {
+ my @directions = qw(left right top down);
+ for my $i (1 .. 50) {
+ my $direction = $directions[rand @directions];
+ cmd "focus $direction";
+
+ return unless is(focused_ws, 'left-top', "'focus $direction' did not change workspace");
+ }
+ };
+ });
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/309-crash-move-parent.t b/testcases/t/309-crash-move-parent.t
new file mode 100644
index 00000000..eb58251b
--- /dev/null
+++ b/testcases/t/309-crash-move-parent.t
@@ -0,0 +1,43 @@
+#!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 moving a container that is to be flattened does not crash i3
+# Ticket: #3831
+# Bug still in: 4.17-199-ga638e0408
+use i3test;
+
+cmp_tree(
+ msg => 'Moving a redundant container that is to be flattened does not crash i3',
+ layout_before => 'a H[V[b* c]]', # Not very attached to this result but
+ layout_after => 'H[a] b* c', # mainly checking if the crash happens.
+ cb => sub {
+ cmd 'focus parent, focus parent, move down';
+ does_i3_live;
+ is(get_ws(focused_ws)->{layout}, 'splitv', 'Workspace changed to splitv');
+ });
+
+cmp_tree(
+ msg => "Same but create the redundant container with a 'split h' command",
+ layout_before => 'a V[b* c]',
+ layout_after => 'H[a] b* c',
+ cb => sub {
+ cmd 'focus parent, split h, focus parent, move down';
+ does_i3_live;
+ is(get_ws(focused_ws)->{layout}, 'splitv', 'Workspace changed to splitv');
+ });
+
+
+done_testing;
diff --git a/testcases/t/542-layout-restore-remanage.t b/testcases/t/542-layout-restore-remanage.t
new file mode 100644
index 00000000..26b50835
--- /dev/null
+++ b/testcases/t/542-layout-restore-remanage.t
@@ -0,0 +1,86 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Tests that swallowing still works after a window gets managed and its property
+# updated.
+use i3test;
+use File::Temp qw(tempfile);
+use IO::Handle;
+use X11::XCB qw(PROP_MODE_REPLACE);
+
+sub change_window_title {
+ my ($window, $title, $length) = @_;
+ my $atomname = $x->atom(name => '_NET_WM_NAME');
+ my $atomtype = $x->atom(name => 'UTF8_STRING');
+ $length ||= length($title) + 1;
+ $x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 8,
+ $length,
+ $title
+ );
+ sync_with_i3;
+}
+
+my $ws = fresh_workspace;
+
+my @content = @{get_ws_content($ws)};
+is(@content, 0, 'no nodes on the new workspace yet');
+
+my ($fh, $filename) = tempfile(UNLINK => 1);
+print $fh <<EOT;
+{
+ "layout": "splitv",
+ "nodes": [
+ {
+ "swallows": [
+ {
+ "title": "different_title"
+ }
+ ]
+ }
+ ]
+}
+EOT
+$fh->flush;
+cmd "append_layout $filename";
+
+@content = @{get_ws_content($ws)};
+is(@content, 1, 'one node on the workspace now');
+
+my $window = open_window(
+ name => 'original_title',
+ wm_class => 'a',
+);
+
+@content = @{get_ws_content($ws)};
+is(@content, 2, 'two nodes on the workspace now');
+
+change_window_title($window, "different_title");
+
+does_i3_live;
+
+@content = @{get_ws_content($ws)};
+my @nodes = @{$content[0]->{nodes}};
+is(@content, 1, 'only one node on the workspace now');
+is($nodes[0]->{name}, 'different_title', 'test window got swallowed');
+
+close($fh);
+
+done_testing;
diff --git a/travis/check-spelling.pl b/travis/check-spelling.pl
index 6d070136..2a112c50 100755
--- a/travis/check-spelling.pl
+++ b/travis/check-spelling.pl
@@ -12,7 +12,7 @@ use v5.10;
use autodie;
use lib 'testcases/lib';
use i3test::Util qw(slurp);
-use Lintian::Check qw(check_spelling);
+use Lintian::Spelling qw(check_spelling);
# Lintian complains if we don’t set a vendor.
use Lintian::Data;