Merge branch 'next' into master

This commit is contained in:
Michael Stapelberg 2020-02-17 18:27:46 +01:00
commit 53718d354b
97 changed files with 2324 additions and 954 deletions

View File

@ -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

View File

@ -1 +1 @@
4.17.1-non-git
4.18-non-git

View File

@ -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 \

View File

@ -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

71
RELEASE-NOTES-4.18 Normal file
View File

@ -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: dont 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

View File

@ -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

12
debian/changelog vendored
View File

@ -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.

View File

@ -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.

View File

@ -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)::

View File

@ -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"
}
]
--------------------------------------------------------------------------------

View File

@ -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+::

View File

@ -68,6 +68,7 @@
} while (0)
#include "xcb.h"
xcb_visualtype_t *visual_type = NULL;
#include "libi3.h"
#define TEXT_PADDING logical_px(4)

View File

@ -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"

View File

@ -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;

View File

@ -17,7 +17,7 @@
typedef struct rect_t rect;
struct ev_loop *main_loop;
extern struct ev_loop *main_loop;
struct rect_t {
int x;
@ -82,8 +82,8 @@ struct status_block {
blocks;
};
TAILQ_HEAD(statusline_head, status_block)
statusline_head;
extern TAILQ_HEAD(statusline_head, status_block)
statusline_head;
#include "child.h"
#include "ipc.h"

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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 */
};

View File

@ -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 */

View File

@ -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

View File

@ -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;
}

View File

@ -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();

View File

@ -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);

View File

@ -252,6 +252,7 @@ static yajl_callbacks outputs_callbacks = {
.yajl_end_map = outputs_end_map_cb,
};
struct outputs_head *outputs;
/*
* Initiate the outputs list
*

View File

@ -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);

View File

@ -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)) {

View File

@ -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"

View File

@ -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'.
*

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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;
};
/**

61
include/drag.h Normal file
View File

@ -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);

View File

@ -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

View File

@ -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)
*

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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.

View File

@ -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);

View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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 */

View File

@ -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;

View File

@ -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

View File

@ -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])

View File

@ -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)

View File

@ -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

View File

@ -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" ]

View File

@ -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 doesnt 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:

View File

@ -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);

View File

@ -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
View File

@ -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);
}

View File

@ -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);
}

View File

