Merge branch 'next' into master
This commit is contained in:
commit
53718d354b
|
@ -31,7 +31,7 @@ install:
|
|||
script:
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-safe-wrappers.sh
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-formatting.sh
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'autoreconf -fi && mkdir -p build && cd build && (../configure || (cat config.log; false)) && make -j CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror"'
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'autoreconf -fi && mkdir -p build && cd build && (../configure || (cat config.log; false)) && make -j CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common"'
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-spelling.pl
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} ./travis/run-tests.sh
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/debian-build.sh deb/debian-amd64/DIST
|
||||
|
|
|
@ -1 +1 @@
|
|||
4.17.1-non-git
|
||||
4.18-non-git
|
||||
|
|
|
@ -118,7 +118,7 @@ EXTRA_DIST = \
|
|||
I3_VERSION \
|
||||
LICENSE \
|
||||
PACKAGE-MAINTAINER \
|
||||
RELEASE-NOTES-4.17.1 \
|
||||
RELEASE-NOTES-4.18 \
|
||||
generate-command-parser.pl \
|
||||
parser-specs/commands.spec \
|
||||
parser-specs/config.spec \
|
||||
|
@ -503,6 +503,7 @@ i3_SOURCES = \
|
|||
include/con.h \
|
||||
include/data.h \
|
||||
include/display_version.h \
|
||||
include/drag.h \
|
||||
include/ewmh.h \
|
||||
include/fake_outputs.h \
|
||||
include/floating.h \
|
||||
|
@ -548,6 +549,7 @@ i3_SOURCES = \
|
|||
src/config_directives.c \
|
||||
src/config_parser.c \
|
||||
src/display_version.c \
|
||||
src/drag.c \
|
||||
src/ewmh.c \
|
||||
src/fake_outputs.c \
|
||||
src/floating.c \
|
||||
|
|
|
@ -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.
|
||||
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([i3], [4.17.1], [https://github.com/i3/i3/issues])
|
||||
AC_INIT([i3], [4.18], [https://github.com/i3/i3/issues])
|
||||
# For AX_EXTEND_SRCDIR
|
||||
AX_ENABLE_BUILDDIR
|
||||
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
|
||||
|
@ -29,7 +29,7 @@ AS_VAR_IF([_cv_gnu_make_command], [""], [AC_MSG_ERROR([the i3 Makefile.am requir
|
|||
|
||||
AX_EXTEND_SRCDIR
|
||||
|
||||
AS_IF([test -d ${srcdir}/.git],
|
||||
AS_IF([test -e ${srcdir}/.git],
|
||||
[
|
||||
VERSION="$(git -C ${srcdir} describe --tags --abbrev=0)"
|
||||
I3_VERSION="$(git -C ${srcdir} describe --tags --always) ($(git -C ${srcdir} rev-list --format=%cd --date=short -n1 $(git rev-parse HEAD) | tail -n1), branch \\\"$(git -C ${srcdir} describe --tags --always --all | sed s:heads/::)\\\")"
|
||||
|
@ -72,7 +72,8 @@ AC_CHECK_TYPES([mode_t, off_t, pid_t, size_t, ssize_t], , [AC_MSG_FAILURE([canno
|
|||
AC_FUNC_FORK
|
||||
AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK
|
||||
AC_FUNC_STRNLEN
|
||||
AC_CHECK_FUNCS([atexit dup2 ftruncate getcwd gettimeofday localtime_r memchr memset mkdir rmdir setlocale socket strcasecmp strchr strdup strerror strncasecmp strndup strrchr strspn strstr strtol strtoul], , [AC_MSG_FAILURE([cannot find the $ac_func function, which i3 requires])])
|
||||
AC_CHECK_FUNCS([atexit dup2 ftruncate getcwd gettimeofday localtime_r memchr memset mkdir rmdir setlocale socket strcasecmp strchr strdup strerror strncasecmp strrchr strspn strstr strtol strtoul], , [AC_MSG_FAILURE([cannot find the $ac_func function, which i3 requires])])
|
||||
AC_REPLACE_FUNCS([mkdirp strndup])
|
||||
|
||||
# Checks for libraries.
|
||||
|
||||
|
@ -83,8 +84,11 @@ AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_ru
|
|||
|
||||
AC_SEARCH_LIBS([shm_open], [rt], [], [], [-pthread])
|
||||
|
||||
AC_SEARCH_LIBS([iconv_open], [iconv], ,
|
||||
AC_SEARCH_LIBS([libiconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])]))
|
||||
AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <iconv.h>], [iconv_open(0, 0)])], ,
|
||||
[LIBS="-liconv $LIBS"
|
||||
AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <iconv.h>], [iconv_open(0, 0)])], ,
|
||||
[AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])])]
|
||||
)
|
||||
|
||||
AX_PTHREAD
|
||||
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
i3-wm (4.17.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Fri, 30 Aug 2019 23:06:40 +0200
|
||||
|
||||
i3-wm (4.17.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Fri, 30 Aug 2019 23:06:40 +0200
|
||||
|
||||
i3-wm (4.17-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
|
|
@ -189,7 +189,7 @@ separator_block_width::
|
|||
is 9 pixels), since the separator line is drawn in the middle.
|
||||
markup::
|
||||
A string that indicates how the text of the block should be parsed. Set to
|
||||
+"pango"+ to use https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup].
|
||||
+"pango"+ to use https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup].
|
||||
Set to +"none"+ to not use any markup (default). Pango markup only works
|
||||
if you use a pango font.
|
||||
|
||||
|
|
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
|
||||
following properties:
|
||||
|
||||
id (integer)::
|
||||
The internal ID (actually a C pointer value) of this container. Do not
|
||||
make any assumptions about it. You can use it to (re-)identify and
|
||||
address containers when talking to i3.
|
||||
num (integer)::
|
||||
The logical number of the workspace. Corresponds to the command
|
||||
to switch to this workspace. For named workspaces, this will be -1.
|
||||
|
@ -344,12 +348,19 @@ window (integer)::
|
|||
containers. This ID corresponds to what xwininfo(1) and other
|
||||
X11-related tools display (usually in hex).
|
||||
window_properties (map)::
|
||||
X11 window properties title, instance, class, window_role and transient_for.
|
||||
This optional field contains all available X11 window properties from the
|
||||
following list: *title*, *instance*, *class*, *window_role* and *transient_for*.
|
||||
window_type (string)::
|
||||
The window type (_NET_WM_WINDOW_TYPE). Possible values are undefined, normal,
|
||||
dialog, utility, toolbar, splash, menu, dropdown_menu, popup_menu, tooltip and
|
||||
notification.
|
||||
urgent (bool)::
|
||||
Whether this container (window, split container, floating container or
|
||||
workspace) has the urgency hint set, directly or indirectly. All parent
|
||||
containers up until the workspace container will be marked urgent if they
|
||||
have at least one urgent child.
|
||||
marks (array of string)::
|
||||
List of marks assigned to container
|
||||
focused (bool)::
|
||||
Whether this container is currently focused.
|
||||
focus (array of integer)::
|
||||
|
@ -357,6 +368,14 @@ focus (array of integer)::
|
|||
order. Traversing the tree by following the first entry in this array
|
||||
will result in eventually reaching the one node with +focused+ set to
|
||||
true.
|
||||
fullscreen_mode (integer)::
|
||||
Whether this container is in fullscreen state or not.
|
||||
Possible values are
|
||||
+0+ (no fullscreen),
|
||||
+1+ (fullscreened on output) or
|
||||
+2+ (fullscreened globally).
|
||||
Note that all workspaces are considered fullscreened on their respective output.
|
||||
|
||||
nodes (array of node)::
|
||||
The tiling (i.e. non-floating) child containers of this node.
|
||||
floating_nodes (array of node)::
|
||||
|
|
|
@ -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
|
||||
------------------------------------------------
|
||||
|
||||
The valid criteria are the same as those for commands, see <<command_criteria>>. Only config
|
||||
directives with a command equivalent can be executed at runtime, see <<list_of_commands>>.
|
||||
The valid criteria are the same as those for commands, see <<command_criteria>>. Only
|
||||
commands can be executed at runtime, not config directives, see <<list_of_commands>>.
|
||||
|
||||
[[no_focus]]
|
||||
=== Don't focus window upon opening
|
||||
|
@ -1005,7 +1005,8 @@ programs to get information from i3, such as the current workspaces
|
|||
(to display a workspace bar), and to control i3.
|
||||
|
||||
The IPC socket is enabled by default and will be created in
|
||||
+/tmp/i3-%u.XXXXXX/ipc-socket.%p+ where +%u+ is your UNIX username, +%p+ is
|
||||
+$XDG_RUNTIME_DIR/i3/ipc-socket.%p+ if the directory is available, falling back
|
||||
to +/tmp/i3-%u.XXXXXX/ipc-socket.%p+, where +%u+ is your UNIX username, +%p+ is
|
||||
the PID of i3 and XXXXXX is a string of random characters from the portable
|
||||
filename character set (see mkdtemp(3)).
|
||||
|
||||
|
@ -1105,9 +1106,14 @@ If you want the focus to *always* wrap and you are aware of using +focus
|
|||
parent+ to switch to different containers, you can instead set +focus_wrapping+
|
||||
to the value +force+.
|
||||
|
||||
To restrict focus inside the current workspace set +focus_wrapping+ to the
|
||||
value +workspace+. You will need to use +focus parent+ until a workspace is
|
||||
selected to switch to a different workspace using the focus commands (the
|
||||
+workspace+ command will still work as expected).
|
||||
|
||||
*Syntax*:
|
||||
---------------------------
|
||||
focus_wrapping yes|no|force
|
||||
focus_wrapping yes|no|force|workspace
|
||||
|
||||
# Legacy syntax, equivalent to "focus_wrapping force"
|
||||
force_focus_wrapping yes
|
||||
|
@ -1625,6 +1631,35 @@ bar {
|
|||
}
|
||||
------------------------
|
||||
|
||||
=== Minimal width for workspace buttons
|
||||
|
||||
By default, the width a workspace button is determined by the width of the text
|
||||
showing the workspace name. If the name is too short (say, one letter), then the
|
||||
workspace button may look too small.
|
||||
|
||||
This option specifies the minimum width for workspace buttons. If the name of
|
||||
a workspace is too short to cover the button, an additional padding is added on
|
||||
both sides of the button so that the text is centered.
|
||||
|
||||
The default value of zero means that no additional padding is added.
|
||||
|
||||
The setting also applies to the current binding mode indicator.
|
||||
|
||||
Note that the specified pixels refer to logical pixels, which may translate
|
||||
into more pixels on HiDPI displays.
|
||||
|
||||
*Syntax*:
|
||||
------------------------
|
||||
workspace_min_width <px> [px]
|
||||
------------------------
|
||||
|
||||
*Example*:
|
||||
------------------------
|
||||
bar {
|
||||
workspace_min_width 40
|
||||
}
|
||||
------------------------
|
||||
|
||||
=== Strip workspace numbers/name
|
||||
|
||||
Specifies whether workspace numbers should be displayed within the workspace
|
||||
|
@ -1867,9 +1902,6 @@ The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are
|
|||
actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
|
||||
information on how to use them.
|
||||
|
||||
Note that config directives listed under <<configuring>> cannot be changed at runtime
|
||||
unless they happen to have a command equivalent.
|
||||
|
||||
[[exec]]
|
||||
=== Executing applications (exec)
|
||||
|
||||
|
@ -1954,6 +1986,7 @@ bindsym $mod+h split horizontal
|
|||
bindsym $mod+t split toggle
|
||||
-------------------------------
|
||||
|
||||
[[manipulating_layout]]
|
||||
=== Manipulating layout
|
||||
|
||||
Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+
|
||||
|
@ -2024,6 +2057,12 @@ parent::
|
|||
child::
|
||||
The opposite of +focus parent+, sets the focus to the last focused
|
||||
child container.
|
||||
next|prev::
|
||||
Automatically sets focus to the adjacent container. If +sibling+ is
|
||||
specified, the command will focus the exact sibling container,
|
||||
including non-leaf containers like split containers. Otherwise, it is
|
||||
an automatic version of +focus left|right|up|down+ in the orientation
|
||||
of the parent container.
|
||||
floating::
|
||||
Sets focus to the last focused floating container.
|
||||
tiling::
|
||||
|
@ -2039,6 +2078,7 @@ output::
|
|||
<criteria> focus
|
||||
focus left|right|down|up
|
||||
focus parent|child|floating|tiling|mode_toggle
|
||||
focus next|prev [sibling]
|
||||
focus output left|right|up|down|primary|<output>
|
||||
----------------------------------------------
|
||||
|
||||
|
@ -2198,7 +2238,7 @@ See <<move_to_outputs>> for how to move a container/workspace to a different
|
|||
RandR output.
|
||||
|
||||
Workspace names are parsed as
|
||||
https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup]
|
||||
https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup]
|
||||
by i3bar.
|
||||
|
||||
[[back_and_forth]]
|
||||
|
@ -2492,7 +2532,7 @@ unmark irssi
|
|||
By default, i3 will simply print the X11 window title. Using +title_format+,
|
||||
this can be customized by setting the format to the desired output. This
|
||||
directive supports
|
||||
https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup]
|
||||
https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup]
|
||||
and the following placeholders which will be replaced:
|
||||
|
||||
+%title+::
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
} while (0)
|
||||
|
||||
#include "xcb.h"
|
||||
xcb_visualtype_t *visual_type = NULL;
|
||||
#include "libi3.h"
|
||||
|
||||
#define TEXT_PADDING logical_px(4)
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
* to i3.
|
||||
*
|
||||
*/
|
||||
#include "libi3.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdlib.h>
|
||||
|
@ -27,6 +25,9 @@
|
|||
#include <xcb/xcb_event.h>
|
||||
#include <xcb/xcb_keysyms.h>
|
||||
|
||||
xcb_visualtype_t *visual_type = NULL;
|
||||
#include "libi3.h"
|
||||
|
||||
#include <X11/keysym.h>
|
||||
|
||||
#include "keysym2ucs.h"
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
* when the user has an error in their configuration file.
|
||||
*
|
||||
*/
|
||||
#include "libi3.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
@ -32,6 +30,9 @@
|
|||
#include <xcb/randr.h>
|
||||
#include <xcb/xcb_cursor.h>
|
||||
|
||||
xcb_visualtype_t *visual_type = NULL;
|
||||
#include "libi3.h"
|
||||
|
||||
#define SN_API_NOT_YET_FROZEN 1
|
||||
#include <libsn/sn-launchee.h>
|
||||
|
||||
|
@ -87,7 +88,7 @@ void verboselog(char *fmt, ...) {
|
|||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
vfprintf(stdout, fmt, args);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
|
@ -282,11 +283,12 @@ static xcb_rectangle_t get_window_position(void) {
|
|||
xcb_randr_get_screen_resources_current_reply_t *res = NULL;
|
||||
|
||||
if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL) {
|
||||
DLOG("Could not determine the primary output.\n");
|
||||
LOG("Could not determine the primary output.\n");
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
|
||||
LOG("Could not query screen resources.\n");
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
|
@ -294,20 +296,24 @@ static xcb_rectangle_t get_window_position(void) {
|
|||
xcb_randr_get_output_info_reply(conn,
|
||||
xcb_randr_get_output_info(conn, primary->output, res->config_timestamp),
|
||||
NULL);
|
||||
if (output == NULL || output->crtc == XCB_NONE)
|
||||
if (output == NULL || output->crtc == XCB_NONE) {
|
||||
LOG("Could not query primary screen.\n");
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
xcb_randr_get_crtc_info_reply_t *crtc =
|
||||
xcb_randr_get_crtc_info_reply(conn,
|
||||
xcb_randr_get_crtc_info(conn, output->crtc, res->config_timestamp),
|
||||
NULL);
|
||||
if (crtc == NULL)
|
||||
if (crtc == NULL) {
|
||||
LOG("Could not get CRTC.\n");
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
DLOG("Found primary output on position x = %i / y = %i / w = %i / h = %i.\n",
|
||||
LOG("Found primary output on position x = %i / y = %i / w = %i / h = %i.\n",
|
||||
crtc->x, crtc->y, crtc->width, crtc->height);
|
||||
if (crtc->width == 0 || crtc->height == 0) {
|
||||
DLOG("Primary output is not active, ignoring it.\n");
|
||||
LOG("Primary output is not active, ignoring it.\n");
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
|
@ -379,10 +385,11 @@ int main(int argc, char *argv[]) {
|
|||
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
|
||||
switch (o) {
|
||||
case 'v':
|
||||
free(pattern);
|
||||
printf("i3-nagbar " I3_VERSION "\n");
|
||||
return 0;
|
||||
case 'f':
|
||||
FREE(pattern);
|
||||
free(pattern);
|
||||
pattern = sstrdup(optarg);
|
||||
break;
|
||||
case 'm':
|
||||
|
@ -393,6 +400,7 @@ int main(int argc, char *argv[]) {
|
|||
bar_type = (strcasecmp(optarg, "warning") == 0 ? TYPE_WARNING : TYPE_ERROR);
|
||||
break;
|
||||
case 'h':
|
||||
free(pattern);
|
||||
printf("i3-nagbar " I3_VERSION "\n");
|
||||
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
|
||||
return 0;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
typedef struct rect_t rect;
|
||||
|
||||
struct ev_loop *main_loop;
|
||||
extern struct ev_loop *main_loop;
|
||||
|
||||
struct rect_t {
|
||||
int x;
|
||||
|
@ -82,7 +82,7 @@ struct status_block {
|
|||
blocks;
|
||||
};
|
||||
|
||||
TAILQ_HEAD(statusline_head, status_block)
|
||||
extern TAILQ_HEAD(statusline_head, status_block)
|
||||
statusline_head;
|
||||
|
||||
#include "child.h"
|
||||
|
|
|
@ -52,6 +52,7 @@ typedef struct config_t {
|
|||
struct xcb_color_strings_t colors;
|
||||
bool disable_binding_mode_indicator;
|
||||
bool disable_ws;
|
||||
int ws_min_width;
|
||||
bool strip_ws_numbers;
|
||||
bool strip_ws_name;
|
||||
char *bar_id;
|
||||
|
@ -73,7 +74,7 @@ typedef struct config_t {
|
|||
S_SHOW = 1 } hidden_state;
|
||||
} config_t;
|
||||
|
||||
config_t config;
|
||||
extern config_t config;
|
||||
|
||||
/**
|
||||
* Start parsing the received bar configuration JSON string
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
/* Name of current binding mode and its render width */
|
||||
struct mode {
|
||||
i3String *name;
|
||||
int width;
|
||||
int name_width;
|
||||
};
|
||||
|
||||
typedef struct mode mode;
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
typedef struct i3_output i3_output;
|
||||
|
||||
SLIST_HEAD(outputs_head, i3_output);
|
||||
struct outputs_head* outputs;
|
||||
extern struct outputs_head* outputs;
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
|
|
|
@ -18,6 +18,9 @@ struct trayclient {
|
|||
bool mapped; /* Whether this window is mapped */
|
||||
int xe_version; /* The XEMBED version supported by the client */
|
||||
|
||||
char *class_class;
|
||||
char *class_instance;
|
||||
|
||||
TAILQ_ENTRY(trayclient)
|
||||
tailq; /* Pointer for the TAILQ-Macro */
|
||||
};
|
||||
|
|
|
@ -30,6 +30,7 @@ void parse_workspaces_json(char *json);
|
|||
void free_workspaces(void);
|
||||
|
||||
struct i3_ws {
|
||||
uintptr_t id; /* Workspace ID - C pointer to a workspace container */
|
||||
int num; /* The internal number of the ws */
|
||||
char *canonical_name; /* The true name of the ws according to the ipc */
|
||||
i3String *name; /* The name of the ws that is displayed on the bar */
|
||||
|
|
|
@ -53,7 +53,7 @@ struct xcb_color_strings_t {
|
|||
typedef struct xcb_colors_t xcb_colors_t;
|
||||
|
||||
/* Cached width of the custom separator if one was set */
|
||||
int separator_symbol_width;
|
||||
extern int separator_symbol_width;
|
||||
|
||||
/*
|
||||
* Early initialization of the connection to X11: Everything which does not
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
config_t config;
|
||||
static char *cur_key;
|
||||
static bool parsing_bindings;
|
||||
static bool parsing_tray_outputs;
|
||||
|
@ -345,6 +346,12 @@ static int config_integer_cb(void *params_, long long val) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "workspace_min_width")) {
|
||||
DLOG("workspace_min_width = %lld\n", val);
|
||||
config.ws_min_width = val;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <getopt.h>
|
||||
#include <glob.h>
|
||||
|
||||
struct ev_loop *main_loop;
|
||||
|
||||
/*
|
||||
* Having verboselog(), errorlog() and debuglog() is necessary when using libi3.
|
||||
*
|
||||
|
@ -92,13 +94,7 @@ static void sig_cb(struct ev_loop *loop, ev_signal *watcher, int revents) {
|
|||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int opt;
|
||||
int option_index = 0;
|
||||
char *socket_path = getenv("I3SOCK");
|
||||
if (socket_path != NULL) {
|
||||
socket_path = sstrdup(socket_path);
|
||||
}
|
||||
char *i3_default_sock_path = "/tmp/i3-ipc.sock";
|
||||
char *socket_path = NULL;
|
||||
|
||||
/* Initialize the standard config to use 0 as default */
|
||||
memset(&config, '\0', sizeof(config_t));
|
||||
|
@ -112,6 +108,8 @@ int main(int argc, char **argv) {
|
|||
{"verbose", no_argument, 0, 'V'},
|
||||
{NULL, 0, 0, 0}};
|
||||
|
||||
int opt;
|
||||
int option_index = 0;
|
||||
while ((opt = getopt_long(argc, argv, "b:s:thvV", long_opt, &option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
|
@ -144,10 +142,17 @@ int main(int argc, char **argv) {
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
main_loop = ev_default_loop(0);
|
||||
|
||||
main_loop = ev_default_loop(0); /* needed in init_xcb_early */
|
||||
char *atom_sock_path = init_xcb_early();
|
||||
|
||||
/* Select a socket_path if the user hasn't specified one */
|
||||
if (socket_path == NULL) {
|
||||
socket_path = getenv("I3SOCK");
|
||||
if (socket_path != NULL) {
|
||||
socket_path = sstrdup(socket_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (socket_path == NULL) {
|
||||
socket_path = atom_sock_path;
|
||||
} else {
|
||||
|
@ -155,8 +160,9 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
|
||||
if (socket_path == NULL) {
|
||||
char *i3_default_sock_path = "/tmp/i3-ipc.sock";
|
||||
ELOG("No socket path specified, default to %s\n", i3_default_sock_path);
|
||||
socket_path = expand_path(i3_default_sock_path);
|
||||
socket_path = sstrdup(i3_default_sock_path);
|
||||
}
|
||||
|
||||
init_dpi();
|
||||
|
|
|
@ -81,7 +81,7 @@ static int mode_end_map_cb(void *params_) {
|
|||
params->mode->name = i3string_from_utf8(params->name);
|
||||
i3string_set_markup(params->mode->name, params->pango_markup);
|
||||
/* Save its rendered width */
|
||||
params->mode->width = predict_text_width(params->mode->name);
|
||||
params->mode->name_width = predict_text_width(params->mode->name);
|
||||
|
||||
DLOG("Got mode change: %s\n", i3string_as_utf8(params->mode->name));
|
||||
FREE(params->cur_key);
|
||||
|
|
|
@ -252,6 +252,7 @@ static yajl_callbacks outputs_callbacks = {
|
|||
.yajl_end_map = outputs_end_map_cb,
|
||||
};
|
||||
|
||||
struct outputs_head *outputs;
|
||||
/*
|
||||
* 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) {
|
||||
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
|
||||
|
||||
if (!strcmp(params->cur_key, "id")) {
|
||||
params->workspaces_walk->id = val;
|
||||
FREE(params->cur_key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(params->cur_key, "num")) {
|
||||
params->workspaces_walk->num = (int)val;
|
||||
FREE(params->cur_key);
|
||||
|
|
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 */
|
||||
static const int sep_voff_px = 4;
|
||||
|
||||
/* Cached width of the custom separator if one was set */
|
||||
int separator_symbol_width;
|
||||
|
||||
int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
|
||||
xcb_generic_error_t *err;
|
||||
if ((err = xcb_request_check(xcb_connection, cookie)) != NULL) {
|
||||
|
@ -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).
|
||||
* 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;
|
||||
|
||||
TAILQ_FOREACH(ws_walk, walk->workspaces, tailq) {
|
||||
int w = 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + ws_walk->name_width;
|
||||
int w = predict_button_width(ws_walk->name_width);
|
||||
if (x >= workspace_width && x <= workspace_width + w)
|
||||
clicked_ws = ws_walk;
|
||||
if (ws_walk->visible)
|
||||
|
@ -683,6 +695,32 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
|
|||
}
|
||||
}
|
||||
|
||||
static int strcasecmp_nullable(const char *a, const char *b) {
|
||||
if (a == b) {
|
||||
return 0;
|
||||
}
|
||||
if (a == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (b == NULL) {
|
||||
return 1;
|
||||
}
|
||||
return strcasecmp(a, b);
|
||||
}
|
||||
|
||||
/*
|
||||
* Comparison function to sort trayclients in ascending alphanumeric order
|
||||
* according to their class.
|
||||
*
|
||||
*/
|
||||
static int reorder_trayclients_cmp(const void *_a, const void *_b) {
|
||||
trayclient *a = *((trayclient **)_a);
|
||||
trayclient *b = *((trayclient **)_b);
|
||||
|
||||
int result = strcasecmp_nullable(a->class_class, b->class_class);
|
||||
return result != 0 ? result : strcasecmp_nullable(a->class_instance, b->class_instance);
|
||||
}
|
||||
|
||||
/*
|
||||
* Adjusts the size of the tray window and alignment of the tray clients by
|
||||
* configuring their respective x coordinates. To be called when mapping or
|
||||
|
@ -690,29 +728,108 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
|
|||
*
|
||||
*/
|
||||
static void configure_trayclients(void) {
|
||||
trayclient *trayclient;
|
||||
i3_output *output;
|
||||
SLIST_FOREACH(output, outputs, slist) {
|
||||
if (!output->active)
|
||||
if (!output->active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int clients = 0;
|
||||
TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
|
||||
if (!trayclient->mapped)
|
||||
continue;
|
||||
clients++;
|
||||
int count = 0;
|
||||
trayclient *client;
|
||||
TAILQ_FOREACH(client, output->trayclients, tailq) {
|
||||
if (client->mapped) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
DLOG("Configuring tray window %08x to x=%d\n",
|
||||
trayclient->win, output->rect.w - (clients * (icon_size + logical_px(config.tray_padding))));
|
||||
uint32_t x = output->rect.w - (clients * (icon_size + logical_px(config.tray_padding)));
|
||||
int idx = 0;
|
||||
trayclient **trayclients = smalloc(count * sizeof(trayclient *));
|
||||
TAILQ_FOREACH(client, output->trayclients, tailq) {
|
||||
if (client->mapped) {
|
||||
trayclients[idx++] = client;
|
||||
}
|
||||
}
|
||||
|
||||
qsort(trayclients, count, sizeof(trayclient *), reorder_trayclients_cmp);
|
||||
|
||||
uint32_t x = output->rect.w;
|
||||
for (idx = count; idx > 0; idx--) {
|
||||
x -= icon_size + logical_px(config.tray_padding);
|
||||
|
||||
DLOG("Configuring tray window %08x to x=%d\n", trayclients[idx - 1]->win, x);
|
||||
xcb_configure_window(xcb_connection,
|
||||
trayclient->win,
|
||||
trayclients[idx - 1]->win,
|
||||
XCB_CONFIG_WINDOW_X,
|
||||
&x);
|
||||
}
|
||||
|
||||
free(trayclients);
|
||||
}
|
||||
}
|
||||
|
||||
static trayclient *trayclient_and_output_from_window(xcb_window_t win, i3_output **output) {
|
||||
i3_output *o_walk;
|
||||
SLIST_FOREACH(o_walk, outputs, slist) {
|
||||
if (!o_walk->active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
trayclient *client;
|
||||
TAILQ_FOREACH(client, o_walk->trayclients, tailq) {
|
||||
if (client->win == win) {
|
||||
if (output) {
|
||||
*output = o_walk;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static trayclient *trayclient_from_window(xcb_window_t win) {
|
||||
return trayclient_and_output_from_window(win, NULL);
|
||||
}
|
||||
|
||||
static void trayclient_update_class(trayclient *client) {
|
||||
xcb_get_property_reply_t *prop = xcb_get_property_reply(
|
||||
conn,
|
||||
xcb_get_property_unchecked(
|
||||
xcb_connection,
|
||||
false,
|
||||
client->win,
|
||||
XCB_ATOM_WM_CLASS,
|
||||
XCB_ATOM_STRING,
|
||||
0,
|
||||
32),
|
||||
NULL);
|
||||
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||
DLOG("WM_CLASS not set.\n");
|
||||
free(prop);
|
||||
return;
|
||||
}
|
||||
|
||||
/* We cannot use asprintf here since this property contains two
|
||||
* null-terminated strings (for compatibility reasons). Instead, we
|
||||
* use strdup() on both strings */
|
||||
const size_t prop_length = xcb_get_property_value_length(prop);
|
||||
char *new_class = xcb_get_property_value(prop);
|
||||
const size_t class_class_index = strnlen(new_class, prop_length) + 1;
|
||||
|
||||
free(client->class_instance);
|
||||
free(client->class_class);
|
||||
|
||||
client->class_instance = sstrndup(new_class, prop_length);
|
||||
if (class_class_index < prop_length) {
|
||||
client->class_class = sstrndup(new_class + class_class_index, prop_length - class_class_index);
|
||||
} else {
|
||||
client->class_class = NULL;
|
||||
}
|
||||
DLOG("WM_CLASS changed to %s (instance), %s (class)\n", client->class_instance, client->class_class);
|
||||
|
||||
free(prop);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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. */
|
||||
xcb_change_save_set(xcb_connection, XCB_SET_MODE_INSERT, client);
|
||||
|
||||
trayclient *tc = smalloc(sizeof(trayclient));
|
||||
trayclient *tc = scalloc(1, sizeof(trayclient));
|
||||
tc->win = client;
|
||||
tc->xe_version = xe_version;
|
||||
tc->mapped = false;
|
||||
TAILQ_INSERT_TAIL(output_for_tray->trayclients, tc, tailq);
|
||||
trayclient_update_class(tc);
|
||||
|
||||
if (map_it) {
|
||||
DLOG("Mapping dock client\n");
|
||||
|
@ -875,27 +993,22 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
|||
static void handle_destroy_notify(xcb_destroy_notify_event_t *event) {
|
||||
DLOG("DestroyNotify for window = %08x, event = %08x\n", event->window, event->event);
|
||||
|
||||
i3_output *walk;
|
||||
SLIST_FOREACH(walk, outputs, slist) {
|
||||
if (!walk->active)
|
||||
continue;
|
||||
DLOG("checking output %s\n", walk->name);
|
||||
trayclient *trayclient;
|
||||
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
|
||||
if (trayclient->win != event->window) {
|
||||
continue;
|
||||
i3_output *output;
|
||||
trayclient *client = trayclient_and_output_from_window(event->window, &output);
|
||||
if (!client) {
|
||||
DLOG("WARNING: Could not find corresponding tray window.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
DLOG("Removing tray client with window ID %08x\n", event->window);
|
||||
TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
|
||||
FREE(trayclient);
|
||||
TAILQ_REMOVE(output->trayclients, client, tailq);
|
||||
free(client->class_class);
|
||||
free(client->class_instance);
|
||||
FREE(client);
|
||||
|
||||
/* Trigger an update, we now have more space for the statusline */
|
||||
configure_trayclients();
|
||||
draw_bars(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -906,25 +1019,18 @@ static void handle_destroy_notify(xcb_destroy_notify_event_t *event) {
|
|||
static void handle_map_notify(xcb_map_notify_event_t *event) {
|
||||
DLOG("MapNotify for window = %08x, event = %08x\n", event->window, event->event);
|
||||
|
||||
i3_output *walk;
|
||||
SLIST_FOREACH(walk, outputs, slist) {
|
||||
if (!walk->active)
|
||||
continue;
|
||||
DLOG("checking output %s\n", walk->name);
|
||||
trayclient *trayclient;
|
||||
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
|
||||
if (trayclient->win != event->window)
|
||||
continue;
|
||||
|
||||
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);
|
||||
trayclient *client = trayclient_from_window(event->window);
|
||||
if (!client) {
|
||||
DLOG("WARNING: Could not find corresponding tray window.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DLOG("Tray client mapped (window ID %08x). Adjusting tray.\n", event->window);
|
||||
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
|
||||
|
@ -934,62 +1040,41 @@ static void handle_map_notify(xcb_map_notify_event_t *event) {
|
|||
static void handle_unmap_notify(xcb_unmap_notify_event_t *event) {
|
||||
DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event);
|
||||
|
||||
i3_output *walk;
|
||||
SLIST_FOREACH(walk, outputs, slist) {
|
||||
if (!walk->active)
|
||||
continue;
|
||||
DLOG("checking output %s\n", walk->name);
|
||||
trayclient *trayclient;
|
||||
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
|
||||
if (trayclient->win != event->window)
|
||||
continue;
|
||||
trayclient *client = trayclient_from_window(event->window);
|
||||
if (!client) {
|
||||
DLOG("WARNING: Could not find corresponding tray window.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
DLOG("Tray client unmapped (window ID %08x). Adjusting tray.\n", event->window);
|
||||
trayclient->mapped = false;
|
||||
client->mapped = false;
|
||||
|
||||
/* Trigger an update, we now have more space for the statusline */
|
||||
configure_trayclients();
|
||||
draw_bars(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle PropertyNotify messages. Currently only the _XEMBED_INFO property is
|
||||
* handled, which tells us whether a dock client should be mapped or unmapped.
|
||||
* Handle PropertyNotify messages.
|
||||
*
|
||||
*/
|
||||
static void handle_property_notify(xcb_property_notify_event_t *event) {
|
||||
DLOG("PropertyNotify\n");
|
||||
if (event->atom == atoms[_XEMBED_INFO] &&
|
||||
event->state == XCB_PROPERTY_NEW_VALUE) {
|
||||
/* _XEMBED_INFO property tells us whether a dock client should be mapped or unmapped. */
|
||||
DLOG("xembed_info updated\n");
|
||||
trayclient *trayclient = NULL, *walk;
|
||||
i3_output *o_walk;
|
||||
SLIST_FOREACH(o_walk, outputs, slist) {
|
||||
if (!o_walk->active)
|
||||
continue;
|
||||
|
||||
TAILQ_FOREACH(walk, o_walk->trayclients, tailq) {
|
||||
if (walk->win != event->window)
|
||||
continue;
|
||||
trayclient = walk;
|
||||
break;
|
||||
}
|
||||
|
||||
if (trayclient)
|
||||
break;
|
||||
}
|
||||
if (!trayclient) {
|
||||
ELOG("PropertyNotify received for unknown window %08x\n",
|
||||
event->window);
|
||||
trayclient *client = trayclient_from_window(event->window);
|
||||
if (!client) {
|
||||
ELOG("PropertyNotify received for unknown window %08x\n", event->window);
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_get_property_cookie_t xembedc;
|
||||
xembedc = xcb_get_property_unchecked(xcb_connection,
|
||||
0,
|
||||
trayclient->win,
|
||||
client->win,
|
||||
atoms[_XEMBED_INFO],
|
||||
XCB_GET_PROPERTY_TYPE_ANY,
|
||||
0,
|
||||
|
@ -1009,14 +1094,19 @@ static void handle_property_notify(xcb_property_notify_event_t *event) {
|
|||
DLOG("xembed flags = %d\n", xembed[1]);
|
||||
bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
|
||||
DLOG("map state now %d\n", map_it);
|
||||
if (trayclient->mapped && !map_it) {
|
||||
if (client->mapped && !map_it) {
|
||||
/* need to unmap the window */
|
||||
xcb_unmap_window(xcb_connection, trayclient->win);
|
||||
} else if (!trayclient->mapped && map_it) {
|
||||
xcb_unmap_window(xcb_connection, client->win);
|
||||
} else if (!client->mapped && map_it) {
|
||||
/* need to map the window */
|
||||
xcb_map_window(xcb_connection, trayclient->win);
|
||||
xcb_map_window(xcb_connection, client->win);
|
||||
}
|
||||
free(xembedr);
|
||||
} else if (event->atom == XCB_ATOM_WM_CLASS) {
|
||||
trayclient *client = trayclient_from_window(event->window);
|
||||
if (client) {
|
||||
trayclient_update_class(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1535,6 +1625,9 @@ void kick_tray_clients(i3_output *output) {
|
|||
0,
|
||||
0);
|
||||
|
||||
free(trayclient->class_class);
|
||||
free(trayclient->class_instance);
|
||||
|
||||
/* We remove the trayclient right here. We might receive an UnmapNotify
|
||||
* event afterwards, but better safe than sorry. */
|
||||
TAILQ_REMOVE(output->trayclients, trayclient, tailq);
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -1964,26 +2076,11 @@ void draw_bars(bool unhide) {
|
|||
unhide = true;
|
||||
}
|
||||
|
||||
/* Draw the border of the button. */
|
||||
draw_util_rectangle(&(outputs_walk->buffer), border_color,
|
||||
workspace_width,
|
||||
logical_px(1),
|
||||
ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
|
||||
font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
|
||||
int w = predict_button_width(ws_walk->name_width);
|
||||
draw_button(&(outputs_walk->buffer), fg_color, bg_color, border_color,
|
||||
workspace_width, w, ws_walk->name_width, ws_walk->name);
|
||||
|
||||
/* Draw the inside of the button. */
|
||||
draw_util_rectangle(&(outputs_walk->buffer), bg_color,
|
||||
workspace_width + logical_px(1),
|
||||
2 * logical_px(1),
|
||||
ws_walk->name_width + 2 * logical_px(ws_hoff_px),
|
||||
font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1));
|
||||
|
||||
draw_util_text(ws_walk->name, &(outputs_walk->buffer), fg_color, bg_color,
|
||||
workspace_width + logical_px(ws_hoff_px) + logical_px(1),
|
||||
logical_px(ws_voff_px),
|
||||
ws_walk->name_width);
|
||||
|
||||
workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + ws_walk->name_width;
|
||||
workspace_width += w;
|
||||
if (TAILQ_NEXT(ws_walk, tailq) != NULL)
|
||||
workspace_width += logical_px(ws_spacing_px);
|
||||
}
|
||||
|
@ -1992,28 +2089,12 @@ void draw_bars(bool unhide) {
|
|||
if (binding.name && !config.disable_binding_mode_indicator) {
|
||||
workspace_width += logical_px(ws_spacing_px);
|
||||
|
||||
color_t fg_color = colors.binding_mode_fg;
|
||||
color_t bg_color = colors.binding_mode_bg;
|
||||
|
||||
draw_util_rectangle(&(outputs_walk->buffer), colors.binding_mode_border,
|
||||
workspace_width,
|
||||
logical_px(1),
|
||||
binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
|
||||
font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
|
||||
|
||||
draw_util_rectangle(&(outputs_walk->buffer), bg_color,
|
||||
workspace_width + logical_px(1),
|
||||
2 * logical_px(1),
|
||||
binding.width + 2 * logical_px(ws_hoff_px),
|
||||
font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1));
|
||||
|
||||
draw_util_text(binding.name, &(outputs_walk->buffer), fg_color, bg_color,
|
||||
workspace_width + logical_px(ws_hoff_px) + logical_px(1),
|
||||
logical_px(ws_voff_px),
|
||||
binding.width);
|
||||
int w = predict_button_width(binding.name_width);
|
||||
draw_button(&(outputs_walk->buffer), colors.binding_mode_fg, colors.binding_mode_bg,
|
||||
colors.binding_mode_border, workspace_width, w, binding.name_width, binding.name);
|
||||
|
||||
unhide = true;
|
||||
workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + binding.width;
|
||||
workspace_width += w;
|
||||
}
|
||||
|
||||
if (!TAILQ_EMPTY(&statusline_head)) {
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
#include "click.h"
|
||||
#include "key_press.h"
|
||||
#include "floating.h"
|
||||
#include "drag.h"
|
||||
#include "configuration.h"
|
||||
#include "handlers.h"
|
||||
#include "randr.h"
|
||||
|
|
|
@ -182,6 +182,12 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command);
|
|||
*/
|
||||
void cmd_focus_direction(I3_CMD, const char *direction);
|
||||
|
||||
/**
|
||||
* Implementation of 'focus next|prev sibling'
|
||||
*
|
||||
*/
|
||||
void cmd_focus_sibling(I3_CMD, const char *direction);
|
||||
|
||||
/**
|
||||
* Implementation of 'focus tiling|floating|mode_toggle'.
|
||||
*
|
||||
|
|
|
@ -45,6 +45,13 @@ void con_focus(Con *con);
|
|||
*/
|
||||
void con_activate(Con *con);
|
||||
|
||||
/**
|
||||
* Activates the container like in con_activate but removes fullscreen
|
||||
* restrictions and properly warps the pointer if needed.
|
||||
*
|
||||
*/
|
||||
void con_activate_unblock(Con *con);
|
||||
|
||||
/**
|
||||
* Closes the given container.
|
||||
*
|
||||
|
@ -533,3 +540,11 @@ bool con_swap(Con *first, Con *second);
|
|||
*
|
||||
*/
|
||||
uint32_t con_rect_size_in_orientation(Con *con);
|
||||
|
||||
/**
|
||||
* Merges container specific data that should move with the window (e.g. marks,
|
||||
* title format, and the window itself) into another container, and closes the
|
||||
* old container.
|
||||
*
|
||||
*/
|
||||
void con_merge_into(Con *old, Con *new);
|
||||
|
|
|
@ -97,6 +97,7 @@ CFGFUN(bar_color_single, const char *colorclass, const char *color);
|
|||
CFGFUN(bar_status_command, const char *command);
|
||||
CFGFUN(bar_binding_mode_indicator, const char *value);
|
||||
CFGFUN(bar_workspace_buttons, const char *value);
|
||||
CFGFUN(bar_workspace_min_width, const long width);
|
||||
CFGFUN(bar_strip_workspace_numbers, const char *value);
|
||||
CFGFUN(bar_strip_workspace_name, const char *value);
|
||||
CFGFUN(bar_start);
|
||||
|
|
|
@ -325,6 +325,9 @@ struct Barconfig {
|
|||
* zero. */
|
||||
bool hide_workspace_buttons;
|
||||
|
||||
/** The minimal width for workspace buttons. */
|
||||
int workspace_min_width;
|
||||
|
||||
/** Strip workspace numbers? Configuration option is
|
||||
* 'strip_workspace_numbers yes'. */
|
||||
bool strip_workspace_numbers;
|
||||
|
|
|
@ -59,6 +59,8 @@ typedef enum { D_LEFT,
|
|||
typedef enum { NO_ORIENTATION = 0,
|
||||
HORIZ,
|
||||
VERT } orientation_t;
|
||||
typedef enum { BEFORE,
|
||||
AFTER } position_t;
|
||||
typedef enum { BS_NORMAL = 0,
|
||||
BS_NONE = 1,
|
||||
BS_PIXEL = 2 } border_style_t;
|
||||
|
@ -139,13 +141,12 @@ typedef enum {
|
|||
typedef enum {
|
||||
FOCUS_WRAPPING_OFF = 0,
|
||||
FOCUS_WRAPPING_ON = 1,
|
||||
FOCUS_WRAPPING_FORCE = 2
|
||||
FOCUS_WRAPPING_FORCE = 2,
|
||||
FOCUS_WRAPPING_WORKSPACE = 3
|
||||
} focus_wrapping_t;
|
||||
|
||||
/**
|
||||
* Stores a rectangle, for example the size of a window, the child window etc.
|
||||
* It needs to be packed so that the compiler will not add any padding bytes.
|
||||
* (it is used in src/ewmh.c for example)
|
||||
*
|
||||
* Note that x and y can contain signed values in some cases (for example when
|
||||
* used for the coordinates of a window, which can be set outside of the
|
||||
|
@ -159,7 +160,7 @@ struct Rect {
|
|||
uint32_t y;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores the reserved pixels on each screen edge read from a
|
||||
|
@ -489,6 +490,10 @@ struct Window {
|
|||
bool shaped;
|
||||
/** The window has a nonrectangular input shape. */
|
||||
bool input_shaped;
|
||||
|
||||
/* Time when the window became managed. Used to determine whether a window
|
||||
* should be swallowed after initial management. */
|
||||
time_t managed_since;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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"
|
||||
|
||||
/** Callback for dragging */
|
||||
typedef void (*callback_t)(Con *, Rect *, uint32_t, uint32_t, const void *);
|
||||
|
||||
/** Macro to create a callback function for dragging */
|
||||
#define DRAGGING_CB(name) \
|
||||
static void name(Con *con, Rect *old_rect, uint32_t new_x, \
|
||||
uint32_t new_y, const void *extra)
|
||||
|
||||
/** On which border was the dragging initiated? */
|
||||
typedef enum { BORDER_LEFT = (1 << 0),
|
||||
BORDER_RIGHT = (1 << 1),
|
||||
|
@ -40,7 +32,7 @@ void floating_enable(Con *con, bool automatic);
|
|||
* to its old parent.
|
||||
*
|
||||
*/
|
||||
void floating_disable(Con *con, bool automatic);
|
||||
void floating_disable(Con *con);
|
||||
|
||||
/**
|
||||
* Calls floating_enable() for tiling containers and floating_disable() for
|
||||
|
@ -83,7 +75,7 @@ void floating_move_to_pointer(Con *con);
|
|||
* Calls the drag_pointer function with the drag_window callback
|
||||
*
|
||||
*/
|
||||
void floating_drag_window(Con *con, const xcb_button_press_event_t *event);
|
||||
void floating_drag_window(Con *con, const xcb_button_press_event_t *event, bool use_threshold);
|
||||
|
||||
/**
|
||||
* Called when the user clicked on a floating window while holding the
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
* This is the return value of a drag operation like drag_pointer.
|
||||
*
|
||||
* DRAGGING will indicate the drag action is still in progress and can be
|
||||
* continued or resolved.
|
||||
*
|
||||
* DRAG_SUCCESS will indicate the intention of the drag action should be
|
||||
* carried out.
|
||||
*
|
||||
* DRAG_REVERT will indicate an attempt should be made to restore the state of
|
||||
* the involved windows to their condition before the drag.
|
||||
*
|
||||
* DRAG_ABORT will indicate that the intention of the drag action cannot be
|
||||
* carried out (e.g. because the window has been unmapped).
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
DRAGGING = 0,
|
||||
DRAG_SUCCESS,
|
||||
DRAG_REVERT,
|
||||
DRAG_ABORT
|
||||
} drag_result_t;
|
||||
|
||||
/**
|
||||
* This function grabs your pointer and keyboard and lets you drag stuff around
|
||||
* (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
|
||||
* be received and the given callback will be called with the parameters
|
||||
* specified (client, border on which the click originally was), the original
|
||||
* rect of the client, the event and the new coordinates (x, y).
|
||||
*
|
||||
*/
|
||||
drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
|
||||
xcb_window_t confine_to, border_t border, int cursor,
|
||||
callback_t callback, const void *extra);
|
||||
|
||||
/**
|
||||
* Repositions the CT_FLOATING_CON to have the coordinates specified by
|
||||
* newrect, but only if the coordinates are not out-of-bounds. Also reassigns
|
||||
|
|
|
@ -341,8 +341,7 @@ gchar *g_utf8_make_valid(const gchar *str, gssize len);
|
|||
*/
|
||||
uint32_t get_colorpixel(const char *hex) __attribute__((const));
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
#ifndef HAVE_strndup
|
||||
/**
|
||||
* Taken from FreeBSD
|
||||
* Returns a pointer to a new string which is a duplicate of the
|
||||
|
@ -350,7 +349,6 @@ uint32_t get_colorpixel(const char *hex) __attribute__((const));
|
|||
*
|
||||
*/
|
||||
char *strndup(const char *str, size_t n);
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
@ -528,7 +526,7 @@ char *resolve_tilde(const char *path);
|
|||
*/
|
||||
char *get_config_path(const char *override_configpath, bool use_system_paths);
|
||||
|
||||
#if !defined(__sun)
|
||||
#ifndef HAVE_mkdirp
|
||||
/**
|
||||
* Emulates mkdir -p (creates any missing folders)
|
||||
*
|
||||
|
|
|
@ -37,3 +37,10 @@ void restore_geometry(void);
|
|||
void manage_window(xcb_window_t window,
|
||||
xcb_get_window_attributes_cookie_t cookie,
|
||||
bool needs_to_be_mapped);
|
||||
|
||||
/**
|
||||
* Remanages a window: performs a swallow check and runs assignments.
|
||||
* Returns con for the window regardless if it updated.
|
||||
*
|
||||
*/
|
||||
Con *remanage_window(Con *con);
|
||||
|
|
|
@ -12,14 +12,10 @@
|
|||
#include <config.h>
|
||||
|
||||
/**
|
||||
* Moves the given container in the given direction (TOK_LEFT, TOK_RIGHT,
|
||||
* TOK_UP, TOK_DOWN from cmdparse.l)
|
||||
* Moves the given container in the given direction
|
||||
*
|
||||
*/
|
||||
void tree_move(Con *con, int direction);
|
||||
|
||||
typedef enum { BEFORE,
|
||||
AFTER } position_t;
|
||||
void tree_move(Con *con, direction_t direction);
|
||||
|
||||
/**
|
||||
* This function detaches 'con' from its parent and inserts it either before or
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
|
||||
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides);
|
||||
|
||||
void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
|
||||
void resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
|
||||
const xcb_button_press_event_t *event,
|
||||
bool use_threshold);
|
||||
|
||||
/**
|
||||
* Resize the two given containers using the given amount of pixels or
|
||||
|
|
|
@ -69,3 +69,9 @@ struct Startup_Sequence *startup_sequence_get(i3Window *cwindow,
|
|||
*
|
||||
*/
|
||||
char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply);
|
||||
|
||||
/**
|
||||
* Deletes the startup sequence for a window if it exists.
|
||||
*
|
||||
*/
|
||||
void startup_sequence_delete_by_window(i3Window *win);
|
||||
|
|
|
@ -59,11 +59,16 @@ bool level_down(void);
|
|||
void tree_render(void);
|
||||
|
||||
/**
|
||||
* Changes focus in the given way (next/previous) and given orientation
|
||||
* (horizontal/vertical).
|
||||
* Changes focus in the given direction
|
||||
*
|
||||
*/
|
||||
void tree_next(char way, orientation_t orientation);
|
||||
void tree_next(Con *con, direction_t direction);
|
||||
|
||||
/**
|
||||
* Get the previous / next sibling
|
||||
*
|
||||
*/
|
||||
Con *get_tree_next_sibling(Con *con, position_t direction);
|
||||
|
||||
/**
|
||||
* Closes the given container including all children.
|
||||
|
|
|
@ -64,6 +64,7 @@ int max(int a, int b);
|
|||
bool rect_contains(Rect rect, uint32_t x, uint32_t y);
|
||||
Rect rect_add(Rect a, Rect b);
|
||||
Rect rect_sub(Rect a, Rect b);
|
||||
bool rect_equals(Rect a, Rect b);
|
||||
|
||||
/**
|
||||
* Returns true if the name consists of only digits.
|
||||
|
@ -123,17 +124,6 @@ bool path_exists(const char *path);
|
|||
*/
|
||||
void i3_restart(bool forget_layout);
|
||||
|
||||
#if defined(__OpenBSD__) || defined(__APPLE__)
|
||||
|
||||
/**
|
||||
* Taken from FreeBSD
|
||||
* Find the first occurrence of the byte string s in byte string l.
|
||||
*
|
||||
*/
|
||||
void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Escapes the given string if a pango font is currently used.
|
||||
* If the string has to be escaped, the input string will be free'd.
|
||||
|
@ -180,3 +170,15 @@ ssize_t slurp(const char *path, char **buf);
|
|||
*
|
||||
*/
|
||||
orientation_t orientation_from_direction(direction_t direction);
|
||||
|
||||
/**
|
||||
* Convert a direction to its corresponding position.
|
||||
*
|
||||
*/
|
||||
position_t position_from_direction(direction_t direction);
|
||||
|
||||
/**
|
||||
* Convert orientation and position to the corresponding direction.
|
||||
*
|
||||
*/
|
||||
direction_t direction_from_orientation_position(orientation_t orientation, position_t position);
|
||||
|
|
|
@ -22,14 +22,14 @@ void window_free(i3Window *win);
|
|||
* given window.
|
||||
*
|
||||
*/
|
||||
void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
|
||||
void window_update_class(i3Window *win, xcb_get_property_reply_t *prop);
|
||||
|
||||
/**
|
||||
* Updates the name by using _NET_WM_NAME (encoded in UTF-8) for the given
|
||||
* window. Further updates using window_update_name_legacy will be ignored.
|
||||
*
|
||||
*/
|
||||
void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
|
||||
void window_update_name(i3Window *win, xcb_get_property_reply_t *prop);
|
||||
|
||||
/**
|
||||
* Updates the name by using WM_NAME (encoded in COMPOUND_TEXT). We do not
|
||||
|
@ -38,7 +38,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
|
|||
* window_update_name()).
|
||||
*
|
||||
*/
|
||||
void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
|
||||
void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop);
|
||||
|
||||
/**
|
||||
* Updates the CLIENT_LEADER (logical parent window).
|
||||
|
@ -62,7 +62,7 @@ void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop);
|
|||
* Updates the WM_WINDOW_ROLE
|
||||
*
|
||||
*/
|
||||
void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
|
||||
void window_update_role(i3Window *win, xcb_get_property_reply_t *prop);
|
||||
|
||||
/**
|
||||
* Updates the _NET_WM_WINDOW_TYPE property.
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
XCB_EVENT_MASK_FOCUS_CHANGE | \
|
||||
XCB_EVENT_MASK_ENTER_WINDOW)
|
||||
|
||||
#define xmacro(atom) xcb_atom_t A_##atom;
|
||||
#define xmacro(atom) extern xcb_atom_t A_##atom;
|
||||
#include "atoms.xmacro"
|
||||
#undef xmacro
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#include <cairo/cairo-xcb.h>
|
||||
|
||||
/* The default visual_type to use if none is specified when creating the surface. Must be defined globally. */
|
||||
xcb_visualtype_t *visual_type;
|
||||
extern xcb_visualtype_t *visual_type;
|
||||
|
||||
/* Forward declarations */
|
||||
static void draw_util_set_source_color(surface_t *surface, color_t color);
|
||||
|
|
|
@ -323,7 +323,7 @@ bool font_is_pango(void) {
|
|||
static int predict_text_width_xcb(const xcb_char2b_t *text, size_t text_len);
|
||||
|
||||
static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawable_t drawable,
|
||||
xcb_gcontext_t gc, int x, int y, int max_width) {
|
||||
xcb_gcontext_t gc, int x, int y) {
|
||||
/* X11 coordinates for fonts start at the baseline */
|
||||
int pos_y = y + savedFont->specific.xcb.info->font_ascent;
|
||||
|
||||
|
@ -372,7 +372,7 @@ void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc,
|
|||
return;
|
||||
case FONT_TYPE_XCB:
|
||||
draw_text_xcb(i3string_as_ucs2(text), i3string_get_num_glyphs(text),
|
||||
drawable, gc, x, y, max_width);
|
||||
drawable, gc, x, y);
|
||||
break;
|
||||
case FONT_TYPE_PANGO:
|
||||
/* Render the text using Pango */
|
||||
|
|
|
@ -12,12 +12,11 @@
|
|||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef HAVE_mkdirp
|
||||
/*
|
||||
* Emulates mkdir -p (creates any missing folders)
|
||||
*
|
||||
*/
|
||||
|
||||
#if !defined(__sun)
|
||||
int mkdirp(const char *path, mode_t mode) {
|
||||
if (mkdir(path, mode) == 0)
|
||||
return 0;
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
#include <sys/types.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
#ifndef HAVE_strndup
|
||||
/*
|
||||
* Taken from FreeBSD
|
||||
* Returns a pointer to a new string which is a duplicate of the
|
||||
|
@ -30,5 +29,4 @@ char *strndup(const char *str, size_t n) {
|
|||
copy[len] = '\0';
|
||||
return (copy);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -107,7 +107,7 @@ AC_DEFUN([AX_CODE_COVERAGE],[
|
|||
])
|
||||
|
||||
# List of supported lcov versions.
|
||||
lcov_version_list="1.6 1.7 1.8 1.9 1.10 1.11 1.12"
|
||||
lcov_version_list="1.6 1.7 1.8 1.9 1.10 1.11 1.12 1.13"
|
||||
|
||||
AC_CHECK_PROG([LCOV], [lcov], [lcov])
|
||||
AC_CHECK_PROG([GENHTML], [genhtml], [genhtml])
|
||||
|
|
|
@ -146,6 +146,8 @@ state WORKSPACE_NUMBER:
|
|||
state FOCUS:
|
||||
direction = 'left', 'right', 'up', 'down'
|
||||
-> call cmd_focus_direction($direction)
|
||||
direction = 'prev', 'next'
|
||||
-> FOCUS_AUTO
|
||||
'output'
|
||||
-> FOCUS_OUTPUT
|
||||
window_mode = 'tiling', 'floating', 'mode_toggle'
|
||||
|
@ -155,6 +157,12 @@ state FOCUS:
|
|||
end
|
||||
-> call cmd_focus()
|
||||
|
||||
state FOCUS_AUTO:
|
||||
'sibling'
|
||||
-> call cmd_focus_sibling($direction)
|
||||
end
|
||||
-> call cmd_focus_direction($direction)
|
||||
|
||||
state FOCUS_OUTPUT:
|
||||
output = string
|
||||
-> call cmd_focus_output($output)
|
||||
|
|
|
@ -215,7 +215,7 @@ state MOUSE_WARPING:
|
|||
|
||||
# focus_wrapping
|
||||
state FOCUS_WRAPPING:
|
||||
value = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive', 'force'
|
||||
value = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive', 'force', 'workspace'
|
||||
-> call cfg_focus_wrapping($value)
|
||||
|
||||
# force_focus_wrapping
|
||||
|
@ -468,6 +468,7 @@ state BAR:
|
|||
'separator_symbol' -> BAR_SEPARATOR_SYMBOL
|
||||
'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR
|
||||
'workspace_buttons' -> BAR_WORKSPACE_BUTTONS
|
||||
'workspace_min_width' -> BAR_WORKSPACE_MIN_WIDTH
|
||||
'strip_workspace_numbers' -> BAR_STRIP_WORKSPACE_NUMBERS
|
||||
'strip_workspace_name' -> BAR_STRIP_WORKSPACE_NAME
|
||||
'verbose' -> BAR_VERBOSE
|
||||
|
@ -572,6 +573,16 @@ state BAR_WORKSPACE_BUTTONS:
|
|||
value = word
|
||||
-> call cfg_bar_workspace_buttons($value); BAR
|
||||
|
||||
state BAR_WORKSPACE_MIN_WIDTH:
|
||||
width = number
|
||||
-> BAR_WORKSPACE_MIN_WIDTH_PX
|
||||
|
||||
state BAR_WORKSPACE_MIN_WIDTH_PX:
|
||||
'px'
|
||||
->
|
||||
end
|
||||
-> call cfg_bar_workspace_min_width(&width); BAR
|
||||
|
||||
state BAR_STRIP_WORKSPACE_NUMBERS:
|
||||
value = word
|
||||
-> call cfg_bar_strip_workspace_numbers($value); BAR
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/bin/zsh
|
||||
# This script is used to prepare a new release of i3.
|
||||
|
||||
export RELEASE_VERSION="4.16"
|
||||
export PREVIOUS_VERSION="4.15"
|
||||
export RELEASE_VERSION="4.17"
|
||||
export PREVIOUS_VERSION="4.16"
|
||||
export RELEASE_BRANCH="next"
|
||||
|
||||
if [ ! -e "../i3.github.io" ]
|
||||
|
|
75
src/click.c
75
src/click.c
|
@ -26,7 +26,7 @@ typedef enum { CLICK_BORDER = 0,
|
|||
* then calls resize_graphical_handler().
|
||||
*
|
||||
*/
|
||||
static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) {
|
||||
static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event, bool use_threshold) {
|
||||
DLOG("border = %d, con = %p\n", border, con);
|
||||
Con *second = NULL;
|
||||
Con *first = con;
|
||||
|
@ -64,7 +64,7 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
|
|||
|
||||
const orientation_t orientation = ((border == BORDER_LEFT || border == BORDER_RIGHT) ? HORIZ : VERT);
|
||||
|
||||
resize_graphical_handler(first, second, orientation, event);
|
||||
resize_graphical_handler(first, second, orientation, event, use_threshold);
|
||||
|
||||
DLOG("After resize handler, rendering\n");
|
||||
tree_render();
|
||||
|
@ -94,22 +94,22 @@ static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *eve
|
|||
if (to_right < to_left &&
|
||||
to_right < to_top &&
|
||||
to_right < to_bottom)
|
||||
return tiling_resize_for_border(con, BORDER_RIGHT, event);
|
||||
return tiling_resize_for_border(con, BORDER_RIGHT, event, false);
|
||||
|
||||
if (to_left < to_right &&
|
||||
to_left < to_top &&
|
||||
to_left < to_bottom)
|
||||
return tiling_resize_for_border(con, BORDER_LEFT, event);
|
||||
return tiling_resize_for_border(con, BORDER_LEFT, event, false);
|
||||
|
||||
if (to_top < to_right &&
|
||||
to_top < to_left &&
|
||||
to_top < to_bottom)
|
||||
return tiling_resize_for_border(con, BORDER_TOP, event);
|
||||
return tiling_resize_for_border(con, BORDER_TOP, event, false);
|
||||
|
||||
if (to_bottom < to_right &&
|
||||
to_bottom < to_left &&
|
||||
to_bottom < to_top)
|
||||
return tiling_resize_for_border(con, BORDER_BOTTOM, event);
|
||||
return tiling_resize_for_border(con, BORDER_BOTTOM, event, false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -118,45 +118,26 @@ static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *eve
|
|||
* Finds out which border was clicked on and calls tiling_resize_for_border().
|
||||
*
|
||||
*/
|
||||
static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click_destination_t dest) {
|
||||
static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click_destination_t dest, bool use_threshold) {
|
||||
/* check if this was a click on the window border (and on which one) */
|
||||
Rect bsr = con_border_style_rect(con);
|
||||
DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n",
|
||||
event->event_x, event->event_y, con, event->event);
|
||||
DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width);
|
||||
if (dest == CLICK_DECORATION) {
|
||||
/* The user clicked on a window decoration. We ignore the following case:
|
||||
* The container is a h-split, tabbed or stacked container with > 1
|
||||
* window. Decorations will end up next to each other and the user
|
||||
* expects to switch to a window by clicking on its decoration. */
|
||||
|
||||
/* Since the container might either be the child *or* already a split
|
||||
* container (in the case of a nested split container), we need to make
|
||||
* sure that we are dealing with the split container here. */
|
||||
Con *check_con = con;
|
||||
if (con_is_leaf(check_con) && check_con->parent->type == CT_CON)
|
||||
check_con = check_con->parent;
|
||||
|
||||
if ((check_con->layout == L_STACKED ||
|
||||
check_con->layout == L_TABBED ||
|
||||
con_orientation(check_con) == HORIZ) &&
|
||||
con_num_children(check_con) > 1) {
|
||||
DLOG("Not handling this resize, this container has > 1 child.\n");
|
||||
return false;
|
||||
}
|
||||
return tiling_resize_for_border(con, BORDER_TOP, event);
|
||||
return tiling_resize_for_border(con, BORDER_TOP, event, use_threshold);
|
||||
}
|
||||
|
||||
if (event->event_x >= 0 && event->event_x <= (int32_t)bsr.x &&
|
||||
event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height))
|
||||
return tiling_resize_for_border(con, BORDER_LEFT, event);
|
||||
return tiling_resize_for_border(con, BORDER_LEFT, event, false);
|
||||
|
||||
if (event->event_x >= (int32_t)(con->window_rect.x + con->window_rect.width) &&
|
||||
event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height))
|
||||
return tiling_resize_for_border(con, BORDER_RIGHT, event);
|
||||
return tiling_resize_for_border(con, BORDER_RIGHT, event, false);
|
||||
|
||||
if (event->event_y >= (int32_t)(con->window_rect.y + con->window_rect.height))
|
||||
return tiling_resize_for_border(con, BORDER_BOTTOM, event);
|
||||
return tiling_resize_for_border(con, BORDER_BOTTOM, event, false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -221,6 +202,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
Con *floatingcon = con_inside_floating(con);
|
||||
const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT;
|
||||
const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED);
|
||||
const bool was_focused = focused == con;
|
||||
|
||||
/* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
|
||||
if (in_stacked &&
|
||||
|
@ -230,21 +212,13 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
event->detail == XCB_BUTTON_SCROLL_LEFT ||
|
||||
event->detail == XCB_BUTTON_SCROLL_RIGHT)) {
|
||||
DLOG("Scrolling on a window decoration\n");
|
||||
orientation_t orientation = con_orientation(con->parent);
|
||||
/* Use the focused child of the tabbed / stacked container, not the
|
||||
* container the user scrolled on. */
|
||||
Con *focused = con->parent;
|
||||
focused = TAILQ_FIRST(&(focused->focus_head));
|
||||
con_activate(con_descend_focused(focused));
|
||||
/* To prevent scrolling from going outside the container (see ticket
|
||||
* #557), we first check if scrolling is possible at all. */
|
||||
bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
|
||||
bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL);
|
||||
if ((event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) && scroll_prev_possible) {
|
||||
tree_next('p', orientation);
|
||||
} else if ((event->detail == XCB_BUTTON_SCROLL_DOWN || event->detail == XCB_BUTTON_SCROLL_RIGHT) && scroll_next_possible) {
|
||||
tree_next('n', orientation);
|
||||
}
|
||||
Con *current = TAILQ_FIRST(&(con->parent->focus_head));
|
||||
const position_t direction =
|
||||
(event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) ? BEFORE : AFTER;
|
||||
Con *next = get_tree_next_sibling(current, direction);
|
||||
con_activate(con_descend_focused(next ? next : current));
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
@ -258,7 +232,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
if (floatingcon != NULL && fs != con) {
|
||||
/* 4: floating_modifier plus left mouse button drags */
|
||||
if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) {
|
||||
floating_drag_window(floatingcon, event);
|
||||
floating_drag_window(floatingcon, event, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -275,7 +249,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
is_left_or_right_click) {
|
||||
/* try tiling resize, but continue if it doesn’t work */
|
||||
DLOG("tiling resize with fallback\n");
|
||||
if (tiling_resize(con, event, dest))
|
||||
if (tiling_resize(con, event, dest, !was_focused))
|
||||
goto done;
|
||||
}
|
||||
|
||||
|
@ -293,9 +267,8 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
|
||||
/* 6: dragging, if this was a click on a decoration (which did not lead
|
||||
* to a resize) */
|
||||
if (!in_stacked && dest == CLICK_DECORATION &&
|
||||
(event->detail == XCB_BUTTON_CLICK_LEFT)) {
|
||||
floating_drag_window(floatingcon, event);
|
||||
if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_CLICK_LEFT) {
|
||||
floating_drag_window(floatingcon, event, !was_focused);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -311,7 +284,11 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
else if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
|
||||
is_left_or_right_click) {
|
||||
DLOG("Trying to resize (tiling)\n");
|
||||
tiling_resize(con, event, dest);
|
||||
/* Since we updated the tree (con_activate() above), we need to
|
||||
* re-render the tree before returning to the event loop (drag_pointer()
|
||||
* inside tiling_resize() runs its own event-loop). */
|
||||
tree_render();
|
||||
tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused);
|
||||
}
|
||||
|
||||
done:
|
||||
|
|
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;
|
||||
Con *focused_con = con_descend_focused(floating_con);
|
||||
|
||||
|
@ -469,7 +469,7 @@ static void cmd_resize_floating(I3_CMD, const char *way, const char *direction_s
|
|||
|
||||
/* Did we actually resize anything or did the size constraints prevent us?
|
||||
* If we could not resize, exit now to not move the window. */
|
||||
if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0) {
|
||||
if (rect_equals(old_rect, floating_con->rect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -596,7 +596,7 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
|
|||
|
||||
Con *floating_con;
|
||||
if ((floating_con = con_inside_floating(current->con))) {
|
||||
cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, resize_px);
|
||||
cmd_resize_floating(current_match, cmd_output, direction, floating_con, resize_px);
|
||||
} else {
|
||||
if (strcmp(direction, "width") == 0 ||
|
||||
strcmp(direction, "height") == 0) {
|
||||
|
@ -1089,7 +1089,7 @@ void cmd_floating(I3_CMD, const char *floating_mode) {
|
|||
if (strcmp(floating_mode, "enable") == 0) {
|
||||
floating_enable(current->con, false);
|
||||
} else {
|
||||
floating_disable(current->con, false);
|
||||
floating_disable(current->con);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1207,30 +1207,74 @@ void cmd_kill(I3_CMD, const char *kill_mode_str) {
|
|||
void cmd_exec(I3_CMD, const char *nosn, const char *command) {
|
||||
bool no_startup_id = (nosn != NULL);
|
||||
|
||||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
int count = 0;
|
||||
owindow *current;
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > 1) {
|
||||
LOG("WARNING: Your criteria for the exec command match %d containers, "
|
||||
"so the command will execute this many times.\n",
|
||||
count);
|
||||
}
|
||||
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
|
||||
start_application(command, no_startup_id);
|
||||
}
|
||||
|
||||
ysuccess(true);
|
||||
}
|
||||
|
||||
#define CMD_FOCUS_WARN_CHILDREN \
|
||||
do { \
|
||||
int count = 0; \
|
||||
owindow *current; \
|
||||
TAILQ_FOREACH(current, &owindows, owindows) { \
|
||||
count++; \
|
||||
} \
|
||||
\
|
||||
if (count > 1) { \
|
||||
LOG("WARNING: Your criteria for the focus command matches %d containers, " \
|
||||
"while only exactly one container can be focused at a time.\n", \
|
||||
count); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Implementation of 'focus left|right|up|down'.
|
||||
* Implementation of 'focus left|right|up|down|next|prev'.
|
||||
*
|
||||
*/
|
||||
void cmd_focus_direction(I3_CMD, const char *direction) {
|
||||
switch (parse_direction(direction)) {
|
||||
case D_LEFT:
|
||||
tree_next('p', HORIZ);
|
||||
break;
|
||||
case D_RIGHT:
|
||||
tree_next('n', HORIZ);
|
||||
break;
|
||||
case D_UP:
|
||||
tree_next('p', VERT);
|
||||
break;
|
||||
case D_DOWN:
|
||||
tree_next('n', VERT);
|
||||
break;
|
||||
void cmd_focus_direction(I3_CMD, const char *direction_str) {
|
||||
HANDLE_EMPTY_MATCH;
|
||||
CMD_FOCUS_WARN_CHILDREN;
|
||||
|
||||
direction_t direction;
|
||||
position_t position;
|
||||
bool auto_direction = true;
|
||||
if (strcmp(direction_str, "prev") == 0) {
|
||||
position = BEFORE;
|
||||
} else if (strcmp(direction_str, "next") == 0) {
|
||||
position = AFTER;
|
||||
} else {
|
||||
auto_direction = false;
|
||||
direction = parse_direction(direction_str);
|
||||
}
|
||||
|
||||
owindow *current;
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
Con *ws = con_get_workspace(current->con);
|
||||
if (!ws || con_is_internal(ws)) {
|
||||
continue;
|
||||
}
|
||||
if (auto_direction) {
|
||||
orientation_t o = con_orientation(current->con->parent);
|
||||
direction = direction_from_orientation_position(o, position);
|
||||
}
|
||||
tree_next(current->con, direction);
|
||||
}
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
|
@ -1239,17 +1283,29 @@ void cmd_focus_direction(I3_CMD, const char *direction) {
|
|||
}
|
||||
|
||||
/*
|
||||
* Focus a container and disable any other fullscreen container not permitting the focus.
|
||||
* Implementation of 'focus next|prev sibling'
|
||||
*
|
||||
*/
|
||||
static void cmd_focus_force_focus(Con *con) {
|
||||
/* Disable fullscreen container in workspace with container to be focused. */
|
||||
Con *ws = con_get_workspace(con);
|
||||
Con *fullscreen_on_ws = con_get_fullscreen_covering_ws(ws);
|
||||
if (fullscreen_on_ws && fullscreen_on_ws != con && !con_has_parent(con, fullscreen_on_ws)) {
|
||||
con_disable_fullscreen(fullscreen_on_ws);
|
||||
void cmd_focus_sibling(I3_CMD, const char *direction_str) {
|
||||
HANDLE_EMPTY_MATCH;
|
||||
CMD_FOCUS_WARN_CHILDREN;
|
||||
|
||||
const position_t direction = (STARTS_WITH(direction_str, "prev")) ? BEFORE : AFTER;
|
||||
owindow *current;
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
Con *ws = con_get_workspace(current->con);
|
||||
if (!ws || con_is_internal(ws)) {
|
||||
continue;
|
||||
}
|
||||
con_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))
|
||||
continue;
|
||||
|
||||
cmd_focus_force_focus(con_descend_focused(current));
|
||||
con_activate_unblock(con_descend_focused(current));
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
|
@ -1329,12 +1385,15 @@ void cmd_focus(I3_CMD) {
|
|||
ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
|
||||
|
||||
yerror("You have to specify which window/container should be focused");
|
||||
|
||||
return;
|
||||
} else if (TAILQ_EMPTY(&owindows)) {
|
||||
yerror("No window matches given criteria");
|
||||
return;
|
||||
}
|
||||
|
||||
CMD_FOCUS_WARN_CHILDREN;
|
||||
|
||||
Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
|
||||
int count = 0;
|
||||
owindow *current;
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
Con *ws = con_get_workspace(current->con);
|
||||
|
@ -1346,43 +1405,18 @@ void cmd_focus(I3_CMD) {
|
|||
/* In case this is a scratchpad window, call scratchpad_show(). */
|
||||
if (ws == __i3_scratch) {
|
||||
scratchpad_show(current->con);
|
||||
count++;
|
||||
/* While for the normal focus case we can change focus multiple
|
||||
* times and only a single window ends up focused, we could show
|
||||
* multiple scratchpad windows. So, rather break here. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* If the container is not on the current workspace,
|
||||
* workspace_show() will switch to a different workspace and (if
|
||||
* enabled) trigger a mouse pointer warp to the currently focused
|
||||
* container (!) on the target workspace.
|
||||
*
|
||||
* Therefore, before calling workspace_show(), we make sure that
|
||||
* 'current' will be focused on the workspace. However, we cannot
|
||||
* just con_focus(current) because then the pointer will not be
|
||||
* warped at all (the code thinks we are already there).
|
||||
*
|
||||
* So we focus 'current' to make it the currently focused window of
|
||||
* the target workspace, then revert focus. */
|
||||
Con *currently_focused = focused;
|
||||
cmd_focus_force_focus(current->con);
|
||||
con_activate(currently_focused);
|
||||
|
||||
/* Now switch to the workspace, then focus */
|
||||
workspace_show(ws);
|
||||
LOG("focusing %p / %s\n", current->con, current->con->name);
|
||||
con_activate(current->con);
|
||||
count++;
|
||||
con_activate_unblock(current->con);
|
||||
}
|
||||
|
||||
if (count > 1)
|
||||
LOG("WARNING: Your criteria for the focus command matches %d containers, "
|
||||
"while only exactly one container can be focused at a time.\n",
|
||||
count);
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
ysuccess(count > 0);
|
||||
ysuccess(true);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1490,9 +1524,11 @@ void cmd_move_direction(I3_CMD, const char *direction_str, long move_px) {
|
|||
}
|
||||
}
|
||||
|
||||
/* the move command should not disturb focus */
|
||||
if (focused != initially_focused)
|
||||
/* The move command should not disturb focus. con_exists is called because
|
||||
* tree_move calls tree_flatten. */
|
||||
if (focused != initially_focused && con_exists(initially_focused)) {
|
||||
con_activate(initially_focused);
|
||||
}
|
||||
|
||||
// XXX: default reply for now, make this a better reply
|
||||
ysuccess(true);
|
||||
|
@ -1562,7 +1598,7 @@ void cmd_layout_toggle(I3_CMD, const char *toggle_mode) {
|
|||
*/
|
||||
void cmd_exit(I3_CMD) {
|
||||
LOG("Exiting due to user command.\n");
|
||||
exit(0);
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
/* unreached */
|
||||
}
|
||||
|
@ -2013,6 +2049,13 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
|
|||
con_focus(previously_focused);
|
||||
}
|
||||
|
||||
/* Let back-and-forth work after renaming the previous workspace.
|
||||
* See #3694. */
|
||||
if (previous_workspace_name && !strcmp(previous_workspace_name, old_name_copy)) {
|
||||
FREE(previous_workspace_name);
|
||||
previous_workspace_name = sstrdup(new_name);
|
||||
}
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
ysuccess(true);
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ static void push_string(const char *identifier, char *str) {
|
|||
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
|
||||
"in the code, or a new command which contains more than "
|
||||
"10 identified tokens.\n");
|
||||
exit(1);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// TODO move to a common util
|
||||
|
@ -128,7 +128,7 @@ static void push_long(const char *identifier, long num) {
|
|||
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
|
||||
"in the code, or a new command which contains more than "
|
||||
"10 identified tokens.\n");
|
||||
exit(1);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// TODO move to a common util
|
||||
|
|
106
src/con.c
106
src/con.c
|
@ -265,6 +265,41 @@ void con_activate(Con *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.
|
||||
*
|
||||
|
@ -1160,7 +1195,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
|
|||
/* 1: save the container which is going to be focused after the current
|
||||
* container is moved away */
|
||||
Con *focus_next = NULL;
|
||||
if (!ignore_focus && source_ws == current_ws) {
|
||||
if (!ignore_focus && source_ws == current_ws && target_ws != source_ws) {
|
||||
focus_next = con_descend_focused(source_ws);
|
||||
if (focus_next == con || con_has_parent(focus_next, con)) {
|
||||
focus_next = con_next_focused(con);
|
||||
|
@ -1258,34 +1293,17 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
|
|||
|
||||
/* 8. If anything within the container is associated with a startup sequence,
|
||||
* delete it so child windows won't be created on the old workspace. */
|
||||
struct Startup_Sequence *sequence;
|
||||
xcb_get_property_cookie_t cookie;
|
||||
xcb_get_property_reply_t *startup_id_reply;
|
||||
|
||||
if (!con_is_leaf(con)) {
|
||||
Con *child;
|
||||
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
|
||||
if (!child->window)
|
||||
continue;
|
||||
|
||||
cookie = xcb_get_property(conn, false, child->window->id,
|
||||
A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
|
||||
startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
|
||||
|
||||
sequence = startup_sequence_get(child->window, startup_id_reply, true);
|
||||
if (sequence != NULL)
|
||||
startup_sequence_delete(sequence);
|
||||
startup_sequence_delete_by_window(child->window);
|
||||
}
|
||||
}
|
||||
|
||||
if (con->window) {
|
||||
cookie = xcb_get_property(conn, false, con->window->id,
|
||||
A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
|
||||
startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
|
||||
|
||||
sequence = startup_sequence_get(con->window, startup_id_reply, true);
|
||||
if (sequence != NULL)
|
||||
startup_sequence_delete(sequence);
|
||||
startup_sequence_delete_by_window(con->window);
|
||||
}
|
||||
|
||||
/* 9. If the container was marked urgent, move the urgency hint. */
|
||||
|
@ -1315,6 +1333,13 @@ bool con_move_to_mark(Con *con, const char *mark) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/* For target containers in the scratchpad, we just send the window to the scratchpad. */
|
||||
if (con_get_workspace(target) == workspace_get("__i3_scratch", NULL)) {
|
||||
DLOG("target container is in the scratchpad, moving container to scratchpad.\n");
|
||||
scratchpad_move(con);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* For floating target containers, we just send the window to the same workspace. */
|
||||
if (con_is_floating(target)) {
|
||||
DLOG("target container is floating, moving container to target's workspace.\n");
|
||||
|
@ -1322,8 +1347,8 @@ bool con_move_to_mark(Con *con, const char *mark) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (target->type == CT_WORKSPACE) {
|
||||
DLOG("target container is a workspace, simply moving the container there.\n");
|
||||
if (target->type == CT_WORKSPACE && con_is_leaf(target)) {
|
||||
DLOG("target container is an empty workspace, simply moving the container there.\n");
|
||||
con_move_to_workspace(con, target, true, false, false);
|
||||
return true;
|
||||
}
|
||||
|
@ -2401,3 +2426,40 @@ bool con_swap(Con *first, Con *second) {
|
|||
uint32_t con_rect_size_in_orientation(Con *con) {
|
||||
return (con_orientation(con) == HORIZ ? con->rect.width : con->rect.height);
|
||||
}
|
||||
|
||||
/*
|
||||
* Merges container specific data that should move with the window (e.g. marks,
|
||||
* title format, and the window itself) into another container, and closes the
|
||||
* old container.
|
||||
*
|
||||
*/
|
||||
void con_merge_into(Con *old, Con *new) {
|
||||
new->window = old->window;
|
||||
old->window = NULL;
|
||||
|
||||
if (old->title_format) {
|
||||
FREE(new->title_format);
|
||||
new->title_format = old->title_format;
|
||||
old->title_format = NULL;
|
||||
}
|
||||
|
||||
if (old->sticky_group) {
|
||||
FREE(new->sticky_group);
|
||||
new->sticky_group = old->sticky_group;
|
||||
old->sticky_group = NULL;
|
||||
}
|
||||
|
||||
new->sticky = old->sticky;
|
||||
|
||||
con_set_urgency(new, old->urgent);
|
||||
|
||||
mark_t *mark;
|
||||
TAILQ_FOREACH(mark, &(old->marks_head), marks) {
|
||||
TAILQ_INSERT_TAIL(&(new->marks_head), mark, marks);
|
||||
ipc_send_window_event("mark", new);
|
||||
}
|
||||
new->mark_changed = (TAILQ_FIRST(&(old->marks_head)) != NULL);
|
||||
TAILQ_INIT(&(old->marks_head));
|
||||
|
||||
tree_close_internal(old, DONT_KILL_WINDOW, false);
|
||||
}
|
||||
|
|
|
@ -268,6 +268,8 @@ CFGFUN(disable_randr15, const char *value) {
|
|||
CFGFUN(focus_wrapping, const char *value) {
|
||||
if (strcmp(value, "force") == 0) {
|
||||
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
|
||||
} else if (strcmp(value, "workspace") == 0) {
|
||||
config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE;
|
||||
} else if (eval_boolstr(value)) {
|
||||
config.focus_wrapping = FOCUS_WRAPPING_ON;
|
||||
} else {
|
||||
|
@ -646,6 +648,10 @@ CFGFUN(bar_workspace_buttons, const char *value) {
|
|||
current_bar->hide_workspace_buttons = !eval_boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(bar_workspace_min_width, const long width) {
|
||||
current_bar->workspace_min_width = width;
|
||||
}
|
||||
|
||||
CFGFUN(bar_strip_workspace_numbers, const char *value) {
|
||||
current_bar->strip_workspace_numbers = eval_boolstr(value);
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ static void push_string(const char *identifier, const char *str) {
|
|||
fprintf(stderr, "BUG: config_parser stack full. This means either a bug "
|
||||
"in the code, or a new command which contains more than "
|
||||
"10 identified tokens.\n");
|
||||
exit(1);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void push_long(const char *identifier, long num) {
|
||||
|
@ -146,7 +146,7 @@ static void push_long(const char *identifier, long num) {
|
|||
fprintf(stderr, "BUG: config_parser stack full. This means either a bug "
|
||||
"in the code, or a new command which contains more than "
|
||||
"10 identified tokens.\n");
|
||||
exit(1);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static const char *get_string(const char *identifier) {
|
||||
|
|
|
@ -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) {
|
||||
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("Geometry = (%d, %d) with %d x %d\n", con->geometry.x, con->geometry.y, con->geometry.width, con->geometry.height);
|
||||
Rect zero = {0, 0, 0, 0};
|
||||
nc->rect = con->geometry;
|
||||
/* If the geometry was not set (split containers), we need to determine a
|
||||
* sensible one by combining the geometry of all children */
|
||||
if (memcmp(&(nc->rect), &zero, sizeof(Rect)) == 0) {
|
||||
if (rect_equals(nc->rect, (Rect){0, 0, 0, 0})) {
|
||||
DLOG("Geometry not set, combining children\n");
|
||||
Con *child;
|
||||
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
|
||||
|
@ -372,6 +371,7 @@ void floating_enable(Con *con, bool automatic) {
|
|||
if (nc->rect.x == 0 && nc->rect.y == 0) {
|
||||
Con *leader;
|
||||
if (con->window && con->window->leader != XCB_NONE &&
|
||||
con->window->id != con->window->leader &&
|
||||
(leader = con_by_window_id(con->window->leader)) != NULL) {
|
||||
DLOG("Centering above leader\n");
|
||||
floating_center(nc, leader->rect);
|
||||
|
@ -421,7 +421,7 @@ void floating_enable(Con *con, bool automatic) {
|
|||
ipc_send_window_event("floating", con);
|
||||
}
|
||||
|
||||
void floating_disable(Con *con, bool automatic) {
|
||||
void floating_disable(Con *con) {
|
||||
if (!con_is_floating(con)) {
|
||||
LOG("Container isn't floating, not doing anything.\n");
|
||||
return;
|
||||
|
@ -469,7 +469,7 @@ void toggle_floating_mode(Con *con, bool automatic) {
|
|||
if (con_is_floating(con)) {
|
||||
LOG("already floating, re-setting to tiling\n");
|
||||
|
||||
floating_disable(con, automatic);
|
||||
floating_disable(con);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -492,6 +492,11 @@ void floating_raise_con(Con *con) {
|
|||
*
|
||||
*/
|
||||
bool floating_maybe_reassign_ws(Con *con) {
|
||||
if (con_is_internal(con_get_workspace(con))) {
|
||||
DLOG("Con in an internal workspace\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
Output *output = get_output_from_rect(con->rect);
|
||||
|
||||
if (!output) {
|
||||
|
@ -561,8 +566,6 @@ void floating_move_to_pointer(Con *con) {
|
|||
}
|
||||
|
||||
DRAGGING_CB(drag_window_callback) {
|
||||
const struct xcb_button_press_event_t *event = extra;
|
||||
|
||||
/* Reposition the client correctly while moving */
|
||||
con->rect.x = old_rect->x + (new_x - event->root_x);
|
||||
con->rect.y = old_rect->y + (new_y - event->root_y);
|
||||
|
@ -584,7 +587,7 @@ DRAGGING_CB(drag_window_callback) {
|
|||
* Calls the drag_pointer function with the drag_window callback
|
||||
*
|
||||
*/
|
||||
void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
|
||||
void floating_drag_window(Con *con, const xcb_button_press_event_t *event, bool use_threshold) {
|
||||
DLOG("floating_drag_window\n");
|
||||
|
||||
/* Push changes before dragging, so that the window gets raised now and not
|
||||
|
@ -595,7 +598,7 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
|
|||
Rect initial_rect = con->rect;
|
||||
|
||||
/* Drag the window */
|
||||
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
|
||||
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, use_threshold, drag_window_callback, NULL);
|
||||
|
||||
if (!con_exists(con)) {
|
||||
DLOG("The container has been closed in the meantime.\n");
|
||||
|
@ -625,12 +628,10 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
|
|||
struct resize_window_callback_params {
|
||||
const border_t corner;
|
||||
const bool proportional;
|
||||
const xcb_button_press_event_t *event;
|
||||
};
|
||||
|
||||
DRAGGING_CB(resize_window_callback) {
|
||||
const struct resize_window_callback_params *params = extra;
|
||||
const xcb_button_press_event_t *event = params->event;
|
||||
border_t corner = params->corner;
|
||||
|
||||
int32_t dest_x = con->rect.x;
|
||||
|
@ -706,12 +707,12 @@ void floating_resize_window(Con *con, const bool proportional,
|
|||
cursor = (corner & BORDER_LEFT) ? XCURSOR_CURSOR_BOTTOM_LEFT_CORNER : XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER;
|
||||
}
|
||||
|
||||
struct resize_window_callback_params params = {corner, proportional, event};
|
||||
struct resize_window_callback_params params = {corner, proportional};
|
||||
|
||||
/* get the initial rect in case of revert/cancel */
|
||||
Rect initial_rect = con->rect;
|
||||
|
||||
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, ¶ms);
|
||||
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, cursor, false, resize_window_callback, ¶ms);
|
||||
|
||||
if (!con_exists(con)) {
|
||||
DLOG("The container has been closed in the meantime.\n");
|
||||
|
@ -727,209 +728,6 @@ void floating_resize_window(Con *con, const bool proportional,
|
|||
con->scratchpad_state = SCRATCHPAD_CHANGED;
|
||||
}
|
||||
|
||||
/* Custom data structure used to track dragging-related events. */
|
||||
struct drag_x11_cb {
|
||||
ev_prepare prepare;
|
||||
|
||||
/* Whether this modal event loop should be exited and with which result. */
|
||||
drag_result_t result;
|
||||
|
||||
/* The container that is being dragged or resized, or NULL if this is a
|
||||
* drag of the resize handle. */
|
||||
Con *con;
|
||||
|
||||
/* The dimensions of con when the loop was started. */
|
||||
Rect old_rect;
|
||||
|
||||
/* The callback to invoke after every pointer movement. */
|
||||
callback_t callback;
|
||||
|
||||
/* User data pointer for callback. */
|
||||
const void *extra;
|
||||
};
|
||||
|
||||
static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
|
||||
xcb_motion_notify_event_t *last_motion_notify = NULL;
|
||||
xcb_generic_event_t *event;
|
||||
|
||||
while ((event = xcb_poll_for_event(conn)) != NULL) {
|
||||
if (event->response_type == 0) {
|
||||
xcb_generic_error_t *error = (xcb_generic_error_t *)event;
|
||||
DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n",
|
||||
error->sequence, error->error_code);
|
||||
free(event);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Strip off the highest bit (set if the event is generated) */
|
||||
int type = (event->response_type & 0x7F);
|
||||
|
||||
switch (type) {
|
||||
case XCB_BUTTON_RELEASE:
|
||||
dragloop->result = DRAG_SUCCESS;
|
||||
break;
|
||||
|
||||
case XCB_KEY_PRESS:
|
||||
DLOG("A key was pressed during drag, reverting changes.\n");
|
||||
dragloop->result = DRAG_REVERT;
|
||||
handle_event(type, event);
|
||||
break;
|
||||
|
||||
case XCB_UNMAP_NOTIFY: {
|
||||
xcb_unmap_notify_event_t *unmap_event = (xcb_unmap_notify_event_t *)event;
|
||||
Con *con = con_by_window_id(unmap_event->window);
|
||||
|
||||
if (con != NULL) {
|
||||
DLOG("UnmapNotify for window 0x%08x (container %p)\n", unmap_event->window, con);
|
||||
|
||||
if (con_get_workspace(con) == con_get_workspace(focused)) {
|
||||
DLOG("UnmapNotify for a managed window on the current workspace, aborting\n");
|
||||
dragloop->result = DRAG_ABORT;
|
||||
}
|
||||
}
|
||||
|
||||
handle_event(type, event);
|
||||
break;
|
||||
}
|
||||
|
||||
case XCB_MOTION_NOTIFY:
|
||||
/* motion_notify events are saved for later */
|
||||
FREE(last_motion_notify);
|
||||
last_motion_notify = (xcb_motion_notify_event_t *)event;
|
||||
break;
|
||||
|
||||
default:
|
||||
DLOG("Passing to original handler\n");
|
||||
handle_event(type, event);
|
||||
break;
|
||||
}
|
||||
|
||||
if (last_motion_notify != (xcb_motion_notify_event_t *)event)
|
||||
free(event);
|
||||
|
||||
if (dragloop->result != DRAGGING) {
|
||||
ev_break(EV_A_ EVBREAK_ONE);
|
||||
if (dragloop->result == DRAG_SUCCESS) {
|
||||
/* Ensure motion notify events are handled. */
|
||||
break;
|
||||
} else {
|
||||
free(last_motion_notify);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (last_motion_notify == NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Ensure that we are either dragging the resize handle (con is NULL) or that the
|
||||
* container still exists. The latter might not be true, e.g., if the window closed
|
||||
* for any reason while the user was dragging it. */
|
||||
if (!dragloop->con || con_exists(dragloop->con)) {
|
||||
dragloop->callback(
|
||||
dragloop->con,
|
||||
&(dragloop->old_rect),
|
||||
last_motion_notify->root_x,
|
||||
last_motion_notify->root_y,
|
||||
dragloop->extra);
|
||||
}
|
||||
FREE(last_motion_notify);
|
||||
|
||||
xcb_flush(conn);
|
||||
return dragloop->result != DRAGGING;
|
||||
}
|
||||
|
||||
static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
|
||||
struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
|
||||
while (!drain_drag_events(EV_A, dragloop)) {
|
||||
/* repeatedly drain events: draining might produce additional ones */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function grabs your pointer and keyboard and lets you drag stuff around
|
||||
* (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
|
||||
* be received and the given callback will be called with the parameters
|
||||
* specified (client, border on which the click originally was), the original
|
||||
* rect of the client, the event and the new coordinates (x, y).
|
||||
*
|
||||
*/
|
||||
drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to,
|
||||
border_t border, int cursor, callback_t callback, const void *extra) {
|
||||
xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE;
|
||||
|
||||
/* Grab the pointer */
|
||||
xcb_grab_pointer_cookie_t cookie;
|
||||
xcb_grab_pointer_reply_t *reply;
|
||||
xcb_generic_error_t *error;
|
||||
|
||||
cookie = xcb_grab_pointer(conn,
|
||||
false, /* get all pointer events specified by the following mask */
|
||||
root, /* grab the root window */
|
||||
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
|
||||
XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
|
||||
XCB_GRAB_MODE_ASYNC, /* keyboard mode */
|
||||
confine_to, /* confine_to = in which window should the cursor stay */
|
||||
xcursor, /* possibly display a special cursor */
|
||||
XCB_CURRENT_TIME);
|
||||
|
||||
if ((reply = xcb_grab_pointer_reply(conn, cookie, &error)) == NULL) {
|
||||
ELOG("Could not grab pointer (error_code = %d)\n", error->error_code);
|
||||
free(error);
|
||||
return DRAG_ABORT;
|
||||
}
|
||||
|
||||
free(reply);
|
||||
|
||||
/* Grab the keyboard */
|
||||
xcb_grab_keyboard_cookie_t keyb_cookie;
|
||||
xcb_grab_keyboard_reply_t *keyb_reply;
|
||||
|
||||
keyb_cookie = xcb_grab_keyboard(conn,
|
||||
false, /* get all keyboard events */
|
||||
root, /* grab the root window */
|
||||
XCB_CURRENT_TIME,
|
||||
XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */
|
||||
XCB_GRAB_MODE_ASYNC /* keyboard mode */
|
||||
);
|
||||
|
||||
if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, &error)) == NULL) {
|
||||
ELOG("Could not grab keyboard (error_code = %d)\n", error->error_code);
|
||||
free(error);
|
||||
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
|
||||
return DRAG_ABORT;
|
||||
}
|
||||
|
||||
free(keyb_reply);
|
||||
|
||||
/* Go into our own event loop */
|
||||
struct drag_x11_cb loop = {
|
||||
.result = DRAGGING,
|
||||
.con = con,
|
||||
.callback = callback,
|
||||
.extra = extra,
|
||||
};
|
||||
ev_prepare *prepare = &loop.prepare;
|
||||
if (con)
|
||||
loop.old_rect = con->rect;
|
||||
ev_prepare_init(prepare, xcb_drag_prepare_cb);
|
||||
prepare->data = &loop;
|
||||
main_set_x11_cb(false);
|
||||
ev_prepare_start(main_loop, prepare);
|
||||
|
||||
ev_loop(main_loop, 0);
|
||||
|
||||
ev_prepare_stop(main_loop, prepare);
|
||||
main_set_x11_cb(true);
|
||||
|
||||
xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
|
||||
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
|
||||
xcb_flush(conn);
|
||||
|
||||
return loop.result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Repositions the CT_FLOATING_CON to have the coordinates specified by
|
||||
* newrect, but only if the coordinates are not out-of-bounds. Also reassigns
|
||||
|
|
|
@ -412,7 +412,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
|
|||
if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(workspace))) {
|
||||
DLOG("Focusing con = %p\n", con);
|
||||
workspace_show(workspace);
|
||||
con_activate(con);
|
||||
con_activate_unblock(con);
|
||||
tree_render();
|
||||
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(workspace))) {
|
||||
DLOG("Marking con = %p urgent\n", con);
|
||||
|
@ -441,7 +441,7 @@ static void handle_screen_change(xcb_generic_event_t *e) {
|
|||
xcb_get_geometry_reply_t *reply = xcb_get_geometry_reply(conn, cookie, NULL);
|
||||
if (reply == NULL) {
|
||||
ELOG("Could not get geometry of the root window, exiting\n");
|
||||
exit(1);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
DLOG("root geometry reply: (%d, %d) %d x %d\n", reply->x, reply->y, reply->width, reply->height);
|
||||
|
||||
|
@ -565,7 +565,9 @@ static bool handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t
|
|||
|
||||
char *old_name = (con->window->name != NULL ? sstrdup(i3string_as_utf8(con->window->name)) : NULL);
|
||||
|
||||
window_update_name(con->window, prop, false);
|
||||
window_update_name(con->window, prop);
|
||||
|
||||
con = remanage_window(con);
|
||||
|
||||
x_push_changes(croot);
|
||||
|
||||
|
@ -590,7 +592,9 @@ static bool handle_windowname_change_legacy(void *data, xcb_connection_t *conn,
|
|||
|
||||
char *old_name = (con->window->name != NULL ? sstrdup(i3string_as_utf8(con->window->name)) : NULL);
|
||||
|
||||
window_update_name_legacy(con->window, prop, false);
|
||||
window_update_name_legacy(con->window, prop);
|
||||
|
||||
con = remanage_window(con);
|
||||
|
||||
x_push_changes(croot);
|
||||
|
||||
|
@ -612,7 +616,9 @@ static bool handle_windowrole_change(void *data, xcb_connection_t *conn, uint8_t
|
|||
if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
|
||||
return false;
|
||||
|
||||
window_update_role(con->window, prop, false);
|
||||
window_update_role(con->window, prop);
|
||||
|
||||
con = remanage_window(con);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -752,7 +758,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
|||
workspace_show(ws);
|
||||
/* Re-set focus, even if unchanged from i3’s perspective. */
|
||||
focused_id = XCB_NONE;
|
||||
con_activate(con);
|
||||
con_activate_unblock(con);
|
||||
}
|
||||
} else {
|
||||
/* Request is from an application. */
|
||||
|
@ -763,8 +769,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
|||
|
||||
if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
|
||||
DLOG("Focusing con = %p\n", con);
|
||||
workspace_show(ws);
|
||||
con_activate(con);
|
||||
con_activate_unblock(con);
|
||||
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
|
||||
DLOG("Marking con = %p urgent\n", con);
|
||||
con_set_urgency(con, true);
|
||||
|
@ -907,7 +912,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
|||
.event_y = y_root - (con->rect.y)};
|
||||
switch (direction) {
|
||||
case _NET_WM_MOVERESIZE_MOVE:
|
||||
floating_drag_window(con->parent, &fake);
|
||||
floating_drag_window(con->parent, &fake, false);
|
||||
break;
|
||||
case _NET_WM_MOVERESIZE_SIZE_TOPLEFT ... _NET_WM_MOVERESIZE_SIZE_LEFT:
|
||||
floating_resize_window(con->parent, false, &fake);
|
||||
|
@ -1109,14 +1114,8 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
|
|||
|
||||
DLOG("focus is different / refocusing floating window: updating decorations\n");
|
||||
|
||||
/* Get the currently focused workspace to check if the focus change also
|
||||
* involves changing workspaces. If so, we need to call workspace_show() to
|
||||
* correctly update state and send the IPC event. */
|
||||
Con *ws = con_get_workspace(con);
|
||||
if (ws != con_get_workspace(focused))
|
||||
workspace_show(ws);
|
||||
con_activate_unblock(con);
|
||||
|
||||
con_activate(con);
|
||||
/* We update focused_id because we don’t need to set focus again */
|
||||
focused_id = event->event;
|
||||
tree_render();
|
||||
|
@ -1158,7 +1157,9 @@ static bool handle_class_change(void *data, xcb_connection_t *conn, uint8_t stat
|
|||
return false;
|
||||
}
|
||||
|
||||
window_update_class(con->window, prop, false);
|
||||
window_update_class(con->window, prop);
|
||||
|
||||
con = remanage_window(con);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
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
|
||||
y(null);
|
||||
|
||||
ystr("window_type");
|
||||
if (con->window) {
|
||||
if (con->window->window_type == A__NET_WM_WINDOW_TYPE_NORMAL) {
|
||||
ystr("normal");
|
||||
} else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_DOCK) {
|
||||
ystr("dock");
|
||||
} else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_DIALOG) {
|
||||
ystr("dialog");
|
||||
} else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_UTILITY) {
|
||||
ystr("utility");
|
||||
} else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_TOOLBAR) {
|
||||
ystr("toolbar");
|
||||
} else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_SPLASH) {
|
||||
ystr("splash");
|
||||
} else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_MENU) {
|
||||
ystr("menu");
|
||||
} else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU) {
|
||||
ystr("dropdown_menu");
|
||||
} else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_POPUP_MENU) {
|
||||
ystr("popup_menu");
|
||||
} else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_TOOLTIP) {
|
||||
ystr("tooltip");
|
||||
} else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_NOTIFICATION) {
|
||||
ystr("notification");
|
||||
} else {
|
||||
ystr("unknown");
|
||||
}
|
||||
} else
|
||||
y(null);
|
||||
|
||||
if (con->window && !inplace_restart) {
|
||||
/* Window properties are useless to preserve when restarting because
|
||||
* they will be queried again anyway. However, for i3-save-tree(1),
|
||||
|
@ -789,6 +819,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
|
|||
ystr("workspace_buttons");
|
||||
y(bool, !config->hide_workspace_buttons);
|
||||
|
||||
ystr("workspace_min_width");
|
||||
y(integer, config->workspace_min_width);
|
||||
|
||||
ystr("strip_workspace_numbers");
|
||||
y(bool, config->strip_workspace_numbers);
|
||||
|
||||
|
@ -873,6 +906,9 @@ IPC_HANDLER(get_workspaces) {
|
|||
assert(ws->type == CT_WORKSPACE);
|
||||
y(map_open);
|
||||
|
||||
ystr("id");
|
||||
y(integer, (uintptr_t)ws);
|
||||
|
||||
ystr("num");
|
||||
y(integer, ws->num);
|
||||
|
||||
|
|
|
@ -141,8 +141,7 @@ static int json_end_map(void *ctx) {
|
|||
// Also set a size if none was supplied, otherwise the placeholder
|
||||
// window cannot be created as X11 requests with width=0 or
|
||||
// height=0 are invalid.
|
||||
const Rect zero = {0, 0, 0, 0};
|
||||
if (memcmp(&(json_node->rect), &zero, sizeof(Rect)) == 0) {
|
||||
if (rect_equals(json_node->rect, (Rect){0, 0, 0, 0})) {
|
||||
DLOG("Geometry not set, combining children\n");
|
||||
Con *child;
|
||||
TAILQ_FOREACH(child, &(json_node->nodes_head), nodes) {
|
||||
|
|
14
src/main.c
14
src/main.c
|
@ -93,6 +93,11 @@ bool shape_supported = true;
|
|||
|
||||
bool force_xinerama = false;
|
||||
|
||||
/* Define all atoms as global variables */
|
||||
#define xmacro(atom) xcb_atom_t A_##atom;
|
||||
#include "atoms.xmacro"
|
||||
#undef xmacro
|
||||
|
||||
/*
|
||||
* This callback is only a dummy, see xcb_prepare_cb.
|
||||
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
|
||||
|
@ -429,12 +434,12 @@ int main(int argc, char *argv[]) {
|
|||
"\ti3 floating toggle\n"
|
||||
"\ti3 kill window\n"
|
||||
"\n");
|
||||
exit(EXIT_FAILURE);
|
||||
exit(opt == 'h' ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (only_check_config) {
|
||||
exit(load_configuration(override_configpath, C_VALIDATE) ? 0 : 1);
|
||||
exit(load_configuration(override_configpath, C_VALIDATE) ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* If the user passes more arguments, we act like i3-msg would: Just send
|
||||
|
@ -813,12 +818,13 @@ int main(int argc, char *argv[]) {
|
|||
if (!output) {
|
||||
ELOG("ERROR: No screen at (%d, %d), starting on the first screen\n",
|
||||
pointerreply->root_x, pointerreply->root_y);
|
||||
}
|
||||
}
|
||||
if (!output) {
|
||||
output = get_first_output();
|
||||
}
|
||||
|
||||
con_activate(con_descend_focused(output_get_content(output->con)));
|
||||
free(pointerreply);
|
||||
}
|
||||
|
||||
tree_render();
|
||||
|
||||
|
|
107
src/manage.c
107
src/manage.c
|
@ -13,6 +13,34 @@
|
|||
|
||||
#include <yajl/yajl_gen.h>
|
||||
|
||||
/*
|
||||
* Match frame and window depth. This is needed because X will refuse to reparent a
|
||||
* window whose background is ParentRelative under a window with a different depth.
|
||||
*
|
||||
*/
|
||||
static xcb_window_t _match_depth(i3Window *win, Con *con) {
|
||||
xcb_window_t old_frame = XCB_NONE;
|
||||
if (con->depth != win->depth) {
|
||||
old_frame = con->frame.id;
|
||||
con->depth = win->depth;
|
||||
x_con_reframe(con);
|
||||
}
|
||||
return old_frame;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove all match criteria, the first swallowed window wins.
|
||||
*
|
||||
*/
|
||||
static void _remove_matches(Con *con) {
|
||||
while (!TAILQ_EMPTY(&(con->swallow_head))) {
|
||||
Match *first = TAILQ_FIRST(&(con->swallow_head));
|
||||
TAILQ_REMOVE(&(con->swallow_head), first, matches);
|
||||
match_free(first);
|
||||
free(first);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Go through all existing windows (if the window manager is restarted) and manage them
|
||||
*
|
||||
|
@ -174,13 +202,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
|||
FREE(buttons);
|
||||
|
||||
/* update as much information as possible so far (some replies may be NULL) */
|
||||
window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true);
|
||||
window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL), true);
|
||||
window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL), true);
|
||||
window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL));
|
||||
window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL));
|
||||
window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL));
|
||||
window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
|
||||
window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
|
||||
window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
|
||||
window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL), true);
|
||||
window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL));
|
||||
bool urgency_hint;
|
||||
window_update_hints(cwindow, xcb_get_property_reply(conn, wm_hints_cookie, NULL), &urgency_hint);
|
||||
border_style_t motif_border_style = BS_NORMAL;
|
||||
|
@ -341,24 +369,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
|||
DLOG("Uh?! Container without a placeholder, but with a window, has swallowed this to-be-managed window?!\n");
|
||||
} else {
|
||||
/* Remove remaining criteria, the first swallowed window wins. */
|
||||
while (!TAILQ_EMPTY(&(nc->swallow_head))) {
|
||||
Match *first = TAILQ_FIRST(&(nc->swallow_head));
|
||||
TAILQ_REMOVE(&(nc->swallow_head), first, matches);
|
||||
match_free(first);
|
||||
free(first);
|
||||
}
|
||||
_remove_matches(nc);
|
||||
}
|
||||
}
|
||||
xcb_window_t old_frame = XCB_NONE;
|
||||
if (nc->window != cwindow && nc->window != NULL) {
|
||||
window_free(nc->window);
|
||||
/* Match frame and window depth. This is needed because X will refuse to reparent a
|
||||
* window whose background is ParentRelative under a window with a different depth. */
|
||||
if (nc->depth != cwindow->depth) {
|
||||
old_frame = nc->frame.id;
|
||||
nc->depth = cwindow->depth;
|
||||
x_con_reframe(nc);
|
||||
}
|
||||
old_frame = _match_depth(cwindow, nc);
|
||||
}
|
||||
nc->window = cwindow;
|
||||
x_reinit(nc);
|
||||
|
@ -594,6 +611,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
|||
}
|
||||
render_con(croot);
|
||||
|
||||
cwindow->managed_since = time(NULL);
|
||||
|
||||
/* Send an event about window creation */
|
||||
ipc_send_window_event("new", nc);
|
||||
|
||||
|
@ -670,3 +689,57 @@ geom_out:
|
|||
out:
|
||||
free(attr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remanages a window: performs a swallow check and runs assignments.
|
||||
* Returns con for the window regardless if it updated.
|
||||
*
|
||||
*/
|
||||
Con *remanage_window(Con *con) {
|
||||
Match *match;
|
||||
Con *nc = con_for_window(croot, con->window, &match);
|
||||
if (nc == NULL || nc->window == NULL || nc->window == con->window) {
|
||||
run_assignments(con->window);
|
||||
return con;
|
||||
}
|
||||
/* Make sure the placeholder that wants to swallow this window didn't spawn
|
||||
* after the window to follow current behavior: adding a placeholder won't
|
||||
* swallow windows currently managed. */
|
||||
if (nc->window->managed_since > con->window->managed_since) {
|
||||
run_assignments(con->window);
|
||||
return con;
|
||||
}
|
||||
|
||||
if (!restore_kill_placeholder(nc->window->id)) {
|
||||
DLOG("Uh?! Container without a placeholder, but with a window, has swallowed this managed window?!\n");
|
||||
} else {
|
||||
_remove_matches(nc);
|
||||
}
|
||||
window_free(nc->window);
|
||||
|
||||
xcb_window_t old_frame = _match_depth(con->window, nc);
|
||||
|
||||
x_reparent_child(nc, con);
|
||||
|
||||
bool moved_workpaces = (con_get_workspace(nc) != con_get_workspace(con));
|
||||
|
||||
con_merge_into(con, nc);
|
||||
|
||||
/* Destroy the old frame if we had to reframe the container. This needs to be done
|
||||
* after rendering in order to prevent the background from flickering in its place. */
|
||||
if (old_frame != XCB_NONE) {
|
||||
xcb_destroy_window(conn, old_frame);
|
||||
}
|
||||
|
||||
run_assignments(nc->window);
|
||||
|
||||
if (moved_workpaces) {
|
||||
/* If the window is associated with a startup sequence, delete it so
|
||||
* child windows won't be created on the old workspace. */
|
||||
startup_sequence_delete_by_window(nc->window);
|
||||
|
||||
ewmh_update_wm_desktop();
|
||||
}
|
||||
|
||||
return nc;
|
||||
}
|
||||
|
|
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 */
|
||||
FREE(con->deco_render_params);
|
||||
|
||||
tree_flatten(croot);
|
||||
ipc_send_window_event("move", con);
|
||||
tree_flatten(croot);
|
||||
ewmh_update_wm_desktop();
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the given container in the given direction (D_LEFT, D_RIGHT,
|
||||
* D_UP, D_DOWN).
|
||||
* Moves the given container in the given direction
|
||||
*
|
||||
*/
|
||||
void tree_move(Con *con, int direction) {
|
||||
void tree_move(Con *con, direction_t direction) {
|
||||
position_t position;
|
||||
Con *target;
|
||||
|
||||
|
@ -283,7 +282,7 @@ void tree_move(Con *con, int direction) {
|
|||
if (!same_orientation) {
|
||||
if (con_is_floating(con)) {
|
||||
/* this is a floating con, we just disable floating */
|
||||
floating_disable(con, true);
|
||||
floating_disable(con);
|
||||
return;
|
||||
}
|
||||
if (con_inside_floating(con)) {
|
||||
|
@ -385,7 +384,7 @@ end:
|
|||
/* force re-painting the indicators */
|
||||
FREE(con->deco_render_params);
|
||||
|
||||
tree_flatten(croot);
|
||||
ipc_send_window_event("move", con);
|
||||
tree_flatten(croot);
|
||||
ewmh_update_wm_desktop();
|
||||
}
|
||||
|
|
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 *output;
|
||||
Output *output, *result = NULL;
|
||||
|
||||
TAILQ_FOREACH(output, &outputs, outputs)
|
||||
if (output->active)
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
if (output->active) {
|
||||
if (output->primary) {
|
||||
return output;
|
||||
}
|
||||
if (!result) {
|
||||
result = output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
die("No usable outputs available.\n");
|
||||
}
|
||||
|
|
47
src/resize.c
47
src/resize.c
|
@ -21,12 +21,32 @@ struct callback_params {
|
|||
Con *output;
|
||||
xcb_window_t helpwin;
|
||||
uint32_t *new_position;
|
||||
bool *threshold_exceeded;
|
||||
};
|
||||
|
||||
DRAGGING_CB(resize_callback) {
|
||||
const struct callback_params *params = extra;
|
||||
Con *output = params->output;
|
||||
DLOG("new x = %d, y = %d\n", new_x, new_y);
|
||||
|
||||
if (!*params->threshold_exceeded) {
|
||||
xcb_map_window(conn, params->helpwin);
|
||||
/* Warp pointer in the same way as resize_graphical_handler() would do
|
||||
* if threshold wasn't enabled, but also take into account travelled
|
||||
* distance. */
|
||||
if (params->orientation == HORIZ) {
|
||||
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
|
||||
*params->new_position + new_x - event->root_x,
|
||||
new_y);
|
||||
} else {
|
||||
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
|
||||
new_x,
|
||||
*params->new_position + new_y - event->root_y);
|
||||
}
|
||||
*params->threshold_exceeded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (params->orientation == HORIZ) {
|
||||
/* Check if the new coordinates are within screen boundaries */
|
||||
if (new_x > (output->rect.x + output->rect.width - 25) ||
|
||||
|
@ -148,7 +168,9 @@ bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
|
||||
void resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
|
||||
const xcb_button_press_event_t *event,
|
||||
bool use_threshold) {
|
||||
Con *output = con_get_output(first);
|
||||
DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width);
|
||||
|
||||
|
@ -179,14 +201,10 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
|
|||
helprect.width = logical_px(2);
|
||||
helprect.height = second->rect.height;
|
||||
initial_position = second->rect.x;
|
||||
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
|
||||
second->rect.x, event->root_y);
|
||||
} else {
|
||||
helprect.width = second->rect.width;
|
||||
helprect.height = logical_px(2);
|
||||
initial_position = second->rect.y;
|
||||
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
|
||||
event->root_x, second->rect.y);
|
||||
}
|
||||
|
||||
mask = XCB_CW_BACK_PIXEL;
|
||||
|
@ -196,7 +214,18 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
|
|||
values[1] = 1;
|
||||
|
||||
xcb_window_t helpwin = create_window(conn, helprect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT, (orientation == HORIZ ? XCURSOR_CURSOR_RESIZE_HORIZONTAL : XCURSOR_CURSOR_RESIZE_VERTICAL), true, mask, values);
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT, (orientation == HORIZ ? XCURSOR_CURSOR_RESIZE_HORIZONTAL : XCURSOR_CURSOR_RESIZE_VERTICAL), false, mask, values);
|
||||
|
||||
if (!use_threshold) {
|
||||
xcb_map_window(conn, helpwin);
|
||||
if (orientation == HORIZ) {
|
||||
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
|
||||
second->rect.x, event->root_y);
|
||||
} else {
|
||||
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
|
||||
event->root_x, second->rect.y);
|
||||
}
|
||||
}
|
||||
|
||||
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
|
||||
|
||||
|
@ -205,10 +234,12 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
|
|||
/* `new_position' will be updated by the `resize_callback'. */
|
||||
new_position = initial_position;
|
||||
|
||||
const struct callback_params params = {orientation, output, helpwin, &new_position};
|
||||
bool threshold_exceeded = !use_threshold;
|
||||
|
||||
const struct callback_params params = {orientation, output, helpwin, &new_position, &threshold_exceeded};
|
||||
|
||||
/* `drag_pointer' blocks until the drag is completed. */
|
||||
drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, ¶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, grabwin);
|
||||
|
|
|
@ -40,9 +40,7 @@ void scratchpad_move(Con *con) {
|
|||
|
||||
/* If the current con is in fullscreen mode, we need to disable that,
|
||||
* as a scratchpad window should never be in fullscreen mode */
|
||||
if (focused && focused->type != CT_WORKSPACE && focused->fullscreen_mode != CF_NONE) {
|
||||
con_toggle_fullscreen(focused, CF_OUTPUT);
|
||||
}
|
||||
con_disable_fullscreen(con);
|
||||
|
||||
/* 1: Ensure the window or any parent is floating. From now on, we deal
|
||||
* with the CT_FLOATING_CON. We use automatic == false because the user
|
||||
|
@ -279,7 +277,7 @@ void scratchpad_fix_resolution(void) {
|
|||
|
||||
Rect new_rect = __i3_output->rect;
|
||||
|
||||
if (memcmp(&old_rect, &new_rect, sizeof(Rect)) == 0) {
|
||||
if (rect_equals(new_rect, old_rect)) {
|
||||
DLOG("Scratchpad size unchanged.\n");
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ static int sighandler_backtrace(void) {
|
|||
NULL};
|
||||
execvp(args[0], args);
|
||||
DLOG("Failed to exec GDB\n");
|
||||
exit(1);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
int status = 0;
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ void start_application(const char *command, bool no_startup_id) {
|
|||
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL);
|
||||
/* not reached */
|
||||
}
|
||||
_exit(0);
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
wait(0);
|
||||
|
||||
|
@ -365,3 +365,22 @@ char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *
|
|||
|
||||
return sequence->workspace;
|
||||
}
|
||||
|
||||
/*
|
||||
* Deletes the startup sequence for a window if it exists.
|
||||
*
|
||||
*/
|
||||
void startup_sequence_delete_by_window(i3Window *win) {
|
||||
struct Startup_Sequence *sequence;
|
||||
xcb_get_property_cookie_t cookie;
|
||||
xcb_get_property_reply_t *startup_id_reply;
|
||||
|
||||
cookie = xcb_get_property(conn, false, win->id, A__NET_STARTUP_ID,
|
||||
XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
|
||||
startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
|
||||
|
||||
sequence = startup_sequence_get(win, startup_id_reply, true);
|
||||
if (sequence != NULL) {
|
||||
startup_sequence_delete(sequence);
|
||||
}
|
||||
}
|
||||
|
|
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_CW_EVENT_MASK, (uint32_t[]){XCB_NONE});
|
||||
xcb_unmap_window(conn, con->window->id);
|
||||
cookie = xcb_reparent_window(conn, con->window->id, root, 0, 0);
|
||||
cookie = xcb_reparent_window(conn, con->window->id, root, con->rect.x, con->rect.y);
|
||||
|
||||
/* Ignore X11 errors for the ReparentWindow request.
|
||||
* X11 Errors are returned when the window was already destroyed */
|
||||
|
@ -462,170 +462,175 @@ void tree_render(void) {
|
|||
DLOG("-- END RENDERING --\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
static Con *get_tree_next_workspace(Con *con, direction_t direction) {
|
||||
if (con_get_fullscreen_con(con, CF_GLOBAL)) {
|
||||
DLOG("Cannot change workspace while in global fullscreen mode.\n");
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
Output *current_output = get_output_containing(con->rect.x, con->rect.y);
|
||||
Output *next_output;
|
||||
|
||||
if (!current_output)
|
||||
return false;
|
||||
Output *current_output = get_output_containing(con->rect.x, con->rect.y);
|
||||
if (!current_output) {
|
||||
return NULL;
|
||||
}
|
||||
DLOG("Current output is %s\n", output_primary_name(current_output));
|
||||
|
||||
/* Try to find next output */
|
||||
direction_t direction;
|
||||
if (way == 'n' && orientation == HORIZ)
|
||||
direction = D_RIGHT;
|
||||
else if (way == 'p' && orientation == HORIZ)
|
||||
direction = D_LEFT;
|
||||
else if (way == 'n' && orientation == VERT)
|
||||
direction = D_DOWN;
|
||||
else if (way == 'p' && orientation == VERT)
|
||||
direction = D_UP;
|
||||
else
|
||||
return false;
|
||||
|
||||
next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
|
||||
if (!next_output)
|
||||
return false;
|
||||
Output *next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
|
||||
if (!next_output) {
|
||||
return NULL;
|
||||
}
|
||||
DLOG("Next output is %s\n", output_primary_name(next_output));
|
||||
|
||||
/* Find visible workspace on next output */
|
||||
Con *workspace = NULL;
|
||||
GREP_FIRST(workspace, output_get_content(next_output->con), workspace_is_visible(child));
|
||||
return workspace;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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. */
|
||||
if (!workspace)
|
||||
return false;
|
||||
|
||||
/* Use descend_focused first to give higher priority to floating or
|
||||
* tiling fullscreen containers. */
|
||||
Con *focus = con_descend_focused(workspace);
|
||||
Con *focus = con_descend_focused(next);
|
||||
if (focus->fullscreen_mode == CF_NONE) {
|
||||
Con *focus_tiling = con_descend_tiling_focused(workspace);
|
||||
Con *focus_tiling = con_descend_tiling_focused(next);
|
||||
/* If descend_tiling returned a workspace then focus is either a
|
||||
* floating container or the same workspace. */
|
||||
if (focus_tiling != workspace) {
|
||||
if (focus_tiling != next) {
|
||||
focus = focus_tiling;
|
||||
}
|
||||
}
|
||||
|
||||
workspace_show(workspace);
|
||||
workspace_show(next);
|
||||
con_activate(focus);
|
||||
x_set_warp_to(&(focus->rect));
|
||||
return true;
|
||||
}
|
||||
|
||||
Con *parent = con->parent;
|
||||
|
||||
if (con->type == CT_FLOATING_CON) {
|
||||
if (orientation != HORIZ)
|
||||
return false;
|
||||
|
||||
/* left/right focuses the previous/next floating container */
|
||||
Con *next;
|
||||
if (way == 'n')
|
||||
next = TAILQ_NEXT(con, floating_windows);
|
||||
else
|
||||
next = TAILQ_PREV(con, floating_head, floating_windows);
|
||||
|
||||
/* If there is no next/previous container, wrap */
|
||||
if (!next) {
|
||||
if (way == 'n')
|
||||
next = TAILQ_FIRST(&(parent->floating_head));
|
||||
else
|
||||
next = TAILQ_LAST(&(parent->floating_head), floating_head);
|
||||
}
|
||||
|
||||
/* Still no next/previous container? bail out */
|
||||
if (!next)
|
||||
return false;
|
||||
|
||||
/* Raise the floating window on top of other windows preserving
|
||||
* relative stack order */
|
||||
return;
|
||||
} else if (next->type == CT_FLOATING_CON) {
|
||||
/* Raise the floating window on top of other windows preserving relative
|
||||
* stack order */
|
||||
Con *parent = next->parent;
|
||||
while (TAILQ_LAST(&(parent->floating_head), floating_head) != next) {
|
||||
Con *last = TAILQ_LAST(&(parent->floating_head), floating_head);
|
||||
TAILQ_REMOVE(&(parent->floating_head), last, floating_windows);
|
||||
TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows);
|
||||
}
|
||||
}
|
||||
|
||||
workspace_show(con_get_workspace(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
|
||||
* (horizontal/vertical).
|
||||
* Get the previous / next sibling
|
||||
*
|
||||
*/
|
||||
void tree_next(char way, orientation_t orientation) {
|
||||
_tree_next(focused, way, orientation,
|
||||
config.focus_wrapping != FOCUS_WRAPPING_OFF);
|
||||
Con *get_tree_next_sibling(Con *con, position_t direction) {
|
||||
Con *to_focus = (direction == BEFORE ? TAILQ_PREV(con, nodes_head, nodes)
|
||||
: TAILQ_NEXT(con, nodes));
|
||||
if (to_focus && con_fullscreen_permits_focusing(to_focus)) {
|
||||
return to_focus;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
62
src/util.c
62
src/util.c
|
@ -53,6 +53,10 @@ Rect rect_sub(Rect a, Rect b) {
|
|||
a.height - b.height};
|
||||
}
|
||||
|
||||
bool rect_equals(Rect a, Rect b) {
|
||||
return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the name consists of only digits.
|
||||
*
|
||||
|
@ -159,7 +163,7 @@ void exec_i3_utility(char *name, char *argv[]) {
|
|||
char buffer[BUFSIZ];
|
||||
if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
|
||||
warn("could not read /proc/self/exe");
|
||||
_exit(1);
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
dir = dirname(buffer);
|
||||
sasprintf(&migratepath, "%s/%s", dir, name);
|
||||
|
@ -308,42 +312,6 @@ void i3_restart(bool forget_layout) {
|
|||
/* not reached */
|
||||
}
|
||||
|
||||
#if defined(__OpenBSD__) || defined(__APPLE__)
|
||||
|
||||
/*
|
||||
* Taken from FreeBSD
|
||||
* Find the first occurrence of the byte string s in byte string l.
|
||||
*
|
||||
*/
|
||||
void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) {
|
||||
register char *cur, *last;
|
||||
const char *cl = (const char *)l;
|
||||
const char *cs = (const char *)s;
|
||||
|
||||
/* we need something to compare */
|
||||
if (l_len == 0 || s_len == 0)
|
||||
return NULL;
|
||||
|
||||
/* "s" must be smaller or equal to "l" */
|
||||
if (l_len < s_len)
|
||||
return NULL;
|
||||
|
||||
/* special case where s_len == 1 */
|
||||
if (s_len == 1)
|
||||
return memchr(l, (int)*cs, l_len);
|
||||
|
||||
/* the last position where its possible to find "s" in "l" */
|
||||
last = (char *)cl + l_len - s_len;
|
||||
|
||||
for (cur = (char *)cl; cur <= last; cur++)
|
||||
if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
|
||||
return cur;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Escapes the given string if a pango font is currently used.
|
||||
* If the string has to be escaped, the input string will be free'd.
|
||||
|
@ -514,3 +482,23 @@ ssize_t slurp(const char *path, char **buf) {
|
|||
orientation_t orientation_from_direction(direction_t direction) {
|
||||
return (direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a direction to its corresponding position.
|
||||
*
|
||||
*/
|
||||
position_t position_from_direction(direction_t direction) {
|
||||
return (direction == D_LEFT || direction == D_UP) ? BEFORE : AFTER;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert orientation and position to the corresponding direction.
|
||||
*
|
||||
*/
|
||||
direction_t direction_from_orientation_position(orientation_t orientation, position_t position) {
|
||||
if (orientation == HORIZ) {
|
||||
return position == BEFORE ? D_LEFT : D_RIGHT;
|
||||
} else {
|
||||
return position == BEFORE ? D_UP : D_DOWN;
|
||||
}
|
||||
}
|
||||
|
|
20
src/window.c
20
src/window.c
|
@ -26,7 +26,7 @@ void window_free(i3Window *win) {
|
|||
* given window.
|
||||
*
|
||||
*/
|
||||
void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
|
||||
void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) {
|
||||
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||
DLOG("WM_CLASS not set.\n");
|
||||
FREE(prop);
|
||||
|
@ -52,9 +52,6 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool bef
|
|||
win->class_instance, win->class_class);
|
||||
|
||||
free(prop);
|
||||
if (!before_mgmt) {
|
||||
run_assignments(win);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -62,7 +59,7 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool bef
|
|||
* window. Further updates using window_update_name_legacy will be ignored.
|
||||
*
|
||||
*/
|
||||
void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
|
||||
void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) {
|
||||
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||
DLOG("_NET_WM_NAME not specified, not changing\n");
|
||||
FREE(prop);
|
||||
|
@ -89,9 +86,6 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
|
|||
win->uses_net_wm_name = true;
|
||||
|
||||
free(prop);
|
||||
if (!before_mgmt) {
|
||||
run_assignments(win);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -101,7 +95,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
|
|||
* window_update_name()).
|
||||
*
|
||||
*/
|
||||
void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
|
||||
void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) {
|
||||
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||
DLOG("WM_NAME not set (_NET_WM_NAME is what you want anyways).\n");
|
||||
FREE(prop);
|
||||
|
@ -134,9 +128,6 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo
|
|||
win->name_x_changed = true;
|
||||
|
||||
free(prop);
|
||||
if (!before_mgmt) {
|
||||
run_assignments(win);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -218,7 +209,7 @@ void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop)
|
|||
* Updates the WM_WINDOW_ROLE
|
||||
*
|
||||
*/
|
||||
void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
|
||||
void window_update_role(i3Window *win, xcb_get_property_reply_t *prop) {
|
||||
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||
DLOG("WM_WINDOW_ROLE not set.\n");
|
||||
FREE(prop);
|
||||
|
@ -233,9 +224,6 @@ void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool befo
|
|||
LOG("WM_WINDOW_ROLE changed to \"%s\"\n", win->role);
|
||||
|
||||
free(prop);
|
||||
if (!before_mgmt) {
|
||||
run_assignments(win);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -197,18 +197,27 @@ void extract_workspace_names_from_bindings(void) {
|
|||
while (*target == ' ' || *target == '\t')
|
||||
target++;
|
||||
/* We check if this is the workspace
|
||||
* next/prev/next_on_output/prev_on_output/back_and_forth/number command.
|
||||
* next/prev/next_on_output/prev_on_output/back_and_forth command.
|
||||
* Beware: The workspace names "next", "prev", "next_on_output",
|
||||
* "prev_on_output", "number", "back_and_forth" and "current" are OK,
|
||||
* "prev_on_output", "back_and_forth" and "current" are OK,
|
||||
* so we check before stripping the double quotes */
|
||||
if (strncasecmp(target, "next", strlen("next")) == 0 ||
|
||||
strncasecmp(target, "prev", strlen("prev")) == 0 ||
|
||||
strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
|
||||
strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 ||
|
||||
strncasecmp(target, "number", strlen("number")) == 0 ||
|
||||
strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 ||
|
||||
strncasecmp(target, "current", strlen("current")) == 0)
|
||||
continue;
|
||||
if (strncasecmp(target, "--no-auto-back-and-forth", strlen("--no-auto-back-and-forth")) == 0) {
|
||||
target += strlen("--no-auto-back-and-forth");
|
||||
while (*target == ' ' || *target == '\t')
|
||||
target++;
|
||||
}
|
||||
if (strncasecmp(target, "number", strlen("number")) == 0) {
|
||||
target += strlen("number");
|
||||
while (*target == ' ' || *target == '\t')
|
||||
target++;
|
||||
}
|
||||
char *target_name = parse_string(&target, false);
|
||||
if (target_name == NULL)
|
||||
continue;
|
||||
|
@ -983,11 +992,15 @@ void workspace_move_to_output(Con *ws, Output *output) {
|
|||
bool used_assignment = false;
|
||||
struct Workspace_Assignment *assignment;
|
||||
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
||||
bool attached;
|
||||
int num;
|
||||
if (!output_triggers_assignment(current_output, assignment)) {
|
||||
continue;
|
||||
}
|
||||
/* check if this workspace is already attached to the tree */
|
||||
if (get_existing_workspace_by_name(assignment->name) != NULL) {
|
||||
/* check if this workspace's name or num is already attached to the tree */
|
||||
num = ws_name_to_number(assignment->name);
|
||||
attached = ((num == -1) ? get_existing_workspace_by_name(assignment->name) : get_existing_workspace_by_num(num)) != NULL;
|
||||
if (attached) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
9
src/x.c
9
src/x.c
|
@ -14,8 +14,6 @@
|
|||
#define MAX(x, y) ((x) > (y) ? (x) : (y))
|
||||
#endif
|
||||
|
||||
xcb_window_t ewmh_window;
|
||||
|
||||
/* Stores the X11 window ID of the currently focused window */
|
||||
xcb_window_t focused_id = XCB_NONE;
|
||||
|
||||
|
@ -251,8 +249,7 @@ void x_move_win(Con *src, Con *dest) {
|
|||
state_dest->con = state_src->con;
|
||||
state_src->con = NULL;
|
||||
|
||||
Rect zero = {0, 0, 0, 0};
|
||||
if (memcmp(&(state_dest->window_rect), &(zero), sizeof(Rect)) == 0) {
|
||||
if (rect_equals(state_dest->window_rect, (Rect){0, 0, 0, 0})) {
|
||||
memcpy(&(state_dest->window_rect), &(state_src->window_rect), sizeof(Rect));
|
||||
DLOG("COPYING RECT\n");
|
||||
}
|
||||
|
@ -929,7 +926,7 @@ void x_push_node(Con *con) {
|
|||
bool fake_notify = false;
|
||||
/* Set new position if rect changed (and if height > 0) or if the pixmap
|
||||
* needs to be recreated */
|
||||
if ((is_pixmap_needed && con->frame_buffer.id == XCB_NONE) || (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0 &&
|
||||
if ((is_pixmap_needed && con->frame_buffer.id == XCB_NONE) || (!rect_equals(state->rect, rect) &&
|
||||
rect.height > 0)) {
|
||||
/* We first create the new pixmap, then render to it, set it as the
|
||||
* background and only afterwards change the window size. This reduces
|
||||
|
@ -1008,7 +1005,7 @@ void x_push_node(Con *con) {
|
|||
|
||||
/* dito, but for child windows */
|
||||
if (con->window != NULL &&
|
||||
memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) {
|
||||
!rect_equals(state->window_rect, con->window_rect)) {
|
||||
DLOG("setting window rect (%d, %d, %d, %d)\n",
|
||||
con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height);
|
||||
xcb_set_window_rect(conn, con->window->id, con->window_rect);
|
||||
|
|
|
@ -84,7 +84,7 @@ static void query_screens(xcb_connection_t *conn) {
|
|||
|
||||
if (num_screens == 0) {
|
||||
ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
|
||||
exit(0);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1230,7 +1230,7 @@ sub create_layout {
|
|||
|
||||
$r = $r . '{"swallows": [{';
|
||||
$r = $r . '"class": "^' . "$char" . '$"';
|
||||
$r = $r . '}]},';
|
||||
$r = $r . '}]}' . ($depth == 0 ? "\n" : ',');
|
||||
} else {
|
||||
die "Could not understand $char";
|
||||
}
|
||||
|
|
|
@ -35,9 +35,13 @@ my $bottom = open_window;
|
|||
# end sleeping for half a second to make sure i3 reacted
|
||||
#
|
||||
sub focus_after {
|
||||
my $msg = shift;
|
||||
my ($msg, $win_id) = @_;
|
||||
|
||||
if (defined($win_id)) {
|
||||
cmd "[id=$win_id] $msg";
|
||||
} else {
|
||||
cmd $msg;
|
||||
}
|
||||
return $x->input_focus;
|
||||
}
|
||||
|
||||
|
@ -50,6 +54,14 @@ is($focus, $mid->id, "Middle window focused");
|
|||
$focus = focus_after('focus up');
|
||||
is($focus, $top->id, "Top window focused");
|
||||
|
||||
# Same using command criteria
|
||||
$focus = focus_after('focus up', $bottom->id);
|
||||
is($focus, $mid->id, "Middle window focused");
|
||||
|
||||
cmd 'focus down';
|
||||
$focus = focus_after('focus up', $mid->id);
|
||||
is($focus, $top->id, "Top window focused");
|
||||
|
||||
#####################################################################
|
||||
# Test focus wrapping
|
||||
#####################################################################
|
||||
|
|
|
@ -53,6 +53,7 @@ my $expected = {
|
|||
name => 'root',
|
||||
orientation => $ignore,
|
||||
type => 'root',
|
||||
window_type => undef,
|
||||
id => $ignore,
|
||||
rect => $ignore,
|
||||
deco_rect => $ignore,
|
||||
|
|
|
@ -125,4 +125,22 @@ is_deeply(\@names, [ '3' ], 'i3 starts on workspace 3');
|
|||
|
||||
exit_gracefully($pid);
|
||||
|
||||
##############################################################
|
||||
# 7: verify optional flags do not affect startup workspace
|
||||
##############################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
bindsym Mod1+1 workspace --no-auto-back-and-forth number 3:three
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
@names = @{get_workspace_names()};
|
||||
is_deeply(\@names, [ '3:three' ], 'i3 starts on named workspace 3:three');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
done_testing;
|
||||
|
|
|
@ -171,6 +171,53 @@ cmd 'restart';
|
|||
cmd 'workspace back_and_forth';
|
||||
is(focused_ws, '5: foo', 'workspace 5 focused after restart');
|
||||
|
||||
################################################################################
|
||||
# Check BAF switching to renamed workspace.
|
||||
# Issue: #3694
|
||||
################################################################################
|
||||
|
||||
kill_all_windows;
|
||||
cmd 'workspace --no-auto-back-and-forth 1';
|
||||
open_window;
|
||||
cmd 'workspace --no-auto-back-and-forth 2';
|
||||
|
||||
cmd 'rename workspace 1 to 3';
|
||||
cmd 'workspace back_and_forth';
|
||||
is(focused_ws, '3', 'workspace 3 focused after rename');
|
||||
|
||||
################################################################################
|
||||
# Check BAF switching to renamed and then closed workspace.
|
||||
# Issue: #3694
|
||||
################################################################################
|
||||
|
||||
kill_all_windows;
|
||||
cmd 'workspace --no-auto-back-and-forth 1';
|
||||
$first_win = open_window;
|
||||
cmd 'workspace --no-auto-back-and-forth 2';
|
||||
|
||||
cmd 'rename workspace 1 to 3';
|
||||
cmd '[id="' . $first_win->id . '"] kill';
|
||||
|
||||
cmd 'workspace back_and_forth';
|
||||
is(focused_ws, '3', 'workspace 3 focused after renaming and destroying');
|
||||
|
||||
################################################################################
|
||||
# See if renaming current workspace doesn't affect BAF switching to another
|
||||
# renamed workspace.
|
||||
# Issue: #3694
|
||||
################################################################################
|
||||
|
||||
kill_all_windows;
|
||||
cmd 'workspace --no-auto-back-and-forth 1';
|
||||
$first_win = open_window;
|
||||
cmd 'workspace --no-auto-back-and-forth 2';
|
||||
|
||||
cmd 'rename workspace 1 to 3';
|
||||
cmd 'rename workspace 2 to 4';
|
||||
|
||||
cmd 'workspace back_and_forth';
|
||||
is(focused_ws, '3', 'workspace 3 focused after renaming');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
done_testing;
|
||||
|
|
|
@ -63,6 +63,7 @@ my $bar_config = $i3->get_bar_config($bar_id)->recv;
|
|||
is($bar_config->{status_command}, 'i3status --foo', 'status_command correct');
|
||||
ok(!$bar_config->{verbose}, 'verbose off by default');
|
||||
ok($bar_config->{workspace_buttons}, 'workspace buttons enabled per default');
|
||||
is($bar_config->{workspace_min_width}, 0, 'workspace_min_width ok');
|
||||
ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled per default');
|
||||
is($bar_config->{mode}, 'dock', 'dock mode by default');
|
||||
is($bar_config->{position}, 'bottom', 'position bottom by default');
|
||||
|
@ -102,6 +103,7 @@ bar {
|
|||
mode dock
|
||||
font Terminus
|
||||
workspace_buttons no
|
||||
workspace_min_width 30
|
||||
binding_mode_indicator no
|
||||
verbose yes
|
||||
socket_path /tmp/foobar
|
||||
|
@ -134,6 +136,7 @@ $bar_config = $i3->get_bar_config($bar_id)->recv;
|
|||
is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
|
||||
ok($bar_config->{verbose}, 'verbose on');
|
||||
ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled');
|
||||
is($bar_config->{workspace_min_width}, 30, 'workspace_min_width ok');
|
||||
ok(!$bar_config->{binding_mode_indicator}, 'mode indicator disabled');
|
||||
is($bar_config->{mode}, 'dock', 'dock mode');
|
||||
is($bar_config->{position}, 'top', 'position top');
|
||||
|
|
|
@ -532,5 +532,21 @@ is(scalar @{get_ws_content($ws)}, 0, 'no window in workspace');
|
|||
cmd 'scratchpad show';
|
||||
is($x->input_focus, $window->id, 'scratchpad window shown');
|
||||
|
||||
################################################################################
|
||||
# 19: move position commands do not show scratchpad window
|
||||
# See issue #3832
|
||||
################################################################################
|
||||
|
||||
kill_all_windows;
|
||||
|
||||
fresh_workspace;
|
||||
$first = open_window;
|
||||
$second = open_window;
|
||||
|
||||
cmd '[id=' . $first->id . '] move to scratchpad, move position 100 100';
|
||||
is ($x->input_focus, $second->id, 'moving scratchpad window does not show it');
|
||||
cmd '[id=' . $first->id . '] move position center';
|
||||
is ($x->input_focus, $second->id, 'centering scratchpad window does not show it');
|
||||
|
||||
|
||||
done_testing;
|
||||
|
|
|
@ -149,6 +149,22 @@ send_net_active_window($scratch->id, 'pager');
|
|||
|
||||
is($x->input_focus, $scratch->id, 'scratchpad window is shown');
|
||||
|
||||
################################################################################
|
||||
# Send a _NET_ACTIVE_WINDOW ClientMessage for a window behind a fullscreen
|
||||
# window
|
||||
################################################################################
|
||||
|
||||
$ws1 = fresh_workspace;
|
||||
$win1 = open_window;
|
||||
$win2 = open_window;
|
||||
cmd 'fullscreen enable';
|
||||
is_num_fullscreen($ws1, 1, '1 fullscreen window in workspace');
|
||||
|
||||
send_net_active_window($win1->id);
|
||||
|
||||
is($x->input_focus, $win1->id, 'window behind fullscreen window is now focused');
|
||||
is_num_fullscreen($ws1, 0, 'no fullscreen windows in workspace');
|
||||
|
||||
################################################################################
|
||||
# Verify that the _NET_ACTIVE_WINDOW property is updated on the root window
|
||||
# correctly.
|
||||
|
|
|
@ -733,7 +733,7 @@ EOT
|
|||
$expected = <<'EOT';
|
||||
cfg_bar_start()
|
||||
cfg_bar_output(LVDS-1)
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'colors', '}'
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'workspace_min_width', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'colors', '}'
|
||||
ERROR: CONFIG: (in file <stdin>)
|
||||
ERROR: CONFIG: Line 1: bar {
|
||||
ERROR: CONFIG: Line 2: output LVDS-1
|
||||
|
|
|
@ -21,9 +21,10 @@ use i3test;
|
|||
|
||||
my $tmp = fresh_workspace;
|
||||
|
||||
##########################################################################################
|
||||
# map two windows in one container, fullscreen one of them and then move it to scratchpad
|
||||
##########################################################################################
|
||||
###############################################################################
|
||||
# map two windows in one container, fullscreen one of them and then move it to
|
||||
# scratchpad
|
||||
###############################################################################
|
||||
|
||||
my $first_win = open_window;
|
||||
my $second_win = open_window;
|
||||
|
@ -50,9 +51,10 @@ cmd 'floating toggle';
|
|||
# see if no window is in fullscreen mode
|
||||
is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after showing previously fullscreened scratchpad window');
|
||||
|
||||
########################################################################################
|
||||
# move a window to scratchpad, focus parent container, make it fullscreen, focus a child
|
||||
########################################################################################
|
||||
###############################################################################
|
||||
# move a window to scratchpad, focus parent container, make it fullscreen,
|
||||
# focus a child
|
||||
###############################################################################
|
||||
|
||||
# make layout tabbed
|
||||
cmd 'layout tabbed';
|
||||
|
@ -72,9 +74,9 @@ cmd 'focus child';
|
|||
# see if the window really is in fullscreen mode
|
||||
is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after enabling fullscreen on parent');
|
||||
|
||||
##########################################################################
|
||||
###############################################################################
|
||||
# show a scratchpad window; no window should be in fullscreen mode anymore
|
||||
##########################################################################
|
||||
###############################################################################
|
||||
|
||||
# show the scratchpad window
|
||||
cmd 'scratchpad show';
|
||||
|
@ -82,4 +84,29 @@ cmd 'scratchpad show';
|
|||
# see if no window is in fullscreen mode
|
||||
is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after showing a scratchpad window while a parent container was in fullscreen mode');
|
||||
|
||||
###############################################################################
|
||||
# Moving window to scratchpad with command criteria does not unfullscreen
|
||||
# currently focused container
|
||||
# See https://github.com/i3/i3/issues/2857#issuecomment-496264445
|
||||
###############################################################################
|
||||
|
||||
kill_all_windows;
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
$first_win = open_window;
|
||||
$second_win = open_window;
|
||||
cmd 'fullscreen';
|
||||
cmd '[id=' . $first_win->id . '] move scratchpad';
|
||||
|
||||
is_num_fullscreen($tmp, 1, 'second window still fullscreen');
|
||||
my $__i3_scratch = get_ws('__i3_scratch');
|
||||
my @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
|
||||
is(scalar @scratch_nodes, 1, 'one window in scratchpad');
|
||||
|
||||
cmd '[id=' . $first_win->id . '] scratchpad show';
|
||||
is_num_fullscreen($tmp, 0, 'second window not fullscreen');
|
||||
$__i3_scratch = get_ws('__i3_scratch');
|
||||
@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
|
||||
is(scalar @scratch_nodes, 0, 'windows in scratchpad');
|
||||
|
||||
done_testing;
|
||||
|
|
|
@ -24,6 +24,7 @@ use i3test;
|
|||
|
||||
my ($A, $B, $S, $M, $F, $source_ws, $target_ws, $ws);
|
||||
my ($nodes, $focus);
|
||||
my $__i3_scratch;
|
||||
my $cmd_result;
|
||||
|
||||
my $_NET_WM_STATE_REMOVE = 0;
|
||||
|
@ -360,6 +361,7 @@ does_i3_live;
|
|||
###############################################################################
|
||||
# Given 'S' and 'M' where 'M' is a workspace and 'S' is on a different
|
||||
# workspace, then 'S' ends up as a tiling container on 'M'.
|
||||
# See issue: #2003
|
||||
###############################################################################
|
||||
|
||||
fresh_workspace;
|
||||
|
@ -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(@{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;
|
||||
|
|
|
@ -19,10 +19,9 @@
|
|||
use i3test i3_autostart => 0;
|
||||
|
||||
my $first_lines = <<'EOT';
|
||||
set $workspace1 workspace number 1
|
||||
set $workspace0 workspace eggs
|
||||
|
||||
bindsym Mod4+1 $workspace1
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
EOT
|
||||
|
||||
# Intentionally don't add a trailing newline for the last line since this is
|
||||
|
|
|
@ -280,4 +280,33 @@ cmd '[id=' . $windows[0]->id . '] kill';
|
|||
kill_and_confirm_focus($windows[2]->id, 'window 2 focused after tiling killed');
|
||||
kill_and_confirm_focus($windows[3]->id, 'window 3 focused after tiling killed');
|
||||
|
||||
######################################################################
|
||||
# cmp_tree tests
|
||||
######################################################################
|
||||
|
||||
cmp_tree(
|
||||
msg => 'Basic test',
|
||||
layout_before => 'S[a b] V[c d T[e f g*]]',
|
||||
layout_after => ' ',
|
||||
cb => sub {
|
||||
@windows = reverse @{$_[0]};
|
||||
confirm_focus('focus order');
|
||||
});
|
||||
|
||||
cmp_tree(
|
||||
msg => 'Focused container that is moved to mark keeps focus',
|
||||
layout_before => 'S[a b] V[2 3 T[4 5* 6]]',
|
||||
layout_after => 'S[a b*]',
|
||||
cb => sub {
|
||||
cmd '[class=' . $_[0][3]->id . '] mark 3';
|
||||
cmd 'move to mark 3';
|
||||
|
||||
$windows[0] = $_[0][5];
|
||||
$windows[1] = $_[0][6];
|
||||
$windows[2] = $_[0][4];
|
||||
$windows[3] = $_[0][3];
|
||||
$windows[4] = $_[0][2];
|
||||
confirm_focus('focus order');
|
||||
});
|
||||
|
||||
done_testing;
|
||||
|
|
|
@ -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 lib 'testcases/lib';
|
||||
use i3test::Util qw(slurp);
|
||||
use Lintian::Check qw(check_spelling);
|
||||
use Lintian::Spelling qw(check_spelling);
|
||||
|
||||
# Lintian complains if we don’t set a vendor.
|
||||
use Lintian::Data;
|
||||
|
|
Loading…
Reference in New Issue