Merge branch 'next' into master
This commit is contained in:
commit
53718d354b
|
@ -31,7 +31,7 @@ install:
|
||||||
script:
|
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-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 ${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 ${BASENAME} ./travis/check-spelling.pl
|
||||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} ./travis/run-tests.sh
|
- 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
|
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/debian-build.sh deb/debian-amd64/DIST
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
4.17.1-non-git
|
4.18-non-git
|
||||||
|
|
|
@ -118,7 +118,7 @@ EXTRA_DIST = \
|
||||||
I3_VERSION \
|
I3_VERSION \
|
||||||
LICENSE \
|
LICENSE \
|
||||||
PACKAGE-MAINTAINER \
|
PACKAGE-MAINTAINER \
|
||||||
RELEASE-NOTES-4.17.1 \
|
RELEASE-NOTES-4.18 \
|
||||||
generate-command-parser.pl \
|
generate-command-parser.pl \
|
||||||
parser-specs/commands.spec \
|
parser-specs/commands.spec \
|
||||||
parser-specs/config.spec \
|
parser-specs/config.spec \
|
||||||
|
@ -503,6 +503,7 @@ i3_SOURCES = \
|
||||||
include/con.h \
|
include/con.h \
|
||||||
include/data.h \
|
include/data.h \
|
||||||
include/display_version.h \
|
include/display_version.h \
|
||||||
|
include/drag.h \
|
||||||
include/ewmh.h \
|
include/ewmh.h \
|
||||||
include/fake_outputs.h \
|
include/fake_outputs.h \
|
||||||
include/floating.h \
|
include/floating.h \
|
||||||
|
@ -548,6 +549,7 @@ i3_SOURCES = \
|
||||||
src/config_directives.c \
|
src/config_directives.c \
|
||||||
src/config_parser.c \
|
src/config_parser.c \
|
||||||
src/display_version.c \
|
src/display_version.c \
|
||||||
|
src/drag.c \
|
||||||
src/ewmh.c \
|
src/ewmh.c \
|
||||||
src/fake_outputs.c \
|
src/fake_outputs.c \
|
||||||
src/floating.c \
|
src/floating.c \
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
14
configure.ac
14
configure.ac
|
@ -2,7 +2,7 @@
|
||||||
# Run autoreconf -fi to generate a configure script from this file.
|
# Run autoreconf -fi to generate a configure script from this file.
|
||||||
|
|
||||||
AC_PREREQ([2.69])
|
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
|
# For AX_EXTEND_SRCDIR
|
||||||
AX_ENABLE_BUILDDIR
|
AX_ENABLE_BUILDDIR
|
||||||
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
|
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
|
AX_EXTEND_SRCDIR
|
||||||
|
|
||||||
AS_IF([test -d ${srcdir}/.git],
|
AS_IF([test -e ${srcdir}/.git],
|
||||||
[
|
[
|
||||||
VERSION="$(git -C ${srcdir} describe --tags --abbrev=0)"
|
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/::)\\\")"
|
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_FORK
|
||||||
AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK
|
AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK
|
||||||
AC_FUNC_STRNLEN
|
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.
|
# 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([shm_open], [rt], [], [], [-pthread])
|
||||||
|
|
||||||
AC_SEARCH_LIBS([iconv_open], [iconv], ,
|
AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <iconv.h>], [iconv_open(0, 0)])], ,
|
||||||
AC_SEARCH_LIBS([libiconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])]))
|
[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
|
AX_PTHREAD
|
||||||
|
|
||||||
|
|
|
@ -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
|
i3-wm (4.17-1) unstable; urgency=medium
|
||||||
|
|
||||||
* New upstream release.
|
* New upstream release.
|
||||||
|
|
|
@ -189,7 +189,7 @@ separator_block_width::
|
||||||
is 9 pixels), since the separator line is drawn in the middle.
|
is 9 pixels), since the separator line is drawn in the middle.
|
||||||
markup::
|
markup::
|
||||||
A string that indicates how the text of the block should be parsed. Set to
|
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
|
Set to +"none"+ to not use any markup (default). Pango markup only works
|
||||||
if you use a pango font.
|
if you use a pango font.
|
||||||
|
|
||||||
|
|
21
docs/ipc
21
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
|
The reply consists of a serialized list of workspaces. Each workspace has the
|
||||||
following properties:
|
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)::
|
num (integer)::
|
||||||
The logical number of the workspace. Corresponds to the command
|
The logical number of the workspace. Corresponds to the command
|
||||||
to switch to this workspace. For named workspaces, this will be -1.
|
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
|
containers. This ID corresponds to what xwininfo(1) and other
|
||||||
X11-related tools display (usually in hex).
|
X11-related tools display (usually in hex).
|
||||||
window_properties (map)::
|
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)::
|
urgent (bool)::
|
||||||
Whether this container (window, split container, floating container or
|
Whether this container (window, split container, floating container or
|
||||||
workspace) has the urgency hint set, directly or indirectly. All parent
|
workspace) has the urgency hint set, directly or indirectly. All parent
|
||||||
containers up until the workspace container will be marked urgent if they
|
containers up until the workspace container will be marked urgent if they
|
||||||
have at least one urgent child.
|
have at least one urgent child.
|
||||||
|
marks (array of string)::
|
||||||
|
List of marks assigned to container
|
||||||
focused (bool)::
|
focused (bool)::
|
||||||
Whether this container is currently focused.
|
Whether this container is currently focused.
|
||||||
focus (array of integer)::
|
focus (array of integer)::
|
||||||
|
@ -357,6 +368,14 @@ focus (array of integer)::
|
||||||
order. Traversing the tree by following the first entry in this array
|
order. Traversing the tree by following the first entry in this array
|
||||||
will result in eventually reaching the one node with +focused+ set to
|
will result in eventually reaching the one node with +focused+ set to
|
||||||
true.
|
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)::
|
nodes (array of node)::
|
||||||
The tiling (i.e. non-floating) child containers of this node.
|
The tiling (i.e. non-floating) child containers of this node.
|
||||||
floating_nodes (array of node)::
|
floating_nodes (array of node)::
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
|
@ -680,8 +680,8 @@ for_window [class="urxvt"] border pixel 1
|
||||||
for_window [title="x200: ~/work"] floating enable
|
for_window [title="x200: ~/work"] floating enable
|
||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
|
|
||||||
The valid criteria are the same as those for commands, see <<command_criteria>>. Only config
|
The valid criteria are the same as those for commands, see <<command_criteria>>. Only
|
||||||
directives with a command equivalent can be executed at runtime, see <<list_of_commands>>.
|
commands can be executed at runtime, not config directives, see <<list_of_commands>>.
|
||||||
|
|
||||||
[[no_focus]]
|
[[no_focus]]
|
||||||
=== Don't focus window upon opening
|
=== 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.
|
(to display a workspace bar), and to control i3.
|
||||||
|
|
||||||
The IPC socket is enabled by default and will be created in
|
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
|
the PID of i3 and XXXXXX is a string of random characters from the portable
|
||||||
filename character set (see mkdtemp(3)).
|
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+
|
parent+ to switch to different containers, you can instead set +focus_wrapping+
|
||||||
to the value +force+.
|
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*:
|
*Syntax*:
|
||||||
---------------------------
|
---------------------------
|
||||||
focus_wrapping yes|no|force
|
focus_wrapping yes|no|force|workspace
|
||||||
|
|
||||||
# Legacy syntax, equivalent to "focus_wrapping force"
|
# Legacy syntax, equivalent to "focus_wrapping force"
|
||||||
force_focus_wrapping yes
|
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
|
=== Strip workspace numbers/name
|
||||||
|
|
||||||
Specifies whether workspace numbers should be displayed within the workspace
|
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
|
actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
|
||||||
information on how to use them.
|
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]]
|
[[exec]]
|
||||||
=== Executing applications (exec)
|
=== Executing applications (exec)
|
||||||
|
|
||||||
|
@ -1954,6 +1986,7 @@ bindsym $mod+h split horizontal
|
||||||
bindsym $mod+t split toggle
|
bindsym $mod+t split toggle
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
|
[[manipulating_layout]]
|
||||||
=== Manipulating layout
|
=== Manipulating layout
|
||||||
|
|
||||||
Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+
|
Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+
|
||||||
|
@ -2024,6 +2057,12 @@ parent::
|
||||||
child::
|
child::
|
||||||
The opposite of +focus parent+, sets the focus to the last focused
|
The opposite of +focus parent+, sets the focus to the last focused
|
||||||
child container.
|
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::
|
floating::
|
||||||
Sets focus to the last focused floating container.
|
Sets focus to the last focused floating container.
|
||||||
tiling::
|
tiling::
|
||||||
|
@ -2039,6 +2078,7 @@ output::
|
||||||
<criteria> focus
|
<criteria> focus
|
||||||
focus left|right|down|up
|
focus left|right|down|up
|
||||||
focus parent|child|floating|tiling|mode_toggle
|
focus parent|child|floating|tiling|mode_toggle
|
||||||
|
focus next|prev [sibling]
|
||||||
focus output left|right|up|down|primary|<output>
|
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.
|
RandR output.
|
||||||
|
|
||||||
Workspace names are parsed as
|
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.
|
by i3bar.
|
||||||
|
|
||||||
[[back_and_forth]]
|
[[back_and_forth]]
|
||||||
|
@ -2492,7 +2532,7 @@ unmark irssi
|
||||||
By default, i3 will simply print the X11 window title. Using +title_format+,
|
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
|
this can be customized by setting the format to the desired output. This
|
||||||
directive supports
|
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:
|
and the following placeholders which will be replaced:
|
||||||
|
|
||||||
+%title+::
|
+%title+::
|
||||||
|
|
|
@ -68,6 +68,7 @@
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#include "xcb.h"
|
#include "xcb.h"
|
||||||
|
xcb_visualtype_t *visual_type = NULL;
|
||||||
#include "libi3.h"
|
#include "libi3.h"
|
||||||
|
|
||||||
#define TEXT_PADDING logical_px(4)
|
#define TEXT_PADDING logical_px(4)
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
* to i3.
|
* to i3.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#include "libi3.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
@ -27,6 +25,9 @@
|
||||||
#include <xcb/xcb_event.h>
|
#include <xcb/xcb_event.h>
|
||||||
#include <xcb/xcb_keysyms.h>
|
#include <xcb/xcb_keysyms.h>
|
||||||
|
|
||||||
|
xcb_visualtype_t *visual_type = NULL;
|
||||||
|
#include "libi3.h"
|
||||||
|
|
||||||
#include <X11/keysym.h>
|
#include <X11/keysym.h>
|
||||||
|
|
||||||
#include "keysym2ucs.h"
|
#include "keysym2ucs.h"
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
* when the user has an error in their configuration file.
|
* when the user has an error in their configuration file.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#include "libi3.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
@ -32,6 +30,9 @@
|
||||||
#include <xcb/randr.h>
|
#include <xcb/randr.h>
|
||||||
#include <xcb/xcb_cursor.h>
|
#include <xcb/xcb_cursor.h>
|
||||||
|
|
||||||
|
xcb_visualtype_t *visual_type = NULL;
|
||||||
|
#include "libi3.h"
|
||||||
|
|
||||||
#define SN_API_NOT_YET_FROZEN 1
|
#define SN_API_NOT_YET_FROZEN 1
|
||||||
#include <libsn/sn-launchee.h>
|
#include <libsn/sn-launchee.h>
|
||||||
|
|
||||||
|
@ -87,7 +88,7 @@ void verboselog(char *fmt, ...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
vfprintf(stdout, fmt, args);
|
vfprintf(stderr, fmt, args);
|
||||||
va_end(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;
|
xcb_randr_get_screen_resources_current_reply_t *res = NULL;
|
||||||
|
|
||||||
if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == 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;
|
goto free_resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
|
if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
|
||||||
|
LOG("Could not query screen resources.\n");
|
||||||
goto free_resources;
|
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_reply(conn,
|
||||||
xcb_randr_get_output_info(conn, primary->output, res->config_timestamp),
|
xcb_randr_get_output_info(conn, primary->output, res->config_timestamp),
|
||||||
NULL);
|
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;
|
goto free_resources;
|
||||||
|
}
|
||||||
|
|
||||||
xcb_randr_get_crtc_info_reply_t *crtc =
|
xcb_randr_get_crtc_info_reply_t *crtc =
|
||||||
xcb_randr_get_crtc_info_reply(conn,
|
xcb_randr_get_crtc_info_reply(conn,
|
||||||
xcb_randr_get_crtc_info(conn, output->crtc, res->config_timestamp),
|
xcb_randr_get_crtc_info(conn, output->crtc, res->config_timestamp),
|
||||||
NULL);
|
NULL);
|
||||||
if (crtc == NULL)
|
if (crtc == NULL) {
|
||||||
|
LOG("Could not get CRTC.\n");
|
||||||
goto free_resources;
|
goto free_resources;
|
||||||
|
}
|
||||||
|
|
||||||
DLOG("Found primary output on position x = %i / y = %i / w = %i / h = %i.\n",
|
LOG("Found primary output on position x = %i / y = %i / w = %i / h = %i.\n",
|
||||||
crtc->x, crtc->y, crtc->width, crtc->height);
|
crtc->x, crtc->y, crtc->width, crtc->height);
|
||||||
if (crtc->width == 0 || crtc->height == 0) {
|
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;
|
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) {
|
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
|
||||||
switch (o) {
|
switch (o) {
|
||||||
case 'v':
|
case 'v':
|
||||||
|
free(pattern);
|
||||||
printf("i3-nagbar " I3_VERSION "\n");
|
printf("i3-nagbar " I3_VERSION "\n");
|
||||||
return 0;
|
return 0;
|
||||||
case 'f':
|
case 'f':
|
||||||
FREE(pattern);
|
free(pattern);
|
||||||
pattern = sstrdup(optarg);
|
pattern = sstrdup(optarg);
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
|
@ -393,6 +400,7 @@ int main(int argc, char *argv[]) {
|
||||||
bar_type = (strcasecmp(optarg, "warning") == 0 ? TYPE_WARNING : TYPE_ERROR);
|
bar_type = (strcasecmp(optarg, "warning") == 0 ? TYPE_WARNING : TYPE_ERROR);
|
||||||
break;
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
|
free(pattern);
|
||||||
printf("i3-nagbar " I3_VERSION "\n");
|
printf("i3-nagbar " I3_VERSION "\n");
|
||||||
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
|
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
typedef struct rect_t rect;
|
typedef struct rect_t rect;
|
||||||
|
|
||||||
struct ev_loop *main_loop;
|
extern struct ev_loop *main_loop;
|
||||||
|
|
||||||
struct rect_t {
|
struct rect_t {
|
||||||
int x;
|
int x;
|
||||||
|
@ -82,7 +82,7 @@ struct status_block {
|
||||||
blocks;
|
blocks;
|
||||||
};
|
};
|
||||||
|
|
||||||
TAILQ_HEAD(statusline_head, status_block)
|
extern TAILQ_HEAD(statusline_head, status_block)
|
||||||
statusline_head;
|
statusline_head;
|
||||||
|
|
||||||
#include "child.h"
|
#include "child.h"
|
||||||
|
|
|
@ -52,6 +52,7 @@ typedef struct config_t {
|
||||||
struct xcb_color_strings_t colors;
|
struct xcb_color_strings_t colors;
|
||||||
bool disable_binding_mode_indicator;
|
bool disable_binding_mode_indicator;
|
||||||
bool disable_ws;
|
bool disable_ws;
|
||||||
|
int ws_min_width;
|
||||||
bool strip_ws_numbers;
|
bool strip_ws_numbers;
|
||||||
bool strip_ws_name;
|
bool strip_ws_name;
|
||||||
char *bar_id;
|
char *bar_id;
|
||||||
|
@ -73,7 +74,7 @@ typedef struct config_t {
|
||||||
S_SHOW = 1 } hidden_state;
|
S_SHOW = 1 } hidden_state;
|
||||||
} config_t;
|
} config_t;
|
||||||
|
|
||||||
config_t config;
|
extern config_t config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start parsing the received bar configuration JSON string
|
* Start parsing the received bar configuration JSON string
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
/* Name of current binding mode and its render width */
|
/* Name of current binding mode and its render width */
|
||||||
struct mode {
|
struct mode {
|
||||||
i3String *name;
|
i3String *name;
|
||||||
int width;
|
int name_width;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct mode mode;
|
typedef struct mode mode;
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
typedef struct i3_output i3_output;
|
typedef struct i3_output i3_output;
|
||||||
|
|
||||||
SLIST_HEAD(outputs_head, i3_output);
|
SLIST_HEAD(outputs_head, i3_output);
|
||||||
struct outputs_head* outputs;
|
extern struct outputs_head* outputs;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Start parsing the received JSON string
|
* Start parsing the received JSON string
|
||||||
|
|
|
@ -18,6 +18,9 @@ struct trayclient {
|
||||||
bool mapped; /* Whether this window is mapped */
|
bool mapped; /* Whether this window is mapped */
|
||||||
int xe_version; /* The XEMBED version supported by the client */
|
int xe_version; /* The XEMBED version supported by the client */
|
||||||
|
|
||||||
|
char *class_class;
|
||||||
|
char *class_instance;
|
||||||
|
|
||||||
TAILQ_ENTRY(trayclient)
|
TAILQ_ENTRY(trayclient)
|
||||||
tailq; /* Pointer for the TAILQ-Macro */
|
tailq; /* Pointer for the TAILQ-Macro */
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,6 +30,7 @@ void parse_workspaces_json(char *json);
|
||||||
void free_workspaces(void);
|
void free_workspaces(void);
|
||||||
|
|
||||||
struct i3_ws {
|
struct i3_ws {
|
||||||
|
uintptr_t id; /* Workspace ID - C pointer to a workspace container */
|
||||||
int num; /* The internal number of the ws */
|
int num; /* The internal number of the ws */
|
||||||
char *canonical_name; /* The true name of the ws according to the ipc */
|
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 */
|
i3String *name; /* The name of the ws that is displayed on the bar */
|
||||||
|
|
|
@ -53,7 +53,7 @@ struct xcb_color_strings_t {
|
||||||
typedef struct xcb_colors_t xcb_colors_t;
|
typedef struct xcb_colors_t xcb_colors_t;
|
||||||
|
|
||||||
/* Cached width of the custom separator if one was set */
|
/* 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
|
* Early initialization of the connection to X11: Everything which does not
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
|
|
||||||
|
config_t config;
|
||||||
static char *cur_key;
|
static char *cur_key;
|
||||||
static bool parsing_bindings;
|
static bool parsing_bindings;
|
||||||
static bool parsing_tray_outputs;
|
static bool parsing_tray_outputs;
|
||||||
|
@ -345,6 +346,12 @@ static int config_integer_cb(void *params_, long long val) {
|
||||||
return 1;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <glob.h>
|
#include <glob.h>
|
||||||
|
|
||||||
|
struct ev_loop *main_loop;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Having verboselog(), errorlog() and debuglog() is necessary when using libi3.
|
* 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 main(int argc, char **argv) {
|
||||||
int opt;
|
char *socket_path = NULL;
|
||||||
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";
|
|
||||||
|
|
||||||
/* Initialize the standard config to use 0 as default */
|
/* Initialize the standard config to use 0 as default */
|
||||||
memset(&config, '\0', sizeof(config_t));
|
memset(&config, '\0', sizeof(config_t));
|
||||||
|
@ -112,6 +108,8 @@ int main(int argc, char **argv) {
|
||||||
{"verbose", no_argument, 0, 'V'},
|
{"verbose", no_argument, 0, 'V'},
|
||||||
{NULL, 0, 0, 0}};
|
{NULL, 0, 0, 0}};
|
||||||
|
|
||||||
|
int opt;
|
||||||
|
int option_index = 0;
|
||||||
while ((opt = getopt_long(argc, argv, "b:s:thvV", long_opt, &option_index)) != -1) {
|
while ((opt = getopt_long(argc, argv, "b:s:thvV", long_opt, &option_index)) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 's':
|
case 's':
|
||||||
|
@ -144,10 +142,17 @@ int main(int argc, char **argv) {
|
||||||
exit(EXIT_FAILURE);
|
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();
|
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) {
|
if (socket_path == NULL) {
|
||||||
socket_path = atom_sock_path;
|
socket_path = atom_sock_path;
|
||||||
} else {
|
} else {
|
||||||
|
@ -155,8 +160,9 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (socket_path == NULL) {
|
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);
|
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();
|
init_dpi();
|
||||||
|
|
|
@ -81,7 +81,7 @@ static int mode_end_map_cb(void *params_) {
|
||||||
params->mode->name = i3string_from_utf8(params->name);
|
params->mode->name = i3string_from_utf8(params->name);
|
||||||
i3string_set_markup(params->mode->name, params->pango_markup);
|
i3string_set_markup(params->mode->name, params->pango_markup);
|
||||||
/* Save its rendered width */
|
/* 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));
|
DLOG("Got mode change: %s\n", i3string_as_utf8(params->mode->name));
|
||||||
FREE(params->cur_key);
|
FREE(params->cur_key);
|
||||||
|
|
|
@ -252,6 +252,7 @@ static yajl_callbacks outputs_callbacks = {
|
||||||
.yajl_end_map = outputs_end_map_cb,
|
.yajl_end_map = outputs_end_map_cb,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct outputs_head *outputs;
|
||||||
/*
|
/*
|
||||||
* Initiate the outputs list
|
* Initiate the outputs list
|
||||||
*
|
*
|
||||||
|
|
|
@ -61,6 +61,12 @@ static int workspaces_boolean_cb(void *params_, int val) {
|
||||||
static int workspaces_integer_cb(void *params_, long long val) {
|
static int workspaces_integer_cb(void *params_, long long val) {
|
||||||
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
|
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")) {
|
if (!strcmp(params->cur_key, "num")) {
|
||||||
params->workspaces_walk->num = (int)val;
|
params->workspaces_walk->num = (int)val;
|
||||||
FREE(params->cur_key);
|
FREE(params->cur_key);
|
||||||
|
|
323
i3bar/src/xcb.c
323
i3bar/src/xcb.c
|
@ -140,6 +140,9 @@ static const int tray_loff_px = 2;
|
||||||
/* Vertical offset between the bar and a separator */
|
/* Vertical offset between the bar and a separator */
|
||||||
static const int sep_voff_px = 4;
|
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) {
|
int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
|
||||||
xcb_generic_error_t *err;
|
xcb_generic_error_t *err;
|
||||||
if ((err = xcb_request_check(xcb_connection, cookie)) != NULL) {
|
if ((err = xcb_request_check(xcb_connection, cookie)) != NULL) {
|
||||||
|
@ -499,6 +502,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).
|
* 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-
|
* We determine, whether the click occurred on a workspace button or if the scroll-
|
||||||
|
@ -530,7 +542,7 @@ static void handle_button(xcb_button_press_event_t *event) {
|
||||||
i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
|
i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
|
||||||
|
|
||||||
TAILQ_FOREACH(ws_walk, walk->workspaces, tailq) {
|
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)
|
if (x >= workspace_width && x <= workspace_width + w)
|
||||||
clicked_ws = ws_walk;
|
clicked_ws = ws_walk;
|
||||||
if (ws_walk->visible)
|
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
|
* 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
|
* configuring their respective x coordinates. To be called when mapping or
|
||||||
|
@ -690,29 +728,108 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static void configure_trayclients(void) {
|
static void configure_trayclients(void) {
|
||||||
trayclient *trayclient;
|
|
||||||
i3_output *output;
|
i3_output *output;
|
||||||
SLIST_FOREACH(output, outputs, slist) {
|
SLIST_FOREACH(output, outputs, slist) {
|
||||||
if (!output->active)
|
if (!output->active) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int clients = 0;
|
int count = 0;
|
||||||
TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
|
trayclient *client;
|
||||||
if (!trayclient->mapped)
|
TAILQ_FOREACH(client, output->trayclients, tailq) {
|
||||||
continue;
|
if (client->mapped) {
|
||||||
clients++;
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DLOG("Configuring tray window %08x to x=%d\n",
|
int idx = 0;
|
||||||
trayclient->win, output->rect.w - (clients * (icon_size + logical_px(config.tray_padding))));
|
trayclient **trayclients = smalloc(count * sizeof(trayclient *));
|
||||||
uint32_t x = output->rect.w - (clients * (icon_size + logical_px(config.tray_padding)));
|
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,
|
xcb_configure_window(xcb_connection,
|
||||||
trayclient->win,
|
trayclients[idx - 1]->win,
|
||||||
XCB_CONFIG_WINDOW_X,
|
XCB_CONFIG_WINDOW_X,
|
||||||
&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);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handles ClientMessages (messages sent from another client directly to us).
|
* Handles ClientMessages (messages sent from another client directly to us).
|
||||||
*
|
*
|
||||||
|
@ -843,11 +960,12 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
||||||
* exits/crashes. */
|
* exits/crashes. */
|
||||||
xcb_change_save_set(xcb_connection, XCB_SET_MODE_INSERT, client);
|
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->win = client;
|
||||||
tc->xe_version = xe_version;
|
tc->xe_version = xe_version;
|
||||||
tc->mapped = false;
|
tc->mapped = false;
|
||||||
TAILQ_INSERT_TAIL(output_for_tray->trayclients, tc, tailq);
|
TAILQ_INSERT_TAIL(output_for_tray->trayclients, tc, tailq);
|
||||||
|
trayclient_update_class(tc);
|
||||||
|
|
||||||
if (map_it) {
|
if (map_it) {
|
||||||
DLOG("Mapping dock client\n");
|
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) {
|
static void handle_destroy_notify(xcb_destroy_notify_event_t *event) {
|
||||||
DLOG("DestroyNotify for window = %08x, event = %08x\n", event->window, event->event);
|
DLOG("DestroyNotify for window = %08x, event = %08x\n", event->window, event->event);
|
||||||
|
|
||||||
i3_output *walk;
|
i3_output *output;
|
||||||
SLIST_FOREACH(walk, outputs, slist) {
|
trayclient *client = trayclient_and_output_from_window(event->window, &output);
|
||||||
if (!walk->active)
|
if (!client) {
|
||||||
continue;
|
DLOG("WARNING: Could not find corresponding tray window.\n");
|
||||||
DLOG("checking output %s\n", walk->name);
|
return;
|
||||||
trayclient *trayclient;
|
|
||||||
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
|
|
||||||
if (trayclient->win != event->window) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DLOG("Removing tray client with window ID %08x\n", event->window);
|
DLOG("Removing tray client with window ID %08x\n", event->window);
|
||||||
TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
|
TAILQ_REMOVE(output->trayclients, client, tailq);
|
||||||
FREE(trayclient);
|
free(client->class_class);
|
||||||
|
free(client->class_instance);
|
||||||
|
FREE(client);
|
||||||
|
|
||||||
/* Trigger an update, we now have more space for the statusline */
|
/* Trigger an update, we now have more space for the statusline */
|
||||||
configure_trayclients();
|
configure_trayclients();
|
||||||
draw_bars(false);
|
draw_bars(false);
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -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) {
|
static void handle_map_notify(xcb_map_notify_event_t *event) {
|
||||||
DLOG("MapNotify for window = %08x, event = %08x\n", event->window, event->event);
|
DLOG("MapNotify for window = %08x, event = %08x\n", event->window, event->event);
|
||||||
|
|
||||||
i3_output *walk;
|
trayclient *client = trayclient_from_window(event->window);
|
||||||
SLIST_FOREACH(walk, outputs, slist) {
|
if (!client) {
|
||||||
if (!walk->active)
|
DLOG("WARNING: Could not find corresponding tray window.\n");
|
||||||
continue;
|
|
||||||
DLOG("checking output %s\n", walk->name);
|
|
||||||
trayclient *trayclient;
|
|
||||||
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
|
|
||||||
if (trayclient->win != event->window)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
DLOG("Tray client mapped (window ID %08x). Adjusting tray.\n", event->window);
|
|
||||||
trayclient->mapped = true;
|
|
||||||
|
|
||||||
/* Trigger an update, we now have more space for the statusline */
|
|
||||||
configure_trayclients();
|
|
||||||
draw_bars(false);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
DLOG("Tray client mapped (window ID %08x). Adjusting tray.\n", event->window);
|
||||||
|
client->mapped = true;
|
||||||
|
|
||||||
|
/* 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
|
* 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) {
|
static void handle_unmap_notify(xcb_unmap_notify_event_t *event) {
|
||||||
DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event);
|
DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event);
|
||||||
|
|
||||||
i3_output *walk;
|
trayclient *client = trayclient_from_window(event->window);
|
||||||
SLIST_FOREACH(walk, outputs, slist) {
|
if (!client) {
|
||||||
if (!walk->active)
|
DLOG("WARNING: Could not find corresponding tray window.\n");
|
||||||
continue;
|
return;
|
||||||
DLOG("checking output %s\n", walk->name);
|
}
|
||||||
trayclient *trayclient;
|
|
||||||
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
|
|
||||||
if (trayclient->win != event->window)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
DLOG("Tray client unmapped (window ID %08x). Adjusting tray.\n", event->window);
|
DLOG("Tray client unmapped (window ID %08x). Adjusting tray.\n", event->window);
|
||||||
trayclient->mapped = false;
|
client->mapped = false;
|
||||||
|
|
||||||
/* Trigger an update, we now have more space for the statusline */
|
/* Trigger an update, we now have more space for the statusline */
|
||||||
configure_trayclients();
|
configure_trayclients();
|
||||||
draw_bars(false);
|
draw_bars(false);
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handle PropertyNotify messages. Currently only the _XEMBED_INFO property is
|
* Handle PropertyNotify messages.
|
||||||
* handled, which tells us whether a dock client should be mapped or unmapped.
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static void handle_property_notify(xcb_property_notify_event_t *event) {
|
static void handle_property_notify(xcb_property_notify_event_t *event) {
|
||||||
DLOG("PropertyNotify\n");
|
DLOG("PropertyNotify\n");
|
||||||
if (event->atom == atoms[_XEMBED_INFO] &&
|
if (event->atom == atoms[_XEMBED_INFO] &&
|
||||||
event->state == XCB_PROPERTY_NEW_VALUE) {
|
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");
|
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) {
|
trayclient *client = trayclient_from_window(event->window);
|
||||||
if (walk->win != event->window)
|
if (!client) {
|
||||||
continue;
|
ELOG("PropertyNotify received for unknown window %08x\n", event->window);
|
||||||
trayclient = walk;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trayclient)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!trayclient) {
|
|
||||||
ELOG("PropertyNotify received for unknown window %08x\n",
|
|
||||||
event->window);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_get_property_cookie_t xembedc;
|
xcb_get_property_cookie_t xembedc;
|
||||||
xembedc = xcb_get_property_unchecked(xcb_connection,
|
xembedc = xcb_get_property_unchecked(xcb_connection,
|
||||||
0,
|
0,
|
||||||
trayclient->win,
|
client->win,
|
||||||
atoms[_XEMBED_INFO],
|
atoms[_XEMBED_INFO],
|
||||||
XCB_GET_PROPERTY_TYPE_ANY,
|
XCB_GET_PROPERTY_TYPE_ANY,
|
||||||
0,
|
0,
|
||||||
|
@ -1009,14 +1094,19 @@ static void handle_property_notify(xcb_property_notify_event_t *event) {
|
||||||
DLOG("xembed flags = %d\n", xembed[1]);
|
DLOG("xembed flags = %d\n", xembed[1]);
|
||||||
bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
|
bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
|
||||||
DLOG("map state now %d\n", map_it);
|
DLOG("map state now %d\n", map_it);
|
||||||
if (trayclient->mapped && !map_it) {
|
if (client->mapped && !map_it) {
|
||||||
/* need to unmap the window */
|
/* need to unmap the window */
|
||||||
xcb_unmap_window(xcb_connection, trayclient->win);
|
xcb_unmap_window(xcb_connection, client->win);
|
||||||
} else if (!trayclient->mapped && map_it) {
|
} else if (!client->mapped && map_it) {
|
||||||
/* need to map the window */
|
/* need to map the window */
|
||||||
xcb_map_window(xcb_connection, trayclient->win);
|
xcb_map_window(xcb_connection, client->win);
|
||||||
}
|
}
|
||||||
free(xembedr);
|
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,
|
||||||
0);
|
0);
|
||||||
|
|
||||||
|
free(trayclient->class_class);
|
||||||
|
free(trayclient->class_instance);
|
||||||
|
|
||||||
/* We remove the trayclient right here. We might receive an UnmapNotify
|
/* We remove the trayclient right here. We might receive an UnmapNotify
|
||||||
* event afterwards, but better safe than sorry. */
|
* event afterwards, but better safe than sorry. */
|
||||||
TAILQ_REMOVE(output->trayclients, trayclient, tailq);
|
TAILQ_REMOVE(output->trayclients, trayclient, tailq);
|
||||||
|
@ -1909,6 +2002,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
|
* Render the bars, with buttons and statusline
|
||||||
*
|
*
|
||||||
|
@ -1964,26 +2076,11 @@ void draw_bars(bool unhide) {
|
||||||
unhide = true;
|
unhide = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Draw the border of the button. */
|
int w = predict_button_width(ws_walk->name_width);
|
||||||
draw_util_rectangle(&(outputs_walk->buffer), border_color,
|
draw_button(&(outputs_walk->buffer), fg_color, bg_color, border_color,
|
||||||
workspace_width,
|
workspace_width, w, ws_walk->name_width, ws_walk->name);
|
||||||
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. */
|
workspace_width += w;
|
||||||
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;
|
|
||||||
if (TAILQ_NEXT(ws_walk, tailq) != NULL)
|
if (TAILQ_NEXT(ws_walk, tailq) != NULL)
|
||||||
workspace_width += logical_px(ws_spacing_px);
|
workspace_width += logical_px(ws_spacing_px);
|
||||||
}
|
}
|
||||||
|
@ -1992,28 +2089,12 @@ void draw_bars(bool unhide) {
|
||||||
if (binding.name && !config.disable_binding_mode_indicator) {
|
if (binding.name && !config.disable_binding_mode_indicator) {
|
||||||
workspace_width += logical_px(ws_spacing_px);
|
workspace_width += logical_px(ws_spacing_px);
|
||||||
|
|
||||||
color_t fg_color = colors.binding_mode_fg;
|
int w = predict_button_width(binding.name_width);
|
||||||
color_t bg_color = colors.binding_mode_bg;
|
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);
|
||||||
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);
|
|
||||||
|
|
||||||
unhide = true;
|
unhide = true;
|
||||||
workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + binding.width;
|
workspace_width += w;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TAILQ_EMPTY(&statusline_head)) {
|
if (!TAILQ_EMPTY(&statusline_head)) {
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
#include "click.h"
|
#include "click.h"
|
||||||
#include "key_press.h"
|
#include "key_press.h"
|
||||||
#include "floating.h"
|
#include "floating.h"
|
||||||
|
#include "drag.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "handlers.h"
|
#include "handlers.h"
|
||||||
#include "randr.h"
|
#include "randr.h"
|
||||||
|
|
|
@ -182,6 +182,12 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command);
|
||||||
*/
|
*/
|
||||||
void cmd_focus_direction(I3_CMD, const char *direction);
|
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'.
|
* Implementation of 'focus tiling|floating|mode_toggle'.
|
||||||
*
|
*
|
||||||
|
|
|
@ -45,6 +45,13 @@ void con_focus(Con *con);
|
||||||
*/
|
*/
|
||||||
void con_activate(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.
|
* Closes the given container.
|
||||||
*
|
*
|
||||||
|
@ -533,3 +540,11 @@ bool con_swap(Con *first, Con *second);
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
uint32_t con_rect_size_in_orientation(Con *con);
|
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);
|
||||||
|
|
|
@ -97,6 +97,7 @@ CFGFUN(bar_color_single, const char *colorclass, const char *color);
|
||||||
CFGFUN(bar_status_command, const char *command);
|
CFGFUN(bar_status_command, const char *command);
|
||||||
CFGFUN(bar_binding_mode_indicator, const char *value);
|
CFGFUN(bar_binding_mode_indicator, const char *value);
|
||||||
CFGFUN(bar_workspace_buttons, 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_numbers, const char *value);
|
||||||
CFGFUN(bar_strip_workspace_name, const char *value);
|
CFGFUN(bar_strip_workspace_name, const char *value);
|
||||||
CFGFUN(bar_start);
|
CFGFUN(bar_start);
|
||||||
|
|
|
@ -325,6 +325,9 @@ struct Barconfig {
|
||||||
* zero. */
|
* zero. */
|
||||||
bool hide_workspace_buttons;
|
bool hide_workspace_buttons;
|
||||||
|
|
||||||
|
/** The minimal width for workspace buttons. */
|
||||||
|
int workspace_min_width;
|
||||||
|
|
||||||
/** Strip workspace numbers? Configuration option is
|
/** Strip workspace numbers? Configuration option is
|
||||||
* 'strip_workspace_numbers yes'. */
|
* 'strip_workspace_numbers yes'. */
|
||||||
bool strip_workspace_numbers;
|
bool strip_workspace_numbers;
|
||||||
|
|
|
@ -59,6 +59,8 @@ typedef enum { D_LEFT,
|
||||||
typedef enum { NO_ORIENTATION = 0,
|
typedef enum { NO_ORIENTATION = 0,
|
||||||
HORIZ,
|
HORIZ,
|
||||||
VERT } orientation_t;
|
VERT } orientation_t;
|
||||||
|
typedef enum { BEFORE,
|
||||||
|
AFTER } position_t;
|
||||||
typedef enum { BS_NORMAL = 0,
|
typedef enum { BS_NORMAL = 0,
|
||||||
BS_NONE = 1,
|
BS_NONE = 1,
|
||||||
BS_PIXEL = 2 } border_style_t;
|
BS_PIXEL = 2 } border_style_t;
|
||||||
|
@ -139,13 +141,12 @@ typedef enum {
|
||||||
typedef enum {
|
typedef enum {
|
||||||
FOCUS_WRAPPING_OFF = 0,
|
FOCUS_WRAPPING_OFF = 0,
|
||||||
FOCUS_WRAPPING_ON = 1,
|
FOCUS_WRAPPING_ON = 1,
|
||||||
FOCUS_WRAPPING_FORCE = 2
|
FOCUS_WRAPPING_FORCE = 2,
|
||||||
|
FOCUS_WRAPPING_WORKSPACE = 3
|
||||||
} focus_wrapping_t;
|
} focus_wrapping_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a rectangle, for example the size of a window, the child window etc.
|
* 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
|
* 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
|
* 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 y;
|
||||||
uint32_t width;
|
uint32_t width;
|
||||||
uint32_t height;
|
uint32_t height;
|
||||||
} __attribute__((packed));
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the reserved pixels on each screen edge read from a
|
* Stores the reserved pixels on each screen edge read from a
|
||||||
|
@ -489,6 +490,10 @@ struct Window {
|
||||||
bool shaped;
|
bool shaped;
|
||||||
/** The window has a nonrectangular input shape. */
|
/** The window has a nonrectangular input shape. */
|
||||||
bool input_shaped;
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
|
@ -13,14 +13,6 @@
|
||||||
|
|
||||||
#include "tree.h"
|
#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? */
|
/** On which border was the dragging initiated? */
|
||||||
typedef enum { BORDER_LEFT = (1 << 0),
|
typedef enum { BORDER_LEFT = (1 << 0),
|
||||||
BORDER_RIGHT = (1 << 1),
|
BORDER_RIGHT = (1 << 1),
|
||||||
|
@ -40,7 +32,7 @@ void floating_enable(Con *con, bool automatic);
|
||||||
* to its old parent.
|
* 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
|
* 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
|
* 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
|
* Called when the user clicked on a floating window while holding the
|
||||||
|
@ -106,41 +98,6 @@ void floating_resize_window(Con *con, const bool proportional, const xcb_button_
|
||||||
*/
|
*/
|
||||||
void floating_check_size(Con *floating_con, bool prefer_height);
|
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
|
* Repositions the CT_FLOATING_CON to have the coordinates specified by
|
||||||
* newrect, but only if the coordinates are not out-of-bounds. Also reassigns
|
* newrect, but only if the coordinates are not out-of-bounds. Also reassigns
|
||||||
|
|
|
@ -341,8 +341,7 @@ gchar *g_utf8_make_valid(const gchar *str, gssize len);
|
||||||
*/
|
*/
|
||||||
uint32_t get_colorpixel(const char *hex) __attribute__((const));
|
uint32_t get_colorpixel(const char *hex) __attribute__((const));
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#ifndef HAVE_strndup
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Taken from FreeBSD
|
* Taken from FreeBSD
|
||||||
* Returns a pointer to a new string which is a duplicate of the
|
* 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);
|
char *strndup(const char *str, size_t n);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -528,7 +526,7 @@ char *resolve_tilde(const char *path);
|
||||||
*/
|
*/
|
||||||
char *get_config_path(const char *override_configpath, bool use_system_paths);
|
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)
|
* Emulates mkdir -p (creates any missing folders)
|
||||||
*
|
*
|
||||||
|
|
|
@ -37,3 +37,10 @@ void restore_geometry(void);
|
||||||
void manage_window(xcb_window_t window,
|
void manage_window(xcb_window_t window,
|
||||||
xcb_get_window_attributes_cookie_t cookie,
|
xcb_get_window_attributes_cookie_t cookie,
|
||||||
bool needs_to_be_mapped);
|
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);
|
||||||
|
|
|
@ -12,14 +12,10 @@
|
||||||
#include <config.h>
|
#include <config.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves the given container in the given direction (TOK_LEFT, TOK_RIGHT,
|
* Moves the given container in the given direction
|
||||||
* TOK_UP, TOK_DOWN from cmdparse.l)
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void tree_move(Con *con, int direction);
|
void tree_move(Con *con, direction_t direction);
|
||||||
|
|
||||||
typedef enum { BEFORE,
|
|
||||||
AFTER } position_t;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function detaches 'con' from its parent and inserts it either before or
|
* This function detaches 'con' from its parent and inserts it either before or
|
||||||
|
|
|
@ -13,7 +13,9 @@
|
||||||
|
|
||||||
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides);
|
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
|
* Resize the two given containers using the given amount of pixels or
|
||||||
|
|
|
@ -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);
|
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);
|
||||||
|
|
|
@ -59,11 +59,16 @@ bool level_down(void);
|
||||||
void tree_render(void);
|
void tree_render(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes focus in the given way (next/previous) and given orientation
|
* Changes focus in the given direction
|
||||||
* (horizontal/vertical).
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
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.
|
* Closes the given container including all children.
|
||||||
|
|
|
@ -64,6 +64,7 @@ int max(int a, int b);
|
||||||
bool rect_contains(Rect rect, uint32_t x, uint32_t y);
|
bool rect_contains(Rect rect, uint32_t x, uint32_t y);
|
||||||
Rect rect_add(Rect a, Rect b);
|
Rect rect_add(Rect a, Rect b);
|
||||||
Rect rect_sub(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.
|
* 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);
|
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.
|
* 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.
|
* 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);
|
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);
|
||||||
|
|
|
@ -22,14 +22,14 @@ void window_free(i3Window *win);
|
||||||
* given window.
|
* 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
|
* 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.
|
* 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
|
* 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()).
|
* 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).
|
* 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
|
* 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.
|
* Updates the _NET_WM_WINDOW_TYPE property.
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
XCB_EVENT_MASK_FOCUS_CHANGE | \
|
XCB_EVENT_MASK_FOCUS_CHANGE | \
|
||||||
XCB_EVENT_MASK_ENTER_WINDOW)
|
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"
|
#include "atoms.xmacro"
|
||||||
#undef xmacro
|
#undef xmacro
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
#include <cairo/cairo-xcb.h>
|
#include <cairo/cairo-xcb.h>
|
||||||
|
|
||||||
/* The default visual_type to use if none is specified when creating the surface. Must be defined globally. */
|
/* 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 */
|
/* Forward declarations */
|
||||||
static void draw_util_set_source_color(surface_t *surface, color_t color);
|
static void draw_util_set_source_color(surface_t *surface, color_t color);
|
||||||
|
|
|
@ -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 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,
|
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 */
|
/* X11 coordinates for fonts start at the baseline */
|
||||||
int pos_y = y + savedFont->specific.xcb.info->font_ascent;
|
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;
|
return;
|
||||||
case FONT_TYPE_XCB:
|
case FONT_TYPE_XCB:
|
||||||
draw_text_xcb(i3string_as_ucs2(text), i3string_get_num_glyphs(text),
|
draw_text_xcb(i3string_as_ucs2(text), i3string_get_num_glyphs(text),
|
||||||
drawable, gc, x, y, max_width);
|
drawable, gc, x, y);
|
||||||
break;
|
break;
|
||||||
case FONT_TYPE_PANGO:
|
case FONT_TYPE_PANGO:
|
||||||
/* Render the text using Pango */
|
/* Render the text using Pango */
|
||||||
|
|
|
@ -12,12 +12,11 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#ifndef HAVE_mkdirp
|
||||||
/*
|
/*
|
||||||
* Emulates mkdir -p (creates any missing folders)
|
* Emulates mkdir -p (creates any missing folders)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#if !defined(__sun)
|
|
||||||
int mkdirp(const char *path, mode_t mode) {
|
int mkdirp(const char *path, mode_t mode) {
|
||||||
if (mkdir(path, mode) == 0)
|
if (mkdir(path, mode) == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -10,8 +10,7 @@
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#ifndef HAVE_strndup
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Taken from FreeBSD
|
* Taken from FreeBSD
|
||||||
* Returns a pointer to a new string which is a duplicate of the
|
* 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';
|
copy[len] = '\0';
|
||||||
return (copy);
|
return (copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -107,7 +107,7 @@ AC_DEFUN([AX_CODE_COVERAGE],[
|
||||||
])
|
])
|
||||||
|
|
||||||
# List of supported lcov versions.
|
# 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([LCOV], [lcov], [lcov])
|
||||||
AC_CHECK_PROG([GENHTML], [genhtml], [genhtml])
|
AC_CHECK_PROG([GENHTML], [genhtml], [genhtml])
|
||||||
|
|
|
@ -146,6 +146,8 @@ state WORKSPACE_NUMBER:
|
||||||
state FOCUS:
|
state FOCUS:
|
||||||
direction = 'left', 'right', 'up', 'down'
|
direction = 'left', 'right', 'up', 'down'
|
||||||
-> call cmd_focus_direction($direction)
|
-> call cmd_focus_direction($direction)
|
||||||
|
direction = 'prev', 'next'
|
||||||
|
-> FOCUS_AUTO
|
||||||
'output'
|
'output'
|
||||||
-> FOCUS_OUTPUT
|
-> FOCUS_OUTPUT
|
||||||
window_mode = 'tiling', 'floating', 'mode_toggle'
|
window_mode = 'tiling', 'floating', 'mode_toggle'
|
||||||
|
@ -155,6 +157,12 @@ state FOCUS:
|
||||||
end
|
end
|
||||||
-> call cmd_focus()
|
-> call cmd_focus()
|
||||||
|
|
||||||
|
state FOCUS_AUTO:
|
||||||
|
'sibling'
|
||||||
|
-> call cmd_focus_sibling($direction)
|
||||||
|
end
|
||||||
|
-> call cmd_focus_direction($direction)
|
||||||
|
|
||||||
state FOCUS_OUTPUT:
|
state FOCUS_OUTPUT:
|
||||||
output = string
|
output = string
|
||||||
-> call cmd_focus_output($output)
|
-> call cmd_focus_output($output)
|
||||||
|
|
|
@ -215,7 +215,7 @@ state MOUSE_WARPING:
|
||||||
|
|
||||||
# focus_wrapping
|
# focus_wrapping
|
||||||
state 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)
|
-> call cfg_focus_wrapping($value)
|
||||||
|
|
||||||
# force_focus_wrapping
|
# force_focus_wrapping
|
||||||
|
@ -468,6 +468,7 @@ state BAR:
|
||||||
'separator_symbol' -> BAR_SEPARATOR_SYMBOL
|
'separator_symbol' -> BAR_SEPARATOR_SYMBOL
|
||||||
'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR
|
'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR
|
||||||
'workspace_buttons' -> BAR_WORKSPACE_BUTTONS
|
'workspace_buttons' -> BAR_WORKSPACE_BUTTONS
|
||||||
|
'workspace_min_width' -> BAR_WORKSPACE_MIN_WIDTH
|
||||||
'strip_workspace_numbers' -> BAR_STRIP_WORKSPACE_NUMBERS
|
'strip_workspace_numbers' -> BAR_STRIP_WORKSPACE_NUMBERS
|
||||||
'strip_workspace_name' -> BAR_STRIP_WORKSPACE_NAME
|
'strip_workspace_name' -> BAR_STRIP_WORKSPACE_NAME
|
||||||
'verbose' -> BAR_VERBOSE
|
'verbose' -> BAR_VERBOSE
|
||||||
|
@ -572,6 +573,16 @@ state BAR_WORKSPACE_BUTTONS:
|
||||||
value = word
|
value = word
|
||||||
-> call cfg_bar_workspace_buttons($value); BAR
|
-> 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:
|
state BAR_STRIP_WORKSPACE_NUMBERS:
|
||||||
value = word
|
value = word
|
||||||
-> call cfg_bar_strip_workspace_numbers($value); BAR
|
-> call cfg_bar_strip_workspace_numbers($value); BAR
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#!/bin/zsh
|
#!/bin/zsh
|
||||||
# This script is used to prepare a new release of i3.
|
# This script is used to prepare a new release of i3.
|
||||||
|
|
||||||
export RELEASE_VERSION="4.16"
|
export RELEASE_VERSION="4.17"
|
||||||
export PREVIOUS_VERSION="4.15"
|
export PREVIOUS_VERSION="4.16"
|
||||||
export RELEASE_BRANCH="next"
|
export RELEASE_BRANCH="next"
|
||||||
|
|
||||||
if [ ! -e "../i3.github.io" ]
|
if [ ! -e "../i3.github.io" ]
|
||||||
|
|
75
src/click.c
75
src/click.c
|
@ -26,7 +26,7 @@ typedef enum { CLICK_BORDER = 0,
|
||||||
* then calls resize_graphical_handler().
|
* 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);
|
DLOG("border = %d, con = %p\n", border, con);
|
||||||
Con *second = NULL;
|
Con *second = NULL;
|
||||||
Con *first = con;
|
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);
|
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");
|
DLOG("After resize handler, rendering\n");
|
||||||
tree_render();
|
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 &&
|
if (to_right < to_left &&
|
||||||
to_right < to_top &&
|
to_right < to_top &&
|
||||||
to_right < to_bottom)
|
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 &&
|
if (to_left < to_right &&
|
||||||
to_left < to_top &&
|
to_left < to_top &&
|
||||||
to_left < to_bottom)
|
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 &&
|
if (to_top < to_right &&
|
||||||
to_top < to_left &&
|
to_top < to_left &&
|
||||||
to_top < to_bottom)
|
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 &&
|
if (to_bottom < to_right &&
|
||||||
to_bottom < to_left &&
|
to_bottom < to_left &&
|
||||||
to_bottom < to_top)
|
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;
|
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().
|
* 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) */
|
/* check if this was a click on the window border (and on which one) */
|
||||||
Rect bsr = con_border_style_rect(con);
|
Rect bsr = con_border_style_rect(con);
|
||||||
DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n",
|
DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n",
|
||||||
event->event_x, event->event_y, con, event->event);
|
event->event_x, event->event_y, con, event->event);
|
||||||
DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width);
|
DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width);
|
||||||
if (dest == CLICK_DECORATION) {
|
if (dest == CLICK_DECORATION) {
|
||||||
/* The user clicked on a window decoration. We ignore the following case:
|
return tiling_resize_for_border(con, BORDER_TOP, event, use_threshold);
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event->event_x >= 0 && event->event_x <= (int32_t)bsr.x &&
|
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))
|
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) &&
|
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))
|
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))
|
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;
|
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);
|
Con *floatingcon = con_inside_floating(con);
|
||||||
const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT;
|
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 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 */
|
/* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
|
||||||
if (in_stacked &&
|
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_LEFT ||
|
||||||
event->detail == XCB_BUTTON_SCROLL_RIGHT)) {
|
event->detail == XCB_BUTTON_SCROLL_RIGHT)) {
|
||||||
DLOG("Scrolling on a window decoration\n");
|
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
|
/* Use the focused child of the tabbed / stacked container, not the
|
||||||
* container the user scrolled on. */
|
* container the user scrolled on. */
|
||||||
Con *focused = con->parent;
|
Con *current = TAILQ_FIRST(&(con->parent->focus_head));
|
||||||
focused = TAILQ_FIRST(&(focused->focus_head));
|
const position_t direction =
|
||||||
con_activate(con_descend_focused(focused));
|
(event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) ? BEFORE : AFTER;
|
||||||
/* To prevent scrolling from going outside the container (see ticket
|
Con *next = get_tree_next_sibling(current, direction);
|
||||||
* #557), we first check if scrolling is possible at all. */
|
con_activate(con_descend_focused(next ? next : current));
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
goto done;
|
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) {
|
if (floatingcon != NULL && fs != con) {
|
||||||
/* 4: floating_modifier plus left mouse button drags */
|
/* 4: floating_modifier plus left mouse button drags */
|
||||||
if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) {
|
if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) {
|
||||||
floating_drag_window(floatingcon, event);
|
floating_drag_window(floatingcon, event, false);
|
||||||
return 1;
|
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) {
|
is_left_or_right_click) {
|
||||||
/* try tiling resize, but continue if it doesn’t work */
|
/* try tiling resize, but continue if it doesn’t work */
|
||||||
DLOG("tiling resize with fallback\n");
|
DLOG("tiling resize with fallback\n");
|
||||||
if (tiling_resize(con, event, dest))
|
if (tiling_resize(con, event, dest, !was_focused))
|
||||||
goto done;
|
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
|
/* 6: dragging, if this was a click on a decoration (which did not lead
|
||||||
* to a resize) */
|
* to a resize) */
|
||||||
if (!in_stacked && dest == CLICK_DECORATION &&
|
if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_CLICK_LEFT) {
|
||||||
(event->detail == XCB_BUTTON_CLICK_LEFT)) {
|
floating_drag_window(floatingcon, event, !was_focused);
|
||||||
floating_drag_window(floatingcon, event);
|
|
||||||
return 1;
|
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) &&
|
else if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
|
||||||
is_left_or_right_click) {
|
is_left_or_right_click) {
|
||||||
DLOG("Trying to resize (tiling)\n");
|
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:
|
done:
|
||||||
|
|
163
src/commands.c
163
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;
|
Rect old_rect = floating_con->rect;
|
||||||
Con *focused_con = con_descend_focused(floating_con);
|
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?
|
/* 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 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,7 +596,7 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
|
||||||
|
|
||||||
Con *floating_con;
|
Con *floating_con;
|
||||||
if ((floating_con = con_inside_floating(current->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 {
|
} else {
|
||||||
if (strcmp(direction, "width") == 0 ||
|
if (strcmp(direction, "width") == 0 ||
|
||||||
strcmp(direction, "height") == 0) {
|
strcmp(direction, "height") == 0) {
|
||||||
|
@ -1089,7 +1089,7 @@ void cmd_floating(I3_CMD, const char *floating_mode) {
|
||||||
if (strcmp(floating_mode, "enable") == 0) {
|
if (strcmp(floating_mode, "enable") == 0) {
|
||||||
floating_enable(current->con, false);
|
floating_enable(current->con, false);
|
||||||
} else {
|
} 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) {
|
void cmd_exec(I3_CMD, const char *nosn, const char *command) {
|
||||||
bool no_startup_id = (nosn != NULL);
|
bool no_startup_id = (nosn != NULL);
|
||||||
|
|
||||||
|
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);
|
DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
|
||||||
start_application(command, no_startup_id);
|
start_application(command, no_startup_id);
|
||||||
|
}
|
||||||
|
|
||||||
ysuccess(true);
|
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) {
|
void cmd_focus_direction(I3_CMD, const char *direction_str) {
|
||||||
switch (parse_direction(direction)) {
|
HANDLE_EMPTY_MATCH;
|
||||||
case D_LEFT:
|
CMD_FOCUS_WARN_CHILDREN;
|
||||||
tree_next('p', HORIZ);
|
|
||||||
break;
|
direction_t direction;
|
||||||
case D_RIGHT:
|
position_t position;
|
||||||
tree_next('n', HORIZ);
|
bool auto_direction = true;
|
||||||
break;
|
if (strcmp(direction_str, "prev") == 0) {
|
||||||
case D_UP:
|
position = BEFORE;
|
||||||
tree_next('p', VERT);
|
} else if (strcmp(direction_str, "next") == 0) {
|
||||||
break;
|
position = AFTER;
|
||||||
case D_DOWN:
|
} else {
|
||||||
tree_next('n', VERT);
|
auto_direction = false;
|
||||||
break;
|
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;
|
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) {
|
void cmd_focus_sibling(I3_CMD, const char *direction_str) {
|
||||||
/* Disable fullscreen container in workspace with container to be focused. */
|
HANDLE_EMPTY_MATCH;
|
||||||
Con *ws = con_get_workspace(con);
|
CMD_FOCUS_WARN_CHILDREN;
|
||||||
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)) {
|
const position_t direction = (STARTS_WITH(direction_str, "prev")) ? BEFORE : AFTER;
|
||||||
con_disable_fullscreen(fullscreen_on_ws);
|
owindow *current;
|
||||||
|
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||||
|
Con *ws = con_get_workspace(current->con);
|
||||||
|
if (!ws || con_is_internal(ws)) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
con_activate(con);
|
Con *next = get_tree_next_sibling(current->con, direction);
|
||||||
|
if (next) {
|
||||||
|
con_activate(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
(!to_floating && current->type == CT_FLOATING_CON))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
cmd_focus_force_focus(con_descend_focused(current));
|
con_activate_unblock(con_descend_focused(current));
|
||||||
success = true;
|
success = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1329,12 +1385,15 @@ void cmd_focus(I3_CMD) {
|
||||||
ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
|
ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
|
||||||
|
|
||||||
yerror("You have to specify which window/container should be focused");
|
yerror("You have to specify which window/container should be focused");
|
||||||
|
return;
|
||||||
|
} else if (TAILQ_EMPTY(&owindows)) {
|
||||||
|
yerror("No window matches given criteria");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CMD_FOCUS_WARN_CHILDREN;
|
||||||
|
|
||||||
Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
|
Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
|
||||||
int count = 0;
|
|
||||||
owindow *current;
|
owindow *current;
|
||||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||||
Con *ws = con_get_workspace(current->con);
|
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(). */
|
/* In case this is a scratchpad window, call scratchpad_show(). */
|
||||||
if (ws == __i3_scratch) {
|
if (ws == __i3_scratch) {
|
||||||
scratchpad_show(current->con);
|
scratchpad_show(current->con);
|
||||||
count++;
|
|
||||||
/* While for the normal focus case we can change focus multiple
|
/* While for the normal focus case we can change focus multiple
|
||||||
* times and only a single window ends up focused, we could show
|
* times and only a single window ends up focused, we could show
|
||||||
* multiple scratchpad windows. So, rather break here. */
|
* multiple scratchpad windows. So, rather break here. */
|
||||||
break;
|
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);
|
LOG("focusing %p / %s\n", current->con, current->con->name);
|
||||||
con_activate(current->con);
|
con_activate_unblock(current->con);
|
||||||
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);
|
|
||||||
|
|
||||||
cmd_output->needs_tree_render = true;
|
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 */
|
/* The move command should not disturb focus. con_exists is called because
|
||||||
if (focused != initially_focused)
|
* tree_move calls tree_flatten. */
|
||||||
|
if (focused != initially_focused && con_exists(initially_focused)) {
|
||||||
con_activate(initially_focused);
|
con_activate(initially_focused);
|
||||||
|
}
|
||||||
|
|
||||||
// XXX: default reply for now, make this a better reply
|
// XXX: default reply for now, make this a better reply
|
||||||
ysuccess(true);
|
ysuccess(true);
|
||||||
|
@ -1562,7 +1598,7 @@ void cmd_layout_toggle(I3_CMD, const char *toggle_mode) {
|
||||||
*/
|
*/
|
||||||
void cmd_exit(I3_CMD) {
|
void cmd_exit(I3_CMD) {
|
||||||
LOG("Exiting due to user command.\n");
|
LOG("Exiting due to user command.\n");
|
||||||
exit(0);
|
exit(EXIT_SUCCESS);
|
||||||
|
|
||||||
/* unreached */
|
/* unreached */
|
||||||
}
|
}
|
||||||
|
@ -2013,6 +2049,13 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
|
||||||
con_focus(previously_focused);
|
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;
|
cmd_output->needs_tree_render = true;
|
||||||
ysuccess(true);
|
ysuccess(true);
|
||||||
|
|
||||||
|
|
|
@ -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 "
|
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
|
||||||
"in the code, or a new command which contains more than "
|
"in the code, or a new command which contains more than "
|
||||||
"10 identified tokens.\n");
|
"10 identified tokens.\n");
|
||||||
exit(1);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO move to a common util
|
// 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 "
|
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
|
||||||
"in the code, or a new command which contains more than "
|
"in the code, or a new command which contains more than "
|
||||||
"10 identified tokens.\n");
|
"10 identified tokens.\n");
|
||||||
exit(1);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO move to a common util
|
// TODO move to a common util
|
||||||
|
|
106
src/con.c
106
src/con.c
|
@ -265,6 +265,41 @@ void con_activate(Con *con) {
|
||||||
con_raise(con);
|
con_raise(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.
|
* 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
|
/* 1: save the container which is going to be focused after the current
|
||||||
* container is moved away */
|
* container is moved away */
|
||||||
Con *focus_next = NULL;
|
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);
|
focus_next = con_descend_focused(source_ws);
|
||||||
if (focus_next == con || con_has_parent(focus_next, con)) {
|
if (focus_next == con || con_has_parent(focus_next, con)) {
|
||||||
focus_next = con_next_focused(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,
|
/* 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. */
|
* 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)) {
|
if (!con_is_leaf(con)) {
|
||||||
Con *child;
|
Con *child;
|
||||||
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
|
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
|
||||||
if (!child->window)
|
if (!child->window)
|
||||||
continue;
|
continue;
|
||||||
|
startup_sequence_delete_by_window(child->window);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (con->window) {
|
if (con->window) {
|
||||||
cookie = xcb_get_property(conn, false, con->window->id,
|
startup_sequence_delete_by_window(con->window);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 9. If the container was marked urgent, move the urgency hint. */
|
/* 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;
|
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. */
|
/* For floating target containers, we just send the window to the same workspace. */
|
||||||
if (con_is_floating(target)) {
|
if (con_is_floating(target)) {
|
||||||
DLOG("target container is floating, moving container to target's workspace.\n");
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target->type == CT_WORKSPACE) {
|
if (target->type == CT_WORKSPACE && con_is_leaf(target)) {
|
||||||
DLOG("target container is a workspace, simply moving the container there.\n");
|
DLOG("target container is an empty workspace, simply moving the container there.\n");
|
||||||
con_move_to_workspace(con, target, true, false, false);
|
con_move_to_workspace(con, target, true, false, false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2401,3 +2426,40 @@ bool con_swap(Con *first, Con *second) {
|
||||||
uint32_t con_rect_size_in_orientation(Con *con) {
|
uint32_t con_rect_size_in_orientation(Con *con) {
|
||||||
return (con_orientation(con) == HORIZ ? con->rect.width : con->rect.height);
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -268,6 +268,8 @@ CFGFUN(disable_randr15, const char *value) {
|
||||||
CFGFUN(focus_wrapping, const char *value) {
|
CFGFUN(focus_wrapping, const char *value) {
|
||||||
if (strcmp(value, "force") == 0) {
|
if (strcmp(value, "force") == 0) {
|
||||||
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
|
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
|
||||||
|
} else if (strcmp(value, "workspace") == 0) {
|
||||||
|
config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE;
|
||||||
} else if (eval_boolstr(value)) {
|
} else if (eval_boolstr(value)) {
|
||||||
config.focus_wrapping = FOCUS_WRAPPING_ON;
|
config.focus_wrapping = FOCUS_WRAPPING_ON;
|
||||||
} else {
|
} else {
|
||||||
|
@ -646,6 +648,10 @@ CFGFUN(bar_workspace_buttons, const char *value) {
|
||||||
current_bar->hide_workspace_buttons = !eval_boolstr(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) {
|
CFGFUN(bar_strip_workspace_numbers, const char *value) {
|
||||||
current_bar->strip_workspace_numbers = eval_boolstr(value);
|
current_bar->strip_workspace_numbers = eval_boolstr(value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 "
|
fprintf(stderr, "BUG: config_parser stack full. This means either a bug "
|
||||||
"in the code, or a new command which contains more than "
|
"in the code, or a new command which contains more than "
|
||||||
"10 identified tokens.\n");
|
"10 identified tokens.\n");
|
||||||
exit(1);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void push_long(const char *identifier, long num) {
|
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 "
|
fprintf(stderr, "BUG: config_parser stack full. This means either a bug "
|
||||||
"in the code, or a new command which contains more than "
|
"in the code, or a new command which contains more than "
|
||||||
"10 identified tokens.\n");
|
"10 identified tokens.\n");
|
||||||
exit(1);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *get_string(const char *identifier) {
|
static const char *get_string(const char *identifier) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -82,6 +82,6 @@ void fake_outputs_init(const char *output_spec) {
|
||||||
|
|
||||||
if (num_screens == 0) {
|
if (num_screens == 0) {
|
||||||
ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
|
ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
|
||||||
exit(0);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
228
src/floating.c
228
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("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);
|
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;
|
nc->rect = con->geometry;
|
||||||
/* If the geometry was not set (split containers), we need to determine a
|
/* If the geometry was not set (split containers), we need to determine a
|
||||||
* sensible one by combining the geometry of all children */
|
* 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");
|
DLOG("Geometry not set, combining children\n");
|
||||||
Con *child;
|
Con *child;
|
||||||
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
|
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) {
|
if (nc->rect.x == 0 && nc->rect.y == 0) {
|
||||||
Con *leader;
|
Con *leader;
|
||||||
if (con->window && con->window->leader != XCB_NONE &&
|
if (con->window && con->window->leader != XCB_NONE &&
|
||||||
|
con->window->id != con->window->leader &&
|
||||||
(leader = con_by_window_id(con->window->leader)) != NULL) {
|
(leader = con_by_window_id(con->window->leader)) != NULL) {
|
||||||
DLOG("Centering above leader\n");
|
DLOG("Centering above leader\n");
|
||||||
floating_center(nc, leader->rect);
|
floating_center(nc, leader->rect);
|
||||||
|
@ -421,7 +421,7 @@ void floating_enable(Con *con, bool automatic) {
|
||||||
ipc_send_window_event("floating", con);
|
ipc_send_window_event("floating", con);
|
||||||
}
|
}
|
||||||
|
|
||||||
void floating_disable(Con *con, bool automatic) {
|
void floating_disable(Con *con) {
|
||||||
if (!con_is_floating(con)) {
|
if (!con_is_floating(con)) {
|
||||||
LOG("Container isn't floating, not doing anything.\n");
|
LOG("Container isn't floating, not doing anything.\n");
|
||||||
return;
|
return;
|
||||||
|
@ -469,7 +469,7 @@ void toggle_floating_mode(Con *con, bool automatic) {
|
||||||
if (con_is_floating(con)) {
|
if (con_is_floating(con)) {
|
||||||
LOG("already floating, re-setting to tiling\n");
|
LOG("already floating, re-setting to tiling\n");
|
||||||
|
|
||||||
floating_disable(con, automatic);
|
floating_disable(con);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,6 +492,11 @@ void floating_raise_con(Con *con) {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
bool floating_maybe_reassign_ws(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);
|
Output *output = get_output_from_rect(con->rect);
|
||||||
|
|
||||||
if (!output) {
|
if (!output) {
|
||||||
|
@ -561,8 +566,6 @@ void floating_move_to_pointer(Con *con) {
|
||||||
}
|
}
|
||||||
|
|
||||||
DRAGGING_CB(drag_window_callback) {
|
DRAGGING_CB(drag_window_callback) {
|
||||||
const struct xcb_button_press_event_t *event = extra;
|
|
||||||
|
|
||||||
/* Reposition the client correctly while moving */
|
/* Reposition the client correctly while moving */
|
||||||
con->rect.x = old_rect->x + (new_x - event->root_x);
|
con->rect.x = old_rect->x + (new_x - event->root_x);
|
||||||
con->rect.y = old_rect->y + (new_y - event->root_y);
|
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
|
* 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");
|
DLOG("floating_drag_window\n");
|
||||||
|
|
||||||
/* Push changes before dragging, so that the window gets raised now and not
|
/* 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;
|
Rect initial_rect = con->rect;
|
||||||
|
|
||||||
/* Drag the window */
|
/* 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)) {
|
if (!con_exists(con)) {
|
||||||
DLOG("The container has been closed in the meantime.\n");
|
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 {
|
struct resize_window_callback_params {
|
||||||
const border_t corner;
|
const border_t corner;
|
||||||
const bool proportional;
|
const bool proportional;
|
||||||
const xcb_button_press_event_t *event;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DRAGGING_CB(resize_window_callback) {
|
DRAGGING_CB(resize_window_callback) {
|
||||||
const struct resize_window_callback_params *params = extra;
|
const struct resize_window_callback_params *params = extra;
|
||||||
const xcb_button_press_event_t *event = params->event;
|
|
||||||
border_t corner = params->corner;
|
border_t corner = params->corner;
|
||||||
|
|
||||||
int32_t dest_x = con->rect.x;
|
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;
|
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 */
|
/* get the initial rect in case of revert/cancel */
|
||||||
Rect initial_rect = con->rect;
|
Rect initial_rect = con->rect;
|
||||||
|
|
||||||
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, ¶ms);
|
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, cursor, false, resize_window_callback, ¶ms);
|
||||||
|
|
||||||
if (!con_exists(con)) {
|
if (!con_exists(con)) {
|
||||||
DLOG("The container has been closed in the meantime.\n");
|
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;
|
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
|
* Repositions the CT_FLOATING_CON to have the coordinates specified by
|
||||||
* newrect, but only if the coordinates are not out-of-bounds. Also reassigns
|
* newrect, but only if the coordinates are not out-of-bounds. Also reassigns
|
||||||
|
|
|
@ -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))) {
|
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);
|
DLOG("Focusing con = %p\n", con);
|
||||||
workspace_show(workspace);
|
workspace_show(workspace);
|
||||||
con_activate(con);
|
con_activate_unblock(con);
|
||||||
tree_render();
|
tree_render();
|
||||||
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(workspace))) {
|
} 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);
|
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);
|
xcb_get_geometry_reply_t *reply = xcb_get_geometry_reply(conn, cookie, NULL);
|
||||||
if (reply == NULL) {
|
if (reply == NULL) {
|
||||||
ELOG("Could not get geometry of the root window, exiting\n");
|
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);
|
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);
|
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);
|
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);
|
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);
|
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)
|
if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
window_update_role(con->window, prop, false);
|
window_update_role(con->window, prop);
|
||||||
|
|
||||||
|
con = remanage_window(con);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -752,7 +758,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
||||||
workspace_show(ws);
|
workspace_show(ws);
|
||||||
/* Re-set focus, even if unchanged from i3’s perspective. */
|
/* Re-set focus, even if unchanged from i3’s perspective. */
|
||||||
focused_id = XCB_NONE;
|
focused_id = XCB_NONE;
|
||||||
con_activate(con);
|
con_activate_unblock(con);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Request is from an application. */
|
/* 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))) {
|
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);
|
DLOG("Focusing con = %p\n", con);
|
||||||
workspace_show(ws);
|
con_activate_unblock(con);
|
||||||
con_activate(con);
|
|
||||||
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
|
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
|
||||||
DLOG("Marking con = %p urgent\n", con);
|
DLOG("Marking con = %p urgent\n", con);
|
||||||
con_set_urgency(con, true);
|
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)};
|
.event_y = y_root - (con->rect.y)};
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case _NET_WM_MOVERESIZE_MOVE:
|
case _NET_WM_MOVERESIZE_MOVE:
|
||||||
floating_drag_window(con->parent, &fake);
|
floating_drag_window(con->parent, &fake, false);
|
||||||
break;
|
break;
|
||||||
case _NET_WM_MOVERESIZE_SIZE_TOPLEFT ... _NET_WM_MOVERESIZE_SIZE_LEFT:
|
case _NET_WM_MOVERESIZE_SIZE_TOPLEFT ... _NET_WM_MOVERESIZE_SIZE_LEFT:
|
||||||
floating_resize_window(con->parent, false, &fake);
|
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");
|
DLOG("focus is different / refocusing floating window: updating decorations\n");
|
||||||
|
|
||||||
/* Get the currently focused workspace to check if the focus change also
|
con_activate_unblock(con);
|
||||||
* 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(con);
|
|
||||||
/* We update focused_id because we don’t need to set focus again */
|
/* We update focused_id because we don’t need to set focus again */
|
||||||
focused_id = event->event;
|
focused_id = event->event;
|
||||||
tree_render();
|
tree_render();
|
||||||
|
@ -1158,7 +1157,9 @@ static bool handle_class_change(void *data, xcb_connection_t *conn, uint8_t stat
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
window_update_class(con->window, prop, false);
|
window_update_class(con->window, prop);
|
||||||
|
|
||||||
|
con = remanage_window(con);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
36
src/ipc.c
36
src/ipc.c
|
@ -528,6 +528,36 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
|
||||||
else
|
else
|
||||||
y(null);
|
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) {
|
if (con->window && !inplace_restart) {
|
||||||
/* Window properties are useless to preserve when restarting because
|
/* Window properties are useless to preserve when restarting because
|
||||||
* they will be queried again anyway. However, for i3-save-tree(1),
|
* 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");
|
ystr("workspace_buttons");
|
||||||
y(bool, !config->hide_workspace_buttons);
|
y(bool, !config->hide_workspace_buttons);
|
||||||
|
|
||||||
|
ystr("workspace_min_width");
|
||||||
|
y(integer, config->workspace_min_width);
|
||||||
|
|
||||||
ystr("strip_workspace_numbers");
|
ystr("strip_workspace_numbers");
|
||||||
y(bool, config->strip_workspace_numbers);
|
y(bool, config->strip_workspace_numbers);
|
||||||
|
|
||||||
|
@ -873,6 +906,9 @@ IPC_HANDLER(get_workspaces) {
|
||||||
assert(ws->type == CT_WORKSPACE);
|
assert(ws->type == CT_WORKSPACE);
|
||||||
y(map_open);
|
y(map_open);
|
||||||
|
|
||||||
|
ystr("id");
|
||||||
|
y(integer, (uintptr_t)ws);
|
||||||
|
|
||||||
ystr("num");
|
ystr("num");
|
||||||
y(integer, ws->num);
|
y(integer, ws->num);
|
||||||
|
|
||||||
|
|
|
@ -141,8 +141,7 @@ static int json_end_map(void *ctx) {
|
||||||
// Also set a size if none was supplied, otherwise the placeholder
|
// Also set a size if none was supplied, otherwise the placeholder
|
||||||
// window cannot be created as X11 requests with width=0 or
|
// window cannot be created as X11 requests with width=0 or
|
||||||
// height=0 are invalid.
|
// height=0 are invalid.
|
||||||
const Rect zero = {0, 0, 0, 0};
|
if (rect_equals(json_node->rect, (Rect){0, 0, 0, 0})) {
|
||||||
if (memcmp(&(json_node->rect), &zero, sizeof(Rect)) == 0) {
|
|
||||||
DLOG("Geometry not set, combining children\n");
|
DLOG("Geometry not set, combining children\n");
|
||||||
Con *child;
|
Con *child;
|
||||||
TAILQ_FOREACH(child, &(json_node->nodes_head), nodes) {
|
TAILQ_FOREACH(child, &(json_node->nodes_head), nodes) {
|
||||||
|
|
14
src/main.c
14
src/main.c
|
@ -93,6 +93,11 @@ bool shape_supported = true;
|
||||||
|
|
||||||
bool force_xinerama = false;
|
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.
|
* This callback is only a dummy, see xcb_prepare_cb.
|
||||||
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
|
* 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 floating toggle\n"
|
||||||
"\ti3 kill window\n"
|
"\ti3 kill window\n"
|
||||||
"\n");
|
"\n");
|
||||||
exit(EXIT_FAILURE);
|
exit(opt == 'h' ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (only_check_config) {
|
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
|
/* 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) {
|
if (!output) {
|
||||||
ELOG("ERROR: No screen at (%d, %d), starting on the first screen\n",
|
ELOG("ERROR: No screen at (%d, %d), starting on the first screen\n",
|
||||||
pointerreply->root_x, pointerreply->root_y);
|
pointerreply->root_x, pointerreply->root_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!output) {
|
||||||
output = get_first_output();
|
output = get_first_output();
|
||||||
}
|
}
|
||||||
|
|
||||||
con_activate(con_descend_focused(output_get_content(output->con)));
|
con_activate(con_descend_focused(output_get_content(output->con)));
|
||||||
free(pointerreply);
|
free(pointerreply);
|
||||||
}
|
|
||||||
|
|
||||||
tree_render();
|
tree_render();
|
||||||
|
|
||||||
|
|
107
src/manage.c
107
src/manage.c
|
@ -13,6 +13,34 @@
|
||||||
|
|
||||||
#include <yajl/yajl_gen.h>
|
#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
|
* 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);
|
FREE(buttons);
|
||||||
|
|
||||||
/* update as much information as possible so far (some replies may be NULL) */
|
/* 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_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL));
|
||||||
window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL), true);
|
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), true);
|
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_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_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_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;
|
bool urgency_hint;
|
||||||
window_update_hints(cwindow, xcb_get_property_reply(conn, wm_hints_cookie, NULL), &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;
|
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");
|
DLOG("Uh?! Container without a placeholder, but with a window, has swallowed this to-be-managed window?!\n");
|
||||||
} else {
|
} else {
|
||||||
/* Remove remaining criteria, the first swallowed window wins. */
|
/* Remove remaining criteria, the first swallowed window wins. */
|
||||||
while (!TAILQ_EMPTY(&(nc->swallow_head))) {
|
_remove_matches(nc);
|
||||||
Match *first = TAILQ_FIRST(&(nc->swallow_head));
|
|
||||||
TAILQ_REMOVE(&(nc->swallow_head), first, matches);
|
|
||||||
match_free(first);
|
|
||||||
free(first);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
xcb_window_t old_frame = XCB_NONE;
|
xcb_window_t old_frame = XCB_NONE;
|
||||||
if (nc->window != cwindow && nc->window != NULL) {
|
if (nc->window != cwindow && nc->window != NULL) {
|
||||||
window_free(nc->window);
|
window_free(nc->window);
|
||||||
/* Match frame and window depth. This is needed because X will refuse to reparent a
|
old_frame = _match_depth(cwindow, nc);
|
||||||
* window whose background is ParentRelative under a window with a different depth. */
|
|
||||||
if (nc->depth != cwindow->depth) {
|
|
||||||
old_frame = nc->frame.id;
|
|
||||||
nc->depth = cwindow->depth;
|
|
||||||
x_con_reframe(nc);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
nc->window = cwindow;
|
nc->window = cwindow;
|
||||||
x_reinit(nc);
|
x_reinit(nc);
|
||||||
|
@ -594,6 +611,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||||
}
|
}
|
||||||
render_con(croot);
|
render_con(croot);
|
||||||
|
|
||||||
|
cwindow->managed_since = time(NULL);
|
||||||
|
|
||||||
/* Send an event about window creation */
|
/* Send an event about window creation */
|
||||||
ipc_send_window_event("new", nc);
|
ipc_send_window_event("new", nc);
|
||||||
|
|
||||||
|
@ -670,3 +689,57 @@ geom_out:
|
||||||
out:
|
out:
|
||||||
free(attr);
|
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;
|
||||||
|
}
|
||||||
|
|
11
src/move.c
11
src/move.c
|
@ -237,17 +237,16 @@ static void move_to_output_directed(Con *con, direction_t direction) {
|
||||||
/* force re-painting the indicators */
|
/* force re-painting the indicators */
|
||||||
FREE(con->deco_render_params);
|
FREE(con->deco_render_params);
|
||||||
|
|
||||||
tree_flatten(croot);
|
|
||||||
ipc_send_window_event("move", con);
|
ipc_send_window_event("move", con);
|
||||||
|
tree_flatten(croot);
|
||||||
ewmh_update_wm_desktop();
|
ewmh_update_wm_desktop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Moves the given container in the given direction (D_LEFT, D_RIGHT,
|
* Moves the given container in the given direction
|
||||||
* D_UP, D_DOWN).
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void tree_move(Con *con, int direction) {
|
void tree_move(Con *con, direction_t direction) {
|
||||||
position_t position;
|
position_t position;
|
||||||
Con *target;
|
Con *target;
|
||||||
|
|
||||||
|
@ -283,7 +282,7 @@ void tree_move(Con *con, int direction) {
|
||||||
if (!same_orientation) {
|
if (!same_orientation) {
|
||||||
if (con_is_floating(con)) {
|
if (con_is_floating(con)) {
|
||||||
/* this is a floating con, we just disable floating */
|
/* this is a floating con, we just disable floating */
|
||||||
floating_disable(con, true);
|
floating_disable(con);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (con_inside_floating(con)) {
|
if (con_inside_floating(con)) {
|
||||||
|
@ -385,7 +384,7 @@ end:
|
||||||
/* force re-painting the indicators */
|
/* force re-painting the indicators */
|
||||||
FREE(con->deco_render_params);
|
FREE(con->deco_render_params);
|
||||||
|
|
||||||
tree_flatten(croot);
|
|
||||||
ipc_send_window_event("move", con);
|
ipc_send_window_event("move", con);
|
||||||
|
tree_flatten(croot);
|
||||||
ewmh_update_wm_desktop();
|
ewmh_update_wm_desktop();
|
||||||
}
|
}
|
||||||
|
|
17
src/randr.c
17
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 *get_first_output(void) {
|
||||||
Output *output;
|
Output *output, *result = NULL;
|
||||||
|
|
||||||
TAILQ_FOREACH(output, &outputs, outputs)
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||||
if (output->active)
|
if (output->active) {
|
||||||
|
if (output->primary) {
|
||||||
return output;
|
return output;
|
||||||
|
}
|
||||||
|
if (!result) {
|
||||||
|
result = output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
die("No usable outputs available.\n");
|
die("No usable outputs available.\n");
|
||||||
}
|
}
|
||||||
|
|
47
src/resize.c
47
src/resize.c
|
@ -21,12 +21,32 @@ struct callback_params {
|
||||||
Con *output;
|
Con *output;
|
||||||
xcb_window_t helpwin;
|
xcb_window_t helpwin;
|
||||||
uint32_t *new_position;
|
uint32_t *new_position;
|
||||||
|
bool *threshold_exceeded;
|
||||||
};
|
};
|
||||||
|
|
||||||
DRAGGING_CB(resize_callback) {
|
DRAGGING_CB(resize_callback) {
|
||||||
const struct callback_params *params = extra;
|
const struct callback_params *params = extra;
|
||||||
Con *output = params->output;
|
Con *output = params->output;
|
||||||
DLOG("new x = %d, y = %d\n", new_x, new_y);
|
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) {
|
if (params->orientation == HORIZ) {
|
||||||
/* Check if the new coordinates are within screen boundaries */
|
/* Check if the new coordinates are within screen boundaries */
|
||||||
if (new_x > (output->rect.x + output->rect.width - 25) ||
|
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;
|
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);
|
Con *output = con_get_output(first);
|
||||||
DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width);
|
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.width = logical_px(2);
|
||||||
helprect.height = second->rect.height;
|
helprect.height = second->rect.height;
|
||||||
initial_position = second->rect.x;
|
initial_position = second->rect.x;
|
||||||
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
|
|
||||||
second->rect.x, event->root_y);
|
|
||||||
} else {
|
} else {
|
||||||
helprect.width = second->rect.width;
|
helprect.width = second->rect.width;
|
||||||
helprect.height = logical_px(2);
|
helprect.height = logical_px(2);
|
||||||
initial_position = second->rect.y;
|
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;
|
mask = XCB_CW_BACK_PIXEL;
|
||||||
|
@ -196,7 +214,18 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
|
||||||
values[1] = 1;
|
values[1] = 1;
|
||||||
|
|
||||||
xcb_window_t helpwin = create_window(conn, helprect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
|
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);
|
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' will be updated by the `resize_callback'. */
|
||||||
new_position = initial_position;
|
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_pointer' blocks until the drag is completed. */
|
||||||
drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, ¶ms);
|
drag_result_t drag_result = drag_pointer(NULL, event, grabwin, 0, use_threshold, resize_callback, ¶ms);
|
||||||
|
|
||||||
xcb_destroy_window(conn, helpwin);
|
xcb_destroy_window(conn, helpwin);
|
||||||
xcb_destroy_window(conn, grabwin);
|
xcb_destroy_window(conn, grabwin);
|
||||||
|
|
|
@ -40,9 +40,7 @@ void scratchpad_move(Con *con) {
|
||||||
|
|
||||||
/* If the current con is in fullscreen mode, we need to disable that,
|
/* If the current con is in fullscreen mode, we need to disable that,
|
||||||
* as a scratchpad window should never be in fullscreen mode */
|
* as a scratchpad window should never be in fullscreen mode */
|
||||||
if (focused && focused->type != CT_WORKSPACE && focused->fullscreen_mode != CF_NONE) {
|
con_disable_fullscreen(con);
|
||||||
con_toggle_fullscreen(focused, CF_OUTPUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 1: Ensure the window or any parent is floating. From now on, we deal
|
/* 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
|
* 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;
|
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");
|
DLOG("Scratchpad size unchanged.\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ static int sighandler_backtrace(void) {
|
||||||
NULL};
|
NULL};
|
||||||
execvp(args[0], args);
|
execvp(args[0], args);
|
||||||
DLOG("Failed to exec GDB\n");
|
DLOG("Failed to exec GDB\n");
|
||||||
exit(1);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
int status = 0;
|
int status = 0;
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,7 @@ void start_application(const char *command, bool no_startup_id) {
|
||||||
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL);
|
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL);
|
||||||
/* not reached */
|
/* not reached */
|
||||||
}
|
}
|
||||||
_exit(0);
|
_exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
wait(0);
|
wait(0);
|
||||||
|
|
||||||
|
@ -365,3 +365,22 @@ char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *
|
||||||
|
|
||||||
return sequence->workspace;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
263
src/tree.c
263
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_change_window_attributes(conn, con->window->id,
|
||||||
XCB_CW_EVENT_MASK, (uint32_t[]){XCB_NONE});
|
XCB_CW_EVENT_MASK, (uint32_t[]){XCB_NONE});
|
||||||
xcb_unmap_window(conn, con->window->id);
|
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.
|
/* Ignore X11 errors for the ReparentWindow request.
|
||||||
* X11 Errors are returned when the window was already destroyed */
|
* X11 Errors are returned when the window was already destroyed */
|
||||||
|
@ -462,170 +462,175 @@ void tree_render(void) {
|
||||||
DLOG("-- END RENDERING --\n");
|
DLOG("-- END RENDERING --\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static Con *get_tree_next_workspace(Con *con, direction_t direction) {
|
||||||
* Recursive function to walk the tree until a con can be found to focus.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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. */
|
|
||||||
if (con->type == CT_WORKSPACE) {
|
|
||||||
if (con_get_fullscreen_con(con, CF_GLOBAL)) {
|
if (con_get_fullscreen_con(con, CF_GLOBAL)) {
|
||||||
DLOG("Cannot change workspace while in global fullscreen mode.\n");
|
DLOG("Cannot change workspace while in global fullscreen mode.\n");
|
||||||
return false;
|
return NULL;
|
||||||
}
|
}
|
||||||
Output *current_output = get_output_containing(con->rect.x, con->rect.y);
|
|
||||||
Output *next_output;
|
|
||||||
|
|
||||||
if (!current_output)
|
Output *current_output = get_output_containing(con->rect.x, con->rect.y);
|
||||||
return false;
|
if (!current_output) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
DLOG("Current output is %s\n", output_primary_name(current_output));
|
DLOG("Current output is %s\n", output_primary_name(current_output));
|
||||||
|
|
||||||
/* Try to find next output */
|
Output *next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
|
||||||
direction_t direction;
|
if (!next_output) {
|
||||||
if (way == 'n' && orientation == HORIZ)
|
return NULL;
|
||||||
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;
|
|
||||||
|
|
||||||
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));
|
DLOG("Next output is %s\n", output_primary_name(next_output));
|
||||||
|
|
||||||
/* Find visible workspace on next output */
|
/* Find visible workspace on next output */
|
||||||
Con *workspace = NULL;
|
Con *workspace = NULL;
|
||||||
GREP_FIRST(workspace, output_get_content(next_output->con), workspace_is_visible(child));
|
GREP_FIRST(workspace, output_get_content(next_output->con), workspace_is_visible(child));
|
||||||
|
return workspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 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) {
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Con *const parent = con->parent;
|
||||||
|
if (con->type == CT_FLOATING_CON) {
|
||||||
|
if (orientation != HORIZ) {
|
||||||
|
/* up/down does not change floating containers */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
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. */
|
/* Show next workspace and focus appropriate container if possible. */
|
||||||
if (!workspace)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* Use descend_focused first to give higher priority to floating or
|
/* Use descend_focused first to give higher priority to floating or
|
||||||
* tiling fullscreen containers. */
|
* tiling fullscreen containers. */
|
||||||
Con *focus = con_descend_focused(workspace);
|
Con *focus = con_descend_focused(next);
|
||||||
if (focus->fullscreen_mode == CF_NONE) {
|
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
|
/* If descend_tiling returned a workspace then focus is either a
|
||||||
* floating container or the same workspace. */
|
* floating container or the same workspace. */
|
||||||
if (focus_tiling != workspace) {
|
if (focus_tiling != next) {
|
||||||
focus = focus_tiling;
|
focus = focus_tiling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
workspace_show(workspace);
|
workspace_show(next);
|
||||||
con_activate(focus);
|
con_activate(focus);
|
||||||
x_set_warp_to(&(focus->rect));
|
x_set_warp_to(&(focus->rect));
|
||||||
return true;
|
return;
|
||||||
}
|
} else if (next->type == CT_FLOATING_CON) {
|
||||||
|
/* Raise the floating window on top of other windows preserving relative
|
||||||
Con *parent = con->parent;
|
* stack order */
|
||||||
|
Con *parent = next->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 */
|
|
||||||
while (TAILQ_LAST(&(parent->floating_head), floating_head) != next) {
|
while (TAILQ_LAST(&(parent->floating_head), floating_head) != next) {
|
||||||
Con *last = TAILQ_LAST(&(parent->floating_head), floating_head);
|
Con *last = TAILQ_LAST(&(parent->floating_head), floating_head);
|
||||||
TAILQ_REMOVE(&(parent->floating_head), last, floating_windows);
|
TAILQ_REMOVE(&(parent->floating_head), last, floating_windows);
|
||||||
TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows);
|
TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace_show(con_get_workspace(next));
|
||||||
con_activate(con_descend_focused(next));
|
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 */
|
|
||||||
con_activate(con_descend_focused(next));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Changes focus in the given way (next/previous) and given orientation
|
* Get the previous / next sibling
|
||||||
* (horizontal/vertical).
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void tree_next(char way, orientation_t orientation) {
|
Con *get_tree_next_sibling(Con *con, position_t direction) {
|
||||||
_tree_next(focused, way, orientation,
|
Con *to_focus = (direction == BEFORE ? TAILQ_PREV(con, nodes_head, nodes)
|
||||||
config.focus_wrapping != FOCUS_WRAPPING_OFF);
|
: TAILQ_NEXT(con, nodes));
|
||||||
|
if (to_focus && con_fullscreen_permits_focusing(to_focus)) {
|
||||||
|
return to_focus;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
62
src/util.c
62
src/util.c
|
@ -53,6 +53,10 @@ Rect rect_sub(Rect a, Rect b) {
|
||||||
a.height - b.height};
|
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.
|
* Returns true if the name consists of only digits.
|
||||||
*
|
*
|
||||||
|
@ -159,7 +163,7 @@ void exec_i3_utility(char *name, char *argv[]) {
|
||||||
char buffer[BUFSIZ];
|
char buffer[BUFSIZ];
|
||||||
if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
|
if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
|
||||||
warn("could not read /proc/self/exe");
|
warn("could not read /proc/self/exe");
|
||||||
_exit(1);
|
_exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
dir = dirname(buffer);
|
dir = dirname(buffer);
|
||||||
sasprintf(&migratepath, "%s/%s", dir, name);
|
sasprintf(&migratepath, "%s/%s", dir, name);
|
||||||
|
@ -308,42 +312,6 @@ void i3_restart(bool forget_layout) {
|
||||||
/* not reached */
|
/* 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.
|
* 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.
|
* 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) {
|
orientation_t orientation_from_direction(direction_t direction) {
|
||||||
return (direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
20
src/window.c
20
src/window.c
|
@ -26,7 +26,7 @@ void window_free(i3Window *win) {
|
||||||
* given window.
|
* 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) {
|
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||||
DLOG("WM_CLASS not set.\n");
|
DLOG("WM_CLASS not set.\n");
|
||||||
FREE(prop);
|
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);
|
win->class_instance, win->class_class);
|
||||||
|
|
||||||
free(prop);
|
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.
|
* 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) {
|
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||||
DLOG("_NET_WM_NAME not specified, not changing\n");
|
DLOG("_NET_WM_NAME not specified, not changing\n");
|
||||||
FREE(prop);
|
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;
|
win->uses_net_wm_name = true;
|
||||||
|
|
||||||
free(prop);
|
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()).
|
* 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) {
|
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||||
DLOG("WM_NAME not set (_NET_WM_NAME is what you want anyways).\n");
|
DLOG("WM_NAME not set (_NET_WM_NAME is what you want anyways).\n");
|
||||||
FREE(prop);
|
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;
|
win->name_x_changed = true;
|
||||||
|
|
||||||
free(prop);
|
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
|
* 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) {
|
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||||
DLOG("WM_WINDOW_ROLE not set.\n");
|
DLOG("WM_WINDOW_ROLE not set.\n");
|
||||||
FREE(prop);
|
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);
|
LOG("WM_WINDOW_ROLE changed to \"%s\"\n", win->role);
|
||||||
|
|
||||||
free(prop);
|
free(prop);
|
||||||
if (!before_mgmt) {
|
|
||||||
run_assignments(win);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -197,18 +197,27 @@ void extract_workspace_names_from_bindings(void) {
|
||||||
while (*target == ' ' || *target == '\t')
|
while (*target == ' ' || *target == '\t')
|
||||||
target++;
|
target++;
|
||||||
/* We check if this is the workspace
|
/* 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",
|
* 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 */
|
* so we check before stripping the double quotes */
|
||||||
if (strncasecmp(target, "next", strlen("next")) == 0 ||
|
if (strncasecmp(target, "next", strlen("next")) == 0 ||
|
||||||
strncasecmp(target, "prev", strlen("prev")) == 0 ||
|
strncasecmp(target, "prev", strlen("prev")) == 0 ||
|
||||||
strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
|
strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
|
||||||
strncasecmp(target, "prev_on_output", strlen("prev_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, "back_and_forth", strlen("back_and_forth")) == 0 ||
|
||||||
strncasecmp(target, "current", strlen("current")) == 0)
|
strncasecmp(target, "current", strlen("current")) == 0)
|
||||||
continue;
|
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);
|
char *target_name = parse_string(&target, false);
|
||||||
if (target_name == NULL)
|
if (target_name == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
@ -983,11 +992,15 @@ void workspace_move_to_output(Con *ws, Output *output) {
|
||||||
bool used_assignment = false;
|
bool used_assignment = false;
|
||||||
struct Workspace_Assignment *assignment;
|
struct Workspace_Assignment *assignment;
|
||||||
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
||||||
|
bool attached;
|
||||||
|
int num;
|
||||||
if (!output_triggers_assignment(current_output, assignment)) {
|
if (!output_triggers_assignment(current_output, assignment)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/* check if this workspace is already attached to the tree */
|
/* check if this workspace's name or num is already attached to the tree */
|
||||||
if (get_existing_workspace_by_name(assignment->name) != NULL) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
9
src/x.c
9
src/x.c
|
@ -14,8 +14,6 @@
|
||||||
#define MAX(x, y) ((x) > (y) ? (x) : (y))
|
#define MAX(x, y) ((x) > (y) ? (x) : (y))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
xcb_window_t ewmh_window;
|
|
||||||
|
|
||||||
/* Stores the X11 window ID of the currently focused window */
|
/* Stores the X11 window ID of the currently focused window */
|
||||||
xcb_window_t focused_id = XCB_NONE;
|
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_dest->con = state_src->con;
|
||||||
state_src->con = NULL;
|
state_src->con = NULL;
|
||||||
|
|
||||||
Rect zero = {0, 0, 0, 0};
|
if (rect_equals(state_dest->window_rect, (Rect){0, 0, 0, 0})) {
|
||||||
if (memcmp(&(state_dest->window_rect), &(zero), sizeof(Rect)) == 0) {
|
|
||||||
memcpy(&(state_dest->window_rect), &(state_src->window_rect), sizeof(Rect));
|
memcpy(&(state_dest->window_rect), &(state_src->window_rect), sizeof(Rect));
|
||||||
DLOG("COPYING RECT\n");
|
DLOG("COPYING RECT\n");
|
||||||
}
|
}
|
||||||
|
@ -929,7 +926,7 @@ void x_push_node(Con *con) {
|
||||||
bool fake_notify = false;
|
bool fake_notify = false;
|
||||||
/* Set new position if rect changed (and if height > 0) or if the pixmap
|
/* Set new position if rect changed (and if height > 0) or if the pixmap
|
||||||
* needs to be recreated */
|
* 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)) {
|
rect.height > 0)) {
|
||||||
/* We first create the new pixmap, then render to it, set it as the
|
/* We first create the new pixmap, then render to it, set it as the
|
||||||
* background and only afterwards change the window size. This reduces
|
* 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 */
|
/* dito, but for child windows */
|
||||||
if (con->window != NULL &&
|
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",
|
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);
|
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);
|
xcb_set_window_rect(conn, con->window->id, con->window_rect);
|
||||||
|
|
|
@ -84,7 +84,7 @@ static void query_screens(xcb_connection_t *conn) {
|
||||||
|
|
||||||
if (num_screens == 0) {
|
if (num_screens == 0) {
|
||||||
ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
|
ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
|
||||||
exit(0);
|
exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1230,7 +1230,7 @@ sub create_layout {
|
||||||
|
|
||||||
$r = $r . '{"swallows": [{';
|
$r = $r . '{"swallows": [{';
|
||||||
$r = $r . '"class": "^' . "$char" . '$"';
|
$r = $r . '"class": "^' . "$char" . '$"';
|
||||||
$r = $r . '}]},';
|
$r = $r . '}]}' . ($depth == 0 ? "\n" : ',');
|
||||||
} else {
|
} else {
|
||||||
die "Could not understand $char";
|
die "Could not understand $char";
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,13 @@ my $bottom = open_window;
|
||||||
# end sleeping for half a second to make sure i3 reacted
|
# end sleeping for half a second to make sure i3 reacted
|
||||||
#
|
#
|
||||||
sub focus_after {
|
sub focus_after {
|
||||||
my $msg = shift;
|
my ($msg, $win_id) = @_;
|
||||||
|
|
||||||
|
if (defined($win_id)) {
|
||||||
|
cmd "[id=$win_id] $msg";
|
||||||
|
} else {
|
||||||
cmd $msg;
|
cmd $msg;
|
||||||
|
}
|
||||||
return $x->input_focus;
|
return $x->input_focus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +54,14 @@ is($focus, $mid->id, "Middle window focused");
|
||||||
$focus = focus_after('focus up');
|
$focus = focus_after('focus up');
|
||||||
is($focus, $top->id, "Top window focused");
|
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
|
# Test focus wrapping
|
||||||
#####################################################################
|
#####################################################################
|
||||||
|
|
|
@ -53,6 +53,7 @@ my $expected = {
|
||||||
name => 'root',
|
name => 'root',
|
||||||
orientation => $ignore,
|
orientation => $ignore,
|
||||||
type => 'root',
|
type => 'root',
|
||||||
|
window_type => undef,
|
||||||
id => $ignore,
|
id => $ignore,
|
||||||
rect => $ignore,
|
rect => $ignore,
|
||||||
deco_rect => $ignore,
|
deco_rect => $ignore,
|
||||||
|
|
|
@ -125,4 +125,22 @@ is_deeply(\@names, [ '3' ], 'i3 starts on workspace 3');
|
||||||
|
|
||||||
exit_gracefully($pid);
|
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;
|
done_testing;
|
||||||
|
|
|
@ -171,6 +171,53 @@ cmd 'restart';
|
||||||
cmd 'workspace back_and_forth';
|
cmd 'workspace back_and_forth';
|
||||||
is(focused_ws, '5: foo', 'workspace 5 focused after restart');
|
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);
|
exit_gracefully($pid);
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -63,6 +63,7 @@ my $bar_config = $i3->get_bar_config($bar_id)->recv;
|
||||||
is($bar_config->{status_command}, 'i3status --foo', 'status_command correct');
|
is($bar_config->{status_command}, 'i3status --foo', 'status_command correct');
|
||||||
ok(!$bar_config->{verbose}, 'verbose off by default');
|
ok(!$bar_config->{verbose}, 'verbose off by default');
|
||||||
ok($bar_config->{workspace_buttons}, 'workspace buttons enabled per 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');
|
ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled per default');
|
||||||
is($bar_config->{mode}, 'dock', 'dock mode by default');
|
is($bar_config->{mode}, 'dock', 'dock mode by default');
|
||||||
is($bar_config->{position}, 'bottom', 'position bottom by default');
|
is($bar_config->{position}, 'bottom', 'position bottom by default');
|
||||||
|
@ -102,6 +103,7 @@ bar {
|
||||||
mode dock
|
mode dock
|
||||||
font Terminus
|
font Terminus
|
||||||
workspace_buttons no
|
workspace_buttons no
|
||||||
|
workspace_min_width 30
|
||||||
binding_mode_indicator no
|
binding_mode_indicator no
|
||||||
verbose yes
|
verbose yes
|
||||||
socket_path /tmp/foobar
|
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');
|
is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
|
||||||
ok($bar_config->{verbose}, 'verbose on');
|
ok($bar_config->{verbose}, 'verbose on');
|
||||||
ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled');
|
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');
|
ok(!$bar_config->{binding_mode_indicator}, 'mode indicator disabled');
|
||||||
is($bar_config->{mode}, 'dock', 'dock mode');
|
is($bar_config->{mode}, 'dock', 'dock mode');
|
||||||
is($bar_config->{position}, 'top', 'position top');
|
is($bar_config->{position}, 'top', 'position top');
|
||||||
|
|
|
@ -532,5 +532,21 @@ is(scalar @{get_ws_content($ws)}, 0, 'no window in workspace');
|
||||||
cmd 'scratchpad show';
|
cmd 'scratchpad show';
|
||||||
is($x->input_focus, $window->id, 'scratchpad window shown');
|
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;
|
done_testing;
|
||||||
|
|
|
@ -149,6 +149,22 @@ send_net_active_window($scratch->id, 'pager');
|
||||||
|
|
||||||
is($x->input_focus, $scratch->id, 'scratchpad window is shown');
|
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
|
# Verify that the _NET_ACTIVE_WINDOW property is updated on the root window
|
||||||
# correctly.
|
# correctly.
|
||||||
|
|
|
@ -733,7 +733,7 @@ EOT
|
||||||
$expected = <<'EOT';
|
$expected = <<'EOT';
|
||||||
cfg_bar_start()
|
cfg_bar_start()
|
||||||
cfg_bar_output(LVDS-1)
|
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: (in file <stdin>)
|
||||||
ERROR: CONFIG: Line 1: bar {
|
ERROR: CONFIG: Line 1: bar {
|
||||||
ERROR: CONFIG: Line 2: output LVDS-1
|
ERROR: CONFIG: Line 2: output LVDS-1
|
||||||
|
|
|
@ -21,9 +21,10 @@ use i3test;
|
||||||
|
|
||||||
my $tmp = fresh_workspace;
|
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 $first_win = open_window;
|
||||||
my $second_win = open_window;
|
my $second_win = open_window;
|
||||||
|
@ -50,9 +51,10 @@ cmd 'floating toggle';
|
||||||
# see if no window is in fullscreen mode
|
# see if no window is in fullscreen mode
|
||||||
is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after showing previously fullscreened scratchpad window');
|
is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after showing previously fullscreened scratchpad window');
|
||||||
|
|
||||||
########################################################################################
|
###############################################################################
|
||||||
# move a window to scratchpad, focus parent container, make it fullscreen, focus a child
|
# move a window to scratchpad, focus parent container, make it fullscreen,
|
||||||
########################################################################################
|
# focus a child
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
# make layout tabbed
|
# make layout tabbed
|
||||||
cmd 'layout tabbed';
|
cmd 'layout tabbed';
|
||||||
|
@ -72,9 +74,9 @@ cmd 'focus child';
|
||||||
# see if the window really is in fullscreen mode
|
# see if the window really is in fullscreen mode
|
||||||
is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after enabling fullscreen on parent');
|
is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after enabling fullscreen on parent');
|
||||||
|
|
||||||
##########################################################################
|
###############################################################################
|
||||||
# show a scratchpad window; no window should be in fullscreen mode anymore
|
# show a scratchpad window; no window should be in fullscreen mode anymore
|
||||||
##########################################################################
|
###############################################################################
|
||||||
|
|
||||||
# show the scratchpad window
|
# show the scratchpad window
|
||||||
cmd 'scratchpad show';
|
cmd 'scratchpad show';
|
||||||
|
@ -82,4 +84,29 @@ cmd 'scratchpad show';
|
||||||
# see if no window is in fullscreen mode
|
# 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');
|
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;
|
done_testing;
|
||||||
|
|
|
@ -24,6 +24,7 @@ use i3test;
|
||||||
|
|
||||||
my ($A, $B, $S, $M, $F, $source_ws, $target_ws, $ws);
|
my ($A, $B, $S, $M, $F, $source_ws, $target_ws, $ws);
|
||||||
my ($nodes, $focus);
|
my ($nodes, $focus);
|
||||||
|
my $__i3_scratch;
|
||||||
my $cmd_result;
|
my $cmd_result;
|
||||||
|
|
||||||
my $_NET_WM_STATE_REMOVE = 0;
|
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
|
# 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'.
|
# workspace, then 'S' ends up as a tiling container on 'M'.
|
||||||
|
# See issue: #2003
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
fresh_workspace;
|
fresh_workspace;
|
||||||
|
@ -401,6 +403,29 @@ is(@{$nodes}, 2, 'there is a window and a container with the contents of the ori
|
||||||
is($nodes->[0]->{window}, $M->{id}, 'M remains the first window');
|
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');
|
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;
|
done_testing;
|
||||||
|
|
|
@ -19,10 +19,9 @@
|
||||||
use i3test i3_autostart => 0;
|
use i3test i3_autostart => 0;
|
||||||
|
|
||||||
my $first_lines = <<'EOT';
|
my $first_lines = <<'EOT';
|
||||||
set $workspace1 workspace number 1
|
|
||||||
set $workspace0 workspace eggs
|
set $workspace0 workspace eggs
|
||||||
|
|
||||||
bindsym Mod4+1 $workspace1
|
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||||
EOT
|
EOT
|
||||||
|
|
||||||
# Intentionally don't add a trailing newline for the last line since this is
|
# Intentionally don't add a trailing newline for the last line since this is
|
||||||
|
|
|
@ -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[2]->id, 'window 2 focused after tiling killed');
|
||||||
kill_and_confirm_focus($windows[3]->id, 'window 3 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;
|
done_testing;
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -12,7 +12,7 @@ use v5.10;
|
||||||
use autodie;
|
use autodie;
|
||||||
use lib 'testcases/lib';
|
use lib 'testcases/lib';
|
||||||
use i3test::Util qw(slurp);
|
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.
|
# Lintian complains if we don’t set a vendor.
|
||||||
use Lintian::Data;
|
use Lintian::Data;
|
||||||
|
|
Loading…
Reference in New Issue