@ -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) {

252
src/drag.c Normal file
View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -322,11 +322,10 @@ void floating_enable(Con *con, bool automatic) {
DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height);
DLOG("Geometry = (%d, %d) with %d x %d\n", con->geometry.x, con->geometry.y, con->geometry.width, con->geometry.height);
Rect zero = {0, 0, 0, 0};
nc->rect = con->geometry;
/* If the geometry was not set (split containers), we need to determine a
* sensible one by combining the geometry of all children */
if (memcmp(&(nc->rect), &zero, sizeof(Rect)) == 0) {
if (rect_equals(nc->rect, (Rect){0, 0, 0, 0})) {
DLOG("Geometry not set, combining children\n");
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
@ -372,6 +371,7 @@ void floating_enable(Con *con, bool automatic) {
if (nc->rect.x == 0 && nc->rect.y == 0) {
Con *leader;
if (con->window && con->window->leader != XCB_NONE &&
con->window->id != con->window->leader &&
(leader = con_by_window_id(con->window->leader)) != NULL) {
DLOG("Centering above leader\n");
floating_center(nc, leader->rect);
@ -421,7 +421,7 @@ void floating_enable(Con *con, bool automatic) {
ipc_send_window_event("floating", con);
}
void floating_disable(Con *con, bool automatic) {
void floating_disable(Con *con) {
if (!con_is_floating(con)) {
LOG("Container isn't floating, not doing anything.\n");
return;
@ -469,7 +469,7 @@ void toggle_floating_mode(Con *con, bool automatic) {
if (con_is_floating(con)) {
LOG("already floating, re-setting to tiling\n");
floating_disable(con, automatic);
floating_disable(con);
return;
}
@ -492,6 +492,11 @@ void floating_raise_con(Con *con) {
*
*/
bool floating_maybe_reassign_ws(Con *con) {
if (con_is_internal(con_get_workspace(con))) {
DLOG("Con in an internal workspace\n");
return false;
}
Output *output = get_output_from_rect(con->rect);
if (!output) {
@ -561,8 +566,6 @@ void floating_move_to_pointer(Con *con) {
}
DRAGGING_CB(drag_window_callback) {
const struct xcb_button_press_event_t *event = extra;
/* Reposition the client correctly while moving */
con->rect.x = old_rect->x + (new_x - event->root_x);
con->rect.y = old_rect->y + (new_y - event->root_y);
@ -584,7 +587,7 @@ DRAGGING_CB(drag_window_callback) {
* Calls the drag_pointer function with the drag_window callback
*
*/
void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
void floating_drag_window(Con *con, const xcb_button_press_event_t *event, bool use_threshold) {
DLOG("floating_drag_window\n");
/* Push changes before dragging, so that the window gets raised now and not
@ -595,7 +598,7 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
Rect initial_rect = con->rect;
/* Drag the window */
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, use_threshold, drag_window_callback, NULL);
if (!con_exists(con)) {
DLOG("The container has been closed in the meantime.\n");
@ -625,12 +628,10 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
struct resize_window_callback_params {
const border_t corner;
const bool proportional;
const xcb_button_press_event_t *event;
};
DRAGGING_CB(resize_window_callback) {
const struct resize_window_callback_params *params = extra;
const xcb_button_press_event_t *event = params->event;
border_t corner = params->corner;
int32_t dest_x = con->rect.x;
@ -706,12 +707,12 @@ void floating_resize_window(Con *con, const bool proportional,
cursor = (corner & BORDER_LEFT) ? XCURSOR_CURSOR_BOTTOM_LEFT_CORNER : XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER;
}
struct resize_window_callback_params params = {corner, proportional, event};
struct resize_window_callback_params params = {corner, proportional};
/* get the initial rect in case of revert/cancel */
Rect initial_rect = con->rect;
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, &params);
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, cursor, false, resize_window_callback, &params);
if (!con_exists(con)) {
DLOG("The container has been closed in the meantime.\n");
@ -727,209 +728,6 @@ void floating_resize_window(Con *con, const bool proportional,
con->scratchpad_state = SCRATCHPAD_CHANGED;
}
/* Custom data structure used to track dragging-related events. */
struct drag_x11_cb {
ev_prepare prepare;
/* Whether this modal event loop should be exited and with which result. */
drag_result_t result;
/* The container that is being dragged or resized, or NULL if this is a
* drag of the resize handle. */
Con *con;
/* The dimensions of con when the loop was started. */
Rect old_rect;
/* The callback to invoke after every pointer movement. */
callback_t callback;
/* User data pointer for callback. */
const void *extra;
};
static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
xcb_motion_notify_event_t *last_motion_notify = NULL;
xcb_generic_event_t *event;
while ((event = xcb_poll_for_event(conn)) != NULL) {
if (event->response_type == 0) {
xcb_generic_error_t *error = (xcb_generic_error_t *)event;
DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n",
error->sequence, error->error_code);
free(event);
continue;
}
/* Strip off the highest bit (set if the event is generated) */
int type = (event->response_type & 0x7F);
switch (type) {
case XCB_BUTTON_RELEASE:
dragloop->result = DRAG_SUCCESS;
break;
case XCB_KEY_PRESS:
DLOG("A key was pressed during drag, reverting changes.\n");
dragloop->result = DRAG_REVERT;
handle_event(type, event);
break;
case XCB_UNMAP_NOTIFY: {
xcb_unmap_notify_event_t *unmap_event = (xcb_unmap_notify_event_t *)event;
Con *con = con_by_window_id(unmap_event->window);
if (con != NULL) {
DLOG("UnmapNotify for window 0x%08x (container %p)\n", unmap_event->window, con);
if (con_get_workspace(con) == con_get_workspace(focused)) {
DLOG("UnmapNotify for a managed window on the current workspace, aborting\n");
dragloop->result = DRAG_ABORT;
}
}
handle_event(type, event);
break;
}
case XCB_MOTION_NOTIFY:
/* motion_notify events are saved for later */
FREE(last_motion_notify);
last_motion_notify = (xcb_motion_notify_event_t *)event;
break;
default:
DLOG("Passing to original handler\n");
handle_event(type, event);
break;
}
if (last_motion_notify != (xcb_motion_notify_event_t *)event)
free(event);
if (dragloop->result != DRAGGING) {
ev_break(EV_A_ EVBREAK_ONE);
if (dragloop->result == DRAG_SUCCESS) {
/* Ensure motion notify events are handled. */
break;
} else {
free(last_motion_notify);
return true;
}
}
}
if (last_motion_notify == NULL) {
return true;
}
/* Ensure that we are either dragging the resize handle (con is NULL) or that the
* container still exists. The latter might not be true, e.g., if the window closed
* for any reason while the user was dragging it. */
if (!dragloop->con || con_exists(dragloop->con)) {
dragloop->callback(
dragloop->con,
&(dragloop->old_rect),
last_motion_notify->root_x,
last_motion_notify->root_y,
dragloop->extra);
}
FREE(last_motion_notify);
xcb_flush(conn);
return dragloop->result != DRAGGING;
}
static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
while (!drain_drag_events(EV_A, dragloop)) {
/* repeatedly drain events: draining might produce additional ones */
}
}
/*
* This function grabs your pointer and keyboard and lets you drag stuff around
* (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
* be received and the given callback will be called with the parameters
* specified (client, border on which the click originally was), the original
* rect of the client, the event and the new coordinates (x, y).
*
*/
drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to,
border_t border, int cursor, callback_t callback, const void *extra) {
xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE;
/* Grab the pointer */
xcb_grab_pointer_cookie_t cookie;
xcb_grab_pointer_reply_t *reply;
xcb_generic_error_t *error;
cookie = xcb_grab_pointer(conn,
false, /* get all pointer events specified by the following mask */
root, /* grab the root window */
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
XCB_GRAB_MODE_ASYNC, /* keyboard mode */
confine_to, /* confine_to = in which window should the cursor stay */
xcursor, /* possibly display a special cursor */
XCB_CURRENT_TIME);
if ((reply = xcb_grab_pointer_reply(conn, cookie, &error)) == NULL) {
ELOG("Could not grab pointer (error_code = %d)\n", error->error_code);
free(error);
return DRAG_ABORT;
}
free(reply);
/* Grab the keyboard */
xcb_grab_keyboard_cookie_t keyb_cookie;
xcb_grab_keyboard_reply_t *keyb_reply;
keyb_cookie = xcb_grab_keyboard(conn,
false, /* get all keyboard events */
root, /* grab the root window */
XCB_CURRENT_TIME,
XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */
XCB_GRAB_MODE_ASYNC /* keyboard mode */
);
if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, &error)) == NULL) {
ELOG("Could not grab keyboard (error_code = %d)\n", error->error_code);
free(error);
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
return DRAG_ABORT;
}
free(keyb_reply);
/* Go into our own event loop */
struct drag_x11_cb loop = {
.result = DRAGGING,
.con = con,
.callback = callback,
.extra = extra,
};
ev_prepare *prepare = &loop.prepare;
if (con)
loop.old_rect = con->rect;
ev_prepare_init(prepare, xcb_drag_prepare_cb);
prepare->data = &loop;
main_set_x11_cb(false);
ev_prepare_start(main_loop, prepare);
ev_loop(main_loop, 0);
ev_prepare_stop(main_loop, prepare);
main_set_x11_cb(true);
xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
xcb_flush(conn);
return loop.result;
}
/*
* Repositions the CT_FLOATING_CON to have the coordinates specified by
* newrect, but only if the coordinates are not out-of-bounds. Also reassigns

View File

@ -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 i3s 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 dont 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;
}

View File

@ -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);

View File

@ -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) {

View File

@ -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();

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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");
}

View File

@ -21,12 +21,32 @@ struct callback_params {
Con *output;
xcb_window_t helpwin;
uint32_t *new_position;
bool *threshold_exceeded;
};
DRAGGING_CB(resize_callback) {
const struct callback_params *params = extra;
Con *output = params->output;
DLOG("new x = %d, y = %d\n", new_x, new_y);
if (!*params->threshold_exceeded) {
xcb_map_window(conn, params->helpwin);
/* Warp pointer in the same way as resize_graphical_handler() would do
* if threshold wasn't enabled, but also take into account travelled
* distance. */
if (params->orientation == HORIZ) {
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
*params->new_position + new_x - event->root_x,
new_y);
} else {
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
new_x,
*params->new_position + new_y - event->root_y);
}
*params->threshold_exceeded = true;
return;
}
if (params->orientation == HORIZ) {
/* Check if the new coordinates are within screen boundaries */
if (new_x > (output->rect.x + output->rect.width - 25) ||
@ -148,7 +168,9 @@ bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt) {
return true;
}
void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
void resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
const xcb_button_press_event_t *event,
bool use_threshold) {
Con *output = con_get_output(first);
DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width);
@ -179,14 +201,10 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
helprect.width = logical_px(2);
helprect.height = second->rect.height;
initial_position = second->rect.x;
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
second->rect.x, event->root_y);
} else {
helprect.width = second->rect.width;
helprect.height = logical_px(2);
initial_position = second->rect.y;
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
event->root_x, second->rect.y);
}
mask = XCB_CW_BACK_PIXEL;
@ -196,7 +214,18 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
values[1] = 1;
xcb_window_t helpwin = create_window(conn, helprect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
XCB_WINDOW_CLASS_INPUT_OUTPUT, (orientation == HORIZ ? XCURSOR_CURSOR_RESIZE_HORIZONTAL : XCURSOR_CURSOR_RESIZE_VERTICAL), true, mask, values);
XCB_WINDOW_CLASS_INPUT_OUTPUT, (orientation == HORIZ ? XCURSOR_CURSOR_RESIZE_HORIZONTAL : XCURSOR_CURSOR_RESIZE_VERTICAL), false, mask, values);
if (!use_threshold) {
xcb_map_window(conn, helpwin);
if (orientation == HORIZ) {
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
second->rect.x, event->root_y);
} else {
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
event->root_x, second->rect.y);
}
}
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
@ -205,10 +234,12 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
/* `new_position' will be updated by the `resize_callback'. */
new_position = initial_position;
const struct callback_params params = {orientation, output, helpwin, &new_position};
bool threshold_exceeded = !use_threshold;
const struct callback_params params = {orientation, output, helpwin, &new_position, &threshold_exceeded};
/* `drag_pointer' blocks until the drag is completed. */
drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, &params);
drag_result_t drag_result = drag_pointer(NULL, event, grabwin, 0, use_threshold, resize_callback, &params);
xcb_destroy_window(conn, helpwin);
xcb_destroy_window(conn, grabwin);

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
}
/*

View File

@ -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;
}
}

View File

@ -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);
}
}
/*

View File

@ -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;
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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";
}

View File

@ -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
#####################################################################

View File

@ -53,6 +53,7 @@ my $expected = {
name => 'root',
orientation => $ignore,
type => 'root',
window_type => undef,
id => $ignore,
rect => $ignore,
deco_rect => $ignore,

View File

@ -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;

View File

@ -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;

View File

@ -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');

View File

@ -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;

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 dont set a vendor.
use Lintian::Data;