Merge branch 'next' into stable

This commit is contained in:
Michael Stapelberg 2022-09-21 18:26:55 +02:00
commit c0ef3caec8
119 changed files with 2107 additions and 521 deletions

View File

@ -41,11 +41,14 @@ Note that bug reports and feature requests for related projects should be filed
* Use `clang-format` to format your code.
* Run the [testsuite](https://i3wm.org/docs/testsuite.html)
* If your changes should be reported on the next release's changelog, also
update the [RELEASE-notes-next](../RELEASE-notes-next) file in the root
folder. Example of changes that should be reported are bug fixes present in
the latest stable version of i3 and new enhancements. Example of changes that
should not be reported are minor code improvements, documentation, regression
and fixes for bugs that were introduced in the `next` branch.
add a small single-line file starting with a number (see examples) containing
a short explanation of your change either in the
[changes](../release-notes/changes) or the
[bugfixes](../release-notes/bugfixes/) folder. Example of changes that should
be reported are bug fixes present in the latest stable version of i3 and new
enhancements. Example of changes that should not be reported are minor code
improvements, documentation, regression and fixes for bugs that were
introduced in the `next` branch.
## Finding something to do

View File

@ -45,12 +45,6 @@ jobs:
echo "::group::Ubuntu i386"
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_UBUNTU_386 }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU_386 }} travis/travis-base-ubuntu-386.Dockerfile
echo "::endgroup::"
- name: verify safe wrapper functions are used
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-safe-wrappers.sh
- name: verify code formatting
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-formatting.sh
- name: build i3
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common" meson .. -Ddocs=true -Dmans=true -Db_sanitize=address && ninja -v'
@ -92,3 +86,17 @@ jobs:
- name: push docs to GitHub pages
run: |
./travis/skip-pkg.sh || travis/deploy-github-pages.sh
formatting:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: check & print release notes
run: ./release-notes/generator.pl
- name: Install dependencies
run: |
sudo apt-get install -y clang-format-10
- name: Check formatting
run: clang-format-10 --dry-run --Werror $(git ls-files '*.c' '*.h')
- name: Verify safe wrapper functions are used
run: ./travis/check-safe-wrappers.sh

View File

@ -1,27 +0,0 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.20.1 │
└──────────────────────────────┘
This is i3 v4.20.1. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• i3bar: fix crash with multiple monitors
• xmlto: fix broken .TH line by extending title length
• i3-msg: fix --raw short form (-r) in manpage
• libi3: add missing sys/stat.h header
• use getcwd(NULL, 0) instead of GNU extension get_current_dir_name()
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
rvalieris, Jakob Haufe, lycurgus, Baptiste Daroussin
-- Michael Stapelberg, 2021-11-03

74
RELEASE-NOTES-4.21 Normal file
View File

@ -0,0 +1,74 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.21 │
└──────────────────────────────┘
This is i3 v4.21. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
The biggest change in this release is that you can now drag tiling windows
with your mouse (floating windows could already be dragged). For more details
on how to use this feature, please refer to the userguide:
https://i3wm.org/docs/userguide.html#_moving_tiling_containers_with_the_mouse
A big thank you goes out to our core i3 developer Orestis Floros who made this
feature possible, based on previous work from Michael Forster and Tony Crisci!
┌────────────────────────────┐
│ Changes in i3 v4.21 │
└────────────────────────────┘
• Allow dragging tiling windows with the mouse
• Add client.focused_tab_title color option
• Add support for multiple output names in the focus command,
allowing users to cycle focus between e.g. VGA1 and LVDS1 but not DVI0.
• Add a toggle option to the title_window_icon command
• i3 switched from the obsolete PCRE 8.x regular expression matching
library to the current PCRE2 10.x version.
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• docs/ipc: document all window_type values
• docs/userguide: clarify the difference between the “workspace N” and
“workspace number N” commands
• i3bar: fix default font not being applied to bars if defined after bar block
• i3-dmenu-desktop: add backslashes for the exec command,
which fixes opening some .desktop files (e.g. electrum)
• i3-sensible-pager: sanitize LESS environment variable to remove -E or -F
• testsuite: catch i3 crashes instead of hanging on crash
• Fix logging on machines with 256 GB of RAM
• Do not replace existing IPC socket on start, to prevent clobbering
the IPC socket when running i3 within i3 (e.g. in Xepyhr, for development)
• Refuse to start without a valid IPC socket
• Fix focus when moving container between outputs with mouse warp and
focus_follows_mouse
• Fix endless loop with transient_for windows
• Fix wrong “failed” IPC reply on move workspace to output
• Fix WM registration selection (from WM_S_S<screen> to WM_S<screen>)
• avoid graphics artifacts when changing the layout tree by
initializing surfaces to all black
• update parent split con titles when child container swaps position with
another child container
• Fix segfault if command in bindsym is empty
• Fix segfault with explicit mode "default" key bindings
• Fix crash if config contains nested variables.
• strip trailing whitespace in bar output names
• Fix crash with long commands
• Fix changing borders by restoring BS_NORMAL _MOTIF_WM_HINTS correctly
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
André Silva, Anton Älgmyr, Baptiste Daroussin, bodea, Chris Templin, George
Rodrigues, Gergely Risko, Ingo Bürk, Jakob Haufe, Jay Ta'ala, Jeff Smith, Jonta,
Josh Soref, Kjetil Torgrim Homme, lycurgus, mariano, Michael Forster, Orestis
Floros, paperluigis, Peder Stray, rvalieris, sergio, Tony Crisci, takelley1, Uli
Schlachter, viri, zhiv-git, zhrvn
-- Michael Stapelberg, 2021-10-19

8
debian/changelog vendored
View File

@ -1,3 +1,9 @@
i3-wm (4.20.1-1) unstable; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Wed, 03 Nov 2021 09:22:48 +0100
i3-wm (4.20-1) unstable; urgency=medium
* New upstream release.
@ -621,7 +627,7 @@ i3-wm (3.d-bf1-1) unstable; urgency=low
* Bugfix: Resize client after updating base_width/base_height
* Bugfix: Force render containers after setting the client active
* Bugfix: Fix two problems in resizing floating windows with right mouse
* Bugfix: Use more precise floating point arithmetics
* Bugfix: Use more precise floating point arithmetic
* Bugfix: Correctly place new windows below fullscreen windows
-- Michael Stapelberg <michael@stapelberg.de> Mon, 21 Dec 2009 22:33:02 +0100

2
debian/control vendored
View File

@ -22,7 +22,7 @@ Build-Depends: debhelper (>= 10),
pkg-config,
libev-dev (>= 1:4.04),
libyajl-dev (>= 2.0.4),
libpcre3-dev (>= 1:8.10),
libpcre2-dev,
libstartup-notification0-dev (>= 0.10),
libcairo2-dev (>= 1.14.4),
libpango1.0-dev,

4
debian/rules vendored
View File

@ -17,9 +17,5 @@ override_dh_auto_configure:
# Set -Ddocdir; the default is /usr/share/doc/i3
dh_auto_configure -- -Ddocdir=/usr/share/doc/i3-wm -Dmans=true
override_dh_builddeb:
# bintray does not support xz currently.
dh_builddeb -- -Zgzip
%:
dh $@ --buildsystem=meson

View File

@ -423,9 +423,9 @@ window_properties (map)::
following list: *title*, *instance*, *class*, *window_role*, *machine*
and *transient_for*.
window_type (string)::
The window type (_NET_WM_WINDOW_TYPE). Possible values are undefined, normal,
dialog, utility, toolbar, splash, menu, dropdown_menu, popup_menu, tooltip and
notification.
The window type (_NET_WM_WINDOW_TYPE). Possible values are `undefined`,
unknown, normal, dialog, utility, toolbar, splash, menu, dropdown_menu,
popup_menu, tooltip and notification.
urgent (bool)::
Whether this container (window, split container, floating container or
workspace) has the urgency hint set, directly or indirectly. All parent

View File

@ -4,11 +4,10 @@ Michael Stapelberg <michael@i3wm.org>
September 2012
This document explains how the i3 testsuite works, how to use it and extend it.
It is targeted at developers who not necessarily have been doing testing before
or have not been testing in Perl before. In general, the testsuite is not of
It is targeted at developers who haven't necessarily done testing before,
or have not used Perl for testing before. In general, the testsuite is not of
interest for end users.
== Introduction
The i3 testsuite is a collection of files which contain testcases for various

View File

@ -196,6 +196,48 @@ provided by the i3 https://github.com/i3/i3/blob/next/etc/config.keycodes[defaul
Floating windows are always on top of tiling windows.
=== Moving tiling containers with the mouse
Since i3 4.21, it's possible to drag tiling containers using the mouse. The
drag can be initiated either by dragging the window's titlebar or by pressing
the <<floating_modifier>> and dragging the container while holding the
left-click button.
Once the drag is initiated and the cursor has left the original container, drop
indicators are created according to the position of the cursor relatively to
the target container. These indicators help you understand what the resulting
<<tree>> layout is going to be after you release the mouse button.
The possible drop positions are:
Drop on container::
This happens when the mouse is relatively near the center of a container.
If the mouse is released, the result is exactly as if you had run the
+move container to mark+ command. See <<move_to_mark>>.
Drop as sibling::
This happens when the mouse is relatively near the edge of a container. If
the mouse is released, the dragged container will become a sibling of the
target container, placed left/right/up/down according to the position of
the indicator.
This might or might not create a new v-split or h-split according to the
previous layout of the target container. For example, if the target
container is in an h-split and you drop the dragged container below it, the
new layout will have to be a v-split.
Drop to parent::
This happens when the mouse is relatively near the edge of a container (but
even closer to the border in comparison to the sibling case above) *and* if
that edge is also the parent container's edge. For example, if three
containers are in a horizontal layout then edges where this can happen is
the left edge of the left container, the right edge of the right container
and all bottom and top edges of all three containers.
If the mouse is released, the container is first dropped as a sibling to
the target container, like in the case above, and then is moved
directionally like with the +move left|right|down|up+ command. See
<<move_direction>>.
The color of the indicator matches the +client.focused+ setting. See <<client_colors>>.
[[tree]]
== Tree
i3 stores all information about the X11 outputs, workspaces and layout of the
@ -538,7 +580,7 @@ for the keybinding.
# The middle button over a titlebar kills the window
bindsym --release button2 kill
# The middle button and a modifer over any part of the window kills the window
# The middle button and a modifier over any part of the window kills the window
bindsym --whole-window $mod+button2 kill
# The right button toggles floating
@ -1041,6 +1083,7 @@ workspace 5 output VGA1 LVDS1
workspace "2: vim" output VGA1
---------------------------
[[client_colors]]
=== Changing colors
You can change all colors which i3 uses to draw the window decorations.
@ -1057,6 +1100,10 @@ client.focused::
client.focused_inactive::
A client which is the focused one of its container, but it does not have
the focus at the moment.
client.focused_tab_title::
Tab or stack container title that is the parent of the focused container
but not directly focused. Defaults to focused_inactive if not specified and
does not use the indicator and child_border colors.
client.unfocused::
A client which is not the focused one of its container.
client.urgent::
@ -1100,7 +1147,7 @@ i3 uses Unix sockets to provide an IPC interface. This allows third-party
programs to get information from i3, such as the current workspaces
(to display a workspace bar), and to control i3.
The IPC socket is enabled by default and will be created in
By default, an IPC socket will be created in
+$XDG_RUNTIME_DIR/i3/ipc-socket.%p+ if the directory is available, falling back
to +/tmp/i3-%u.XXXXXX/ipc-socket.%p+, where +%u+ is your UNIX username, +%p+ is
the PID of i3 and XXXXXX is a string of random characters from the portable
@ -2208,7 +2255,7 @@ output::
focus left|right|down|up
focus parent|child|floating|tiling|mode_toggle
focus next|prev [sibling]
focus output left|right|up|down|primary|<output>
focus output left|right|down|up|current|primary|next|<output1> [output2]…
----------------------------------------------
*Examples*:
@ -2228,6 +2275,9 @@ bindsym $mod+u focus parent
# Focus last floating/tiling container
bindsym $mod+g focus mode_toggle
# Focus the next output (effectively toggles when you only have two outputs)
bindsym $mod+x move workspace to output next
# Focus the output right to the current one
bindsym $mod+x focus output right
@ -2236,6 +2286,9 @@ bindsym $mod+x focus output HDMI-2
# Focus the primary output
bindsym $mod+x focus output primary
# Cycle focus between outputs VGA1 and LVDS1 but not DVI0
bindsym $mod+x move workspace to output VGA1 LVDS1
-------------------------------------------------
Note that you might not have a primary output configured yet. To do so, run:
@ -2243,6 +2296,7 @@ Note that you might not have a primary output configured yet. To do so, run:
xrandr --output <output> --primary
-------------------------
[[move_direction]]
=== Moving containers
Use the +move+ command to move a container.
@ -2477,9 +2531,11 @@ bindsym $mod+2 workspace number "2: mail"
If a workspace does not exist, the command +workspace number "1: mail"+ will
create workspace "1: mail".
If a workspace with number 1 does already exist, the command will switch to this
If a workspace with number 1 already exists, the command will switch to this
workspace and ignore the text part. So even when the workspace has been renamed
to "1: web", the above command will still switch to it.
"1: web", the above command will still switch to it. The command +workspace 1+
will however create and move to a new workspace "1" alongside the existing
"1: mail" workspace.
=== Moving workspaces to a different screen
@ -2525,6 +2581,7 @@ Note that you might not have a primary output configured yet. To do so, run:
xrandr --output <output> --primary
-------------------------
[[move_to_mark]]
=== Moving containers/windows to marks
To move a container to another container with a specific mark (see <<vim_like_marks>>),
@ -2718,8 +2775,8 @@ specific windows or for all windows (using the <<for_window>> directive).
*Syntax*:
-----------------------------
title_window_icon <yes|no>
title_window_icon padding <px>
title_window_icon <yes|no|toggle>
title_window_icon <padding|toggle> <px>
------------------------------
*Examples*:

View File

@ -228,8 +228,15 @@ for my $state (@keys) {
($call_identifier) = ($next_state =~ /^call ([0-9]+)$/);
$next_state = '__CALL';
}
my $identifier = $token->{identifier};
say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } },|;
my $identifier;
# Set $identifier to NULL if there is no identifier
if ($token->{identifier} eq ""){
$identifier = "NULL"
}
else{
$identifier = qq|"$token->{identifier}"|;
}
say $tokfh qq| { "$token_name", $identifier, $next_state, { $call_identifier } },|;
}
say $tokfh '};';
}

View File

@ -332,8 +332,6 @@ static char *rewrite_binding(const char *input) {
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
walk++;
//printf("remaining input: %s\n", walk);
cmdp_token_ptr *ptr = &(tokens[state]);
for (c = 0; c < ptr->n; c++) {
token = &(ptr->array[c]);
@ -426,15 +424,13 @@ static char *rewrite_binding(const char *input) {
}
if (strcmp(token->name, "end") == 0) {
//printf("checking for end: *%s*\n", walk);
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
if ((result = next_state(token)) != NULL)
return result;
/* To make sure we start with an appropriate matching
* datastructure for commands which do *not* specify any
/* To make sure we start with an appropriate matching data
* structure for commands which do *not* specify any
* criteria, we re-initialize the criteria system after
* every command. */
// TODO: make this testable
walk++;
break;
}

View File

@ -482,7 +482,8 @@ EOT
# double quote which is NOT preceded by a backslash (\).
#
# Therefore, we escape all double quotes (") by replacing them with \"
$exec =~ s/"/\\"/g;
$exec =~ s/\\"/\\\\\\"/g;
$exec =~ s/([^\\])"/$1\\"/g;
if (exists($app->{StartupNotify}) && !$app->{StartupNotify}) {
$nosn = '--no-startup-id';

View File

@ -1,6 +1,6 @@
# This list can be used to convert X11 Keysyms to Unicode 2.1 character.
# The list is not checked for correctness by Unicode officials. Use it
# at your own risk and the creator is not responsable for any damage that
# at your own risk and the creator is not responsible for any damage that
# occurred due to using this list.
#
# The list is created by looking at the Keysym names and the Unicode data

View File

@ -8,6 +8,11 @@
# Distributions/packagers can enhance this script with a
# distribution-specific mechanism to find the preferred pager.
# The less -E and -F options exit immediately for short files, strip if present.
case "$LESS" in
*[EF]*) LESS=`echo "$LESS" | tr -d EF`
esac
# Hopefully one of these is installed (no flamewars about preference please!):
# We don't use 'more' because it will exit if the file is too short.
# Worst case scenario we'll open the file in your editor.

View File

@ -14,7 +14,7 @@
# 2. Distribution-specific mechanisms come next, e.g. x-terminal-emulator
# 3. The terminal emulator with best accessibility comes first.
# 4. No order is guaranteed/desired for the remaining terminal emulators.
for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper; do
for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper wezterm; do
if command -v "$terminal" > /dev/null 2>&1; then
exec "$terminal" "$@"
fi

View File

@ -66,7 +66,7 @@ struct status_block {
uint32_t border_left;
bool pango_markup;
/* The amount of pixels necessary to render a separater after the block. */
/* The amount of pixels necessary to render a separator after the block. */
uint32_t sep_block_width;
/* Continuously-updated information on how to render this status block. */

View File

@ -13,7 +13,6 @@
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <yajl/yajl_parse.h>
config_t config;
@ -126,31 +125,31 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
}
if (len == strlen("shift") && !strncmp((const char *)val, "shift", strlen("shift"))) {
config.modifier = ShiftMask;
config.modifier = XCB_MOD_MASK_SHIFT;
return 1;
}
if (len == strlen("ctrl") && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
config.modifier = ControlMask;
config.modifier = XCB_MOD_MASK_CONTROL;
return 1;
}
if (len == strlen("Mod") + 1 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
switch (val[3]) {
case '1':
config.modifier = Mod1Mask;
config.modifier = XCB_MOD_MASK_1;
return 1;
case '2':
config.modifier = Mod2Mask;
config.modifier = XCB_MOD_MASK_2;
return 1;
case '3':
config.modifier = Mod3Mask;
config.modifier = XCB_MOD_MASK_3;
return 1;
case '5':
config.modifier = Mod5Mask;
config.modifier = XCB_MOD_MASK_5;
return 1;
}
}
config.modifier = Mod4Mask;
config.modifier = XCB_MOD_MASK_4;
return 1;
}

View File

@ -203,7 +203,7 @@ static uint32_t predict_statusline_length(bool use_short_text) {
if (block->border)
render->width += logical_px(block->border_left + block->border_right);
/* Compute offset and append for text aligment in min_width. */
/* Compute offset and append for text alignment in min_width. */
if (block->min_width <= render->width) {
render->x_offset = 0;
render->x_append = 0;

View File

@ -58,6 +58,7 @@
#include "match.h"
#include "xcursor.h"
#include "resize.h"
#include "tiling_drag.h"
#include "sighandler.h"
#include "move.h"
#include "output.h"

View File

@ -333,7 +333,7 @@ void cmd_shmlog(I3_CMD, const char *argument);
void cmd_debuglog(I3_CMD, const char *argument);
/**
* Implementation of 'title_window_icon <yes|no>' and 'title_window_icon padding <px>'
* Implementation of 'title_window_icon <yes|no|toggle>' and 'title_window_icon <padding|toggle> <px>'
*
*/
void cmd_title_window_icon(I3_CMD, const char *enable, int padding);

View File

@ -14,7 +14,7 @@
#include <yajl/yajl_gen.h>
/**
* Holds an intermediate represenation of the result of a call to any command.
* Holds an intermediate representation of the result of a call to any command.
* When calling parse_command("floating enable, border none"), the parser will
* internally use this struct when calling cmd_floating and cmd_border.
*/

View File

@ -208,6 +208,14 @@ Con *con_by_frame_id(xcb_window_t frame);
*/
Con *con_by_mark(const char *mark);
/**
* Start from a container and traverse the transient_for linked list. Returns
* true if target window is found in the list. Protects againsts potential
* cycles.
*
*/
bool con_find_transient_for_window(Con *start, xcb_window_t target);
/**
* Returns true if and only if the given containers holds the mark.
*
@ -363,6 +371,7 @@ void con_move_to_output(Con *con, Output *output, bool fix_coordinates);
*/
bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates);
bool con_move_to_target(Con *con, Con *target);
/**
* Moves the given container to the given mark.
*

View File

@ -41,18 +41,18 @@ struct parser_ctx {
Match current_match;
/* A list which contains the states that lead to the current state, e.g.
* INITIAL, WORKSPACE_LAYOUT.
* When jumping back to INITIAL, statelist_idx will simply be set to 1
* (likewise for other states, e.g. MODE or BAR).
* This list is used to process the nearest error token. */
* INITIAL, WORKSPACE_LAYOUT.
* When jumping back to INITIAL, statelist_idx will simply be set to 1
* (likewise for other states, e.g. MODE or BAR).
* This list is used to process the nearest error token. */
int statelist[10];
/* NB: statelist_idx points to where the next entry will be inserted */
int statelist_idx;
/*******************************************************************************
* The (small) stack where identified literals are stored during the parsing
* of a single config directive (like $workspace).
******************************************************************************/
* The (small) stack where identified literals are stored during the parsing
* of a single config directive (like $workspace).
******************************************************************************/
struct stack *stack;
struct variables_head variables;
@ -61,7 +61,7 @@ struct parser_ctx {
};
/**
* An intermediate reprsentation of the result of a parse_config call.
* An intermediate representation of the result of a parse_config call.
* Currently unused, but the JSON output will be useful in the future when we
* implement a config parsing IPC command.
*

View File

@ -238,9 +238,11 @@ struct Config {
color_t background;
struct Colortriple focused;
struct Colortriple focused_inactive;
struct Colortriple focused_tab_title;
struct Colortriple unfocused;
struct Colortriple urgent;
struct Colortriple placeholder;
bool got_focused_tab_title;
} client;
struct config_bar {
struct Colortriple focused;

View File

@ -9,11 +9,13 @@
*/
#pragma once
#define PCRE2_CODE_UNIT_WIDTH 8
#define SN_API_NOT_YET_FROZEN 1
#include <libsn/sn-launcher.h>
#include <xcb/randr.h>
#include <pcre.h>
#include <pcre2.h>
#include <sys/time.h>
#include <cairo/cairo.h>
@ -248,8 +250,7 @@ struct Startup_Sequence {
*/
struct regex {
char *pattern;
pcre *regex;
pcre_extra *extra;
pcre2_code *regex;
};
/**
@ -662,8 +663,8 @@ struct Con {
char *title_format;
/** Whether the window icon should be displayed, and with what padding. -1
* means display no window icon (default behavior), 0 means display without
* any padding, 1 means display with 1 pixel of padding and so on. */
* means display no window icon (default behavior), 0 means display without
* any padding, 1 means display with 1 pixel of padding and so on. */
int window_icon_padding;
/* a sticky-group is an identifier which bundles several containers to a

View File

@ -14,7 +14,7 @@
/**
* Connects to i3 to find out the currently running version. Useful since it
* might be different from the version compiled into this binary (maybe the
* user didnt correctly install i3 or forgot te restart it).
* user didnt correctly install i3 or forgot to restart it).
*
* The output looks like this:
* Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804)

View File

@ -56,7 +56,6 @@ extern xcb_timestamp_t last_timestamp;
extern SnDisplay *sndisplay;
extern xcb_key_symbols_t *keysyms;
extern char **start_argv;
extern Display *xlibdpy, *xkbdpy;
extern int xkb_current_group;
extern TAILQ_HEAD(bindings_head, Binding) *bindings;
extern const char *current_binding_mode;

View File

@ -294,6 +294,12 @@ size_t i3string_get_num_glyphs(i3String *str);
*/
int ipc_connect(const char *socket_path);
/**
* Connects to the socket at the given path with no fallback paths. Returns
* -1 if connect() fails and die()s for other errors.
*/
int ipc_connect_impl(const char *socket_path);
/**
* Formats a message (payload) of the given size and type and sends it to i3 via
* the given socket file descriptor.

View File

@ -50,12 +50,6 @@ void output_init_con(Output *output);
*/
void init_ws_for_output(Output *output);
/**
* Initializes the specified output, assigning the specified workspace to it.
*
*/
//void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace);
/**
* (Re-)queries the outputs via RandR and stores them in the list of outputs.
*

16
include/tiling_drag.h Normal file
View File

@ -0,0 +1,16 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* tiling_drag.h: Reposition tiled windows by dragging.
*
*/
#pragma once
/**
* Initiates a mouse drag operation on a tiled window.
*
*/
void tiling_drag(Con *con, xcb_button_press_event_t *event);

View File

@ -183,3 +183,15 @@ position_t position_from_direction(direction_t direction);
*
*/
direction_t direction_from_orientation_position(orientation_t orientation, position_t position);
/**
* Converts direction to a string representation.
*
*/
const char *direction_to_string(direction_t direction);
/**
* Converts position to a string representation.
*
*/
const char *position_to_string(position_t position);

View File

@ -94,7 +94,7 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
* it is still in use by popular widget toolkits such as GTK+ and Java AWT.
*
*/
void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
bool window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
/**
* Updates the WM_CLIENT_MACHINE

View File

@ -92,7 +92,7 @@ Con *create_workspace_on_output(Output *output, Con *content);
/**
* Returns true if the workspace is currently visible. Especially important for
* multi-monitor environments, as they can have multiple currenlty active
* multi-monitor environments, as they can have multiple currently active
* workspaces.
*
*/

View File

@ -10,6 +10,7 @@
#include <unistd.h>
#include <libgen.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
@ -34,10 +35,20 @@ int create_socket(const char *filename, char **out_socketpath) {
}
free(copy);
/* Check if the socket is in use by another process (this call does not
* succeed if the socket is stale / the owner already exited) */
int sockfd = ipc_connect_impl(resolved);
if (sockfd != -1) {
ELOG("Refusing to create UNIX socket at %s: Socket is already in use\n", resolved);
close(sockfd);
errno = EEXIST;
return -1;
}
/* Unlink the unix domain socket before */
unlink(resolved);
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket()");
free(resolved);

View File

@ -160,7 +160,7 @@ i3Font load_font(const char *pattern, const bool fallback) {
font.type = FONT_TYPE_NONE;
font.pattern = NULL;
/* No XCB connction, return early because we're just validating the
/* No XCB connection, return early because we're just validating the
* configuration file. */
if (conn == NULL) {
return font;

View File

@ -13,6 +13,7 @@
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
/*
* Connects to the i3 IPC socket and returns the file descriptor for the
@ -39,6 +40,20 @@ int ipc_connect(const char *socket_path) {
path = sstrdup("/tmp/i3-ipc.sock");
}
int sockfd = ipc_connect_impl(path);
if (sockfd < 0) {
err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path);
}
free(path);
return sockfd;
}
/**
* Connects to the socket at the given path with no fallback paths. Returns
* -1 if connect() fails and die()s for other errors.
*
*/
int ipc_connect_impl(const char *socket_path) {
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd == -1)
err(EXIT_FAILURE, "Could not create socket");
@ -48,9 +63,10 @@ int ipc_connect(const char *socket_path) {
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path);
free(path);
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
close(sockfd);
return -1;
}
return sockfd;
}

View File

@ -39,7 +39,7 @@ Display the <prompt> string in front of user input text field.
The prompt string is not included in the user input/command.
-f <font>::
Use the specified X11 core font (use +xfontsel+ to chose a font).
Use the specified X11 core font (use +xfontsel+ to choose a font).
-v::
Show version and exit.

View File

@ -6,7 +6,7 @@
project(
'i3',
'c',
version: '4.20.1',
version: '4.21',
default_options: [
'c_std=c11',
'warning_level=1', # enable all warnings (-Wall)
@ -316,7 +316,7 @@ xcb_util_xrm_dep = dependency('xcb-xrm', method: 'pkg-config')
xkbcommon_dep = dependency('xkbcommon', method: 'pkg-config')
xkbcommon_x11_dep = dependency('xkbcommon-x11', method: 'pkg-config')
yajl_dep = dependency('yajl', method: 'pkg-config')
libpcre_dep = dependency('libpcre', version: '>=8.10', method: 'pkg-config')
libpcre_dep = dependency('libpcre2-8', version: '>=10', method: 'pkg-config')
cairo_dep = dependency('cairo', version: '>=1.14.4', method: 'pkg-config')
pangocairo_dep = dependency('pangocairo', method: 'pkg-config')
glib_dep = dependency('glib-2.0', method: 'pkg-config')
@ -409,6 +409,7 @@ i3srcs = [
'src/sighandler.c',
'src/startup.c',
'src/sync.c',
'src/tiling_drag.c',
'src/tree.c',
'src/util.c',
'src/version.c',

View File

@ -166,8 +166,10 @@ state FOCUS_AUTO:
-> call cmd_focus_direction($direction)
state FOCUS_OUTPUT:
output = string
-> call cmd_focus_output($output)
output = word
-> call cmd_focus_output($output); FOCUS_OUTPUT
end
-> call cmd_focus_output(NULL); INITIAL
# kill [window|client]
state KILL:
@ -466,6 +468,8 @@ state TITLE_FORMAT:
state TITLE_WINDOW_ICON:
'padding'
-> TITLE_WINDOW_ICON_PADDING
enable = 'toggle'
-> TITLE_WINDOW_ICON_PADDING
enable = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive'
-> call cmd_title_window_icon($enable, 0)

View File

@ -56,7 +56,7 @@ state INITIAL:
exectype = 'exec_always', 'exec' -> EXEC
colorclass = 'client.background'
-> COLOR_SINGLE
colorclass = 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
colorclass = 'client.focused_inactive', 'client.focused_tab_title', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
-> COLOR_BORDER
# We ignore comments and 'set' lines (variables).
@ -400,8 +400,6 @@ state BINDCOMMAND:
->
command = string
-> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
end
-> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
################################################################################
# Mode configuration
@ -567,7 +565,7 @@ state BAR_POSITION:
-> call cfg_bar_position($position); BAR
state BAR_OUTPUT:
output = string
output = word
-> call cfg_bar_output($output); BAR
state BAR_TRAY_OUTPUT:

View File

@ -0,0 +1 @@
fix crash with "layout default"

View File

@ -0,0 +1 @@
Do not replace existing IPC socket on start

View File

@ -0,0 +1 @@
fix focus when moving container between outputs with mouse warp and focus_follows_mouse

View File

@ -0,0 +1 @@
Fix endless loop with transient_for windows

View File

@ -0,0 +1 @@
fix wrong failed reply on move workspace to output

View File

@ -0,0 +1 @@
changed WM registration selection from WM_S_S<screen> to WM_S<screen>

View File

@ -0,0 +1 @@
avoid graphics artifacts when changing the layout tree by initializing surfaces to all black

View File

@ -0,0 +1 @@
Fix segfault if command in bindsym is empty

View File

@ -0,0 +1 @@
update parent split con titles when child con swaps position with another child con

View File

@ -0,0 +1 @@
fix default font not being applied to bars if defined after bar block

View File

@ -0,0 +1 @@
strip trailing whitespace in bar output names

View File

@ -0,0 +1 @@
Fix crash if config contains nested variables.

View File

@ -0,0 +1 @@
Fix segfault with explicit mode "default" key bindings.

View File

@ -0,0 +1 @@
Restore BS_NORMAL _MOTIF_WM_HINTS correctly

View File

@ -0,0 +1 @@
Acquire the WM_Sn selection when starting as required by ICCCM

View File

@ -0,0 +1 @@
Refuse to start without valid IPC socket

View File

@ -0,0 +1 @@
Add client.focused_tab_title color option

View File

@ -0,0 +1 @@
Add support for multiple outputs in focus command

View File

@ -0,0 +1 @@
Allow moving tiling windows with the mouse

View File

@ -0,0 +1 @@
Add title_window_icon toggle

96
release-notes/generator.pl Executable file
View File

@ -0,0 +1,96 @@
#!/usr/bin/env perl
use strict;
use warnings;
use v5.10;
use Getopt::Long;
my @template = (
'
Release notes for i3 v4.21
This is i3 v4.21. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
Changes in i3 v4.21
',
'
Bugfixes
');
my $print_urls = 0;
my $result = GetOptions('print-urls' => \$print_urls);
sub get_number {
my $s = shift;
return $1 if $s =~ m/^(\d+)/;
return -1;
}
sub read_changefiles {
my $dirpath = shift;
opendir my $dir, $dirpath or die "Cannot open directory $dirpath: $!";
my @files = sort { get_number($a) <=> get_number($b) } readdir $dir;
closedir $dir;
my $s = '';
for my $filename (@files) {
next if $filename eq '.';
next if $filename eq '..';
next if $filename eq '0-example';
die "Filename $filename should start with a number (e.g. the pull request number)" unless get_number($filename) > 0;
$filename = $dirpath . '/' . $filename;
open my $in, '<', $filename or die "can't open $filename: $!";
my @lines = <$in>;
close $in or die "can't close $filename: $!";
my $content = trim(join("\n ", map { trim($_) } @lines));
die "$filename can't be empty" unless length($content) > 0;
my $url = '';
if ($print_urls) {
my $commit = `git log --diff-filter=A --pretty=format:"%H" $filename`;
$commit = trim($commit) if defined($commit);
die "$filename: git log failed to find commit" if ($?) || (length($commit) == 0);
my $pr = find_pr($commit);
$url = 'https://github.com/i3/i3/commit/' . $commit;
$url = 'https://github.com/i3/i3/pull/' . $pr if defined($pr);
$url = $url . "\n";
}
$s = $s . ' • ' . $content . "\n" . $url;
}
return $s;
}
sub find_pr {
my $hash = shift;
my $result = `git log --merges --ancestry-path --oneline $hash..next | grep 'Merge pull request' | tail -n1`;
return unless defined($result);
return unless ($result =~ /Merge pull request .([0-9]+)/);
return $1;
}
sub trim {
(my $s = $_[0]) =~ s/^\s+|\s+$//g;
return $s;
}
# Expected to run for i3's git root
my $changes = read_changefiles('release-notes/changes');
my $bugfixes = read_changefiles('release-notes/bugfixes');
print $template[0] . $changes . $template[1] . $bugfixes;

View File

@ -22,7 +22,8 @@ fi
if [ ! -e "RELEASE-NOTES-${RELEASE_VERSION}" ]
then
echo "RELEASE-NOTES-${RELEASE_VERSION} not found."
echo "RELEASE-NOTES-${RELEASE_VERSION} not found. Here is the output from the generator:"
./release-notes/generator.pl --print-urls
exit 1
fi

View File

@ -80,7 +80,7 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
*/
static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) {
/* The client is in tiling layout. We can still initiate a resize with the
* right mouse button, by chosing the border which is the most near one to
* right mouse button, by choosing the border which is the most near one to
* the position of the mouse pointer */
int to_right = con->rect.width - event->event_x,
to_left = event->event_x,
@ -188,9 +188,6 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
goto done;
}
if (ws != focused_workspace)
workspace_show(ws);
/* get the floating con */
Con *floatingcon = con_inside_floating(con);
const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT;
@ -218,7 +215,13 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
goto done;
}
/* 2: focus this con or one of its children. */
/* 2: floating modifier pressed, initiate a drag */
if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1 && !floatingcon) {
tiling_drag(con, event);
goto done;
}
/* 3: focus this con or one of its children. */
Con *con_to_focus = con;
if (in_stacked && dest == CLICK_DECORATION) {
/* If the container is a tab/stacked container and the click happened
@ -231,19 +234,22 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
}
}
}
if (ws != focused_workspace) {
workspace_show(ws);
}
con_activate(con_to_focus);
/* 3: For floating containers, we also want to raise them on click.
/* 4: For floating containers, we also want to raise them on click.
* We will skip handling events on floating cons in fullscreen mode */
Con *fs = con_get_fullscreen_covering_ws(ws);
if (floatingcon != NULL && fs != con) {
/* 4: floating_modifier plus left mouse button drags */
/* 5: floating_modifier plus left mouse button drags */
if (mod_pressed && is_left_click) {
floating_drag_window(floatingcon, event, false);
return;
}
/* 5: resize (floating) if this was a (left or right) click on the
/* 6: resize (floating) if this was a (left or right) click on the
* left/right/bottom border, or a right click on the decoration.
* also try resizing (tiling) if possible */
if (mod_pressed && is_right_click) {
@ -272,7 +278,7 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
return;
}
/* 6: dragging, if this was a click on a decoration (which did not lead
/* 7: dragging, if this was a click on a decoration (which did not lead
* to a resize) */
if (dest == CLICK_DECORATION && is_left_click) {
floating_drag_window(floatingcon, event, !was_focused);
@ -282,7 +288,13 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
goto done;
}
/* 7: floating modifier pressed, initiate a resize */
/* 8: floating modifier pressed, initiate a drag */
if ((mod_pressed || dest == CLICK_DECORATION) && event->detail == XCB_BUTTON_INDEX_1) {
tiling_drag(con, event);
goto done;
}
/* 9: floating modifier pressed, initiate a resize */
if (dest == CLICK_INSIDE && mod_pressed && is_right_click) {
if (floating_mod_on_tiled_client(con, event)) {
return;
@ -293,7 +305,7 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
xcb_flush(conn);
return;
}
/* 8: otherwise, check for border/decoration clicks and resize */
/* 10: otherwise, check for border/decoration clicks and resize */
if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
is_left_or_right_click) {
DLOG("Trying to resize (tiling)\n");

View File

@ -1023,6 +1023,76 @@ void cmd_mode(I3_CMD, const char *mode) {
ysuccess(true);
}
typedef struct user_output_name {
char *name;
TAILQ_ENTRY(user_output_name) user_output_names;
} user_output_name;
typedef TAILQ_HEAD(user_output_names_head, user_output_name) user_output_names_head;
static void user_output_names_add(user_output_names_head *list, const char *name) {
if (strcmp(name, "next") == 0) {
/* "next" here works like a wildcard: It "expands" to all available
* outputs. */
Output *output;
TAILQ_FOREACH (output, &outputs, outputs) {
user_output_name *co = scalloc(sizeof(user_output_name), 1);
co->name = sstrdup(output_primary_name(output));
TAILQ_INSERT_TAIL(list, co, user_output_names);
}
return;
}
user_output_name *co = scalloc(sizeof(user_output_name), 1);
co->name = sstrdup(name);
TAILQ_INSERT_TAIL(list, co, user_output_names);
return;
}
static Output *user_output_names_find_next(user_output_names_head *names, Output *current_output) {
Output *target_output = NULL;
user_output_name *uo;
TAILQ_FOREACH (uo, names, user_output_names) {
if (!target_output) {
/* The first available output from the list is used in 2 cases:
* 1. When we must wrap around the user list. For example, if user
* specifies outputs A B C and C is `current_output`.
* 2. When the current output is not in the user list. For example,
* user specifies A B C and D is `current_output`. */
target_output = get_output_from_string(current_output, uo->name);
}
if (strcasecmp(output_primary_name(current_output), uo->name) == 0) {
/* The current output is in the user list */
while (true) {
/* This corrupts the outer loop but it is ok since we are going
* to break anyway. */
uo = TAILQ_NEXT(uo, user_output_names);
if (!uo) {
/* We reached the end of the list. We should use the first
* available output that, if it exists, is already saved in
* target_output. */
break;
}
Output *out = get_output_from_string(current_output, uo->name);
if (out) {
return out;
}
}
break;
}
}
return target_output;
}
static void user_output_names_free(user_output_names_head *names) {
user_output_name *uo;
while (!TAILQ_EMPTY(names)) {
uo = TAILQ_FIRST(names);
free(uo->name);
TAILQ_REMOVE(names, uo, user_output_names);
free(uo);
}
}
/*
* Implementation of 'move [window|container|workspace] [to] output <strings>'.
*
@ -1031,40 +1101,21 @@ void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
/* Initialize a data structure that is used to save multiple user-specified
* output names since this function is called multiple types for each
* command call. */
typedef struct user_output_name {
char *name;
TAILQ_ENTRY(user_output_name) user_output_names;
} user_output_name;
static TAILQ_HEAD(user_output_names_head, user_output_name) user_output_names = TAILQ_HEAD_INITIALIZER(user_output_names);
static user_output_names_head names = TAILQ_HEAD_INITIALIZER(names);
if (name) {
if (strcmp(name, "next") == 0) {
/* "next" here works like a wildcard: It "expands" to all available
* outputs. */
Output *output;
TAILQ_FOREACH (output, &outputs, outputs) {
user_output_name *co = scalloc(sizeof(user_output_name), 1);
co->name = sstrdup(output_primary_name(output));
TAILQ_INSERT_TAIL(&user_output_names, co, user_output_names);
}
return;
}
user_output_name *co = scalloc(sizeof(user_output_name), 1);
co->name = sstrdup(name);
TAILQ_INSERT_TAIL(&user_output_names, co, user_output_names);
user_output_names_add(&names, name);
return;
}
HANDLE_EMPTY_MATCH;
if (TAILQ_EMPTY(&user_output_names)) {
if (TAILQ_EMPTY(&names)) {
yerror("At least one output must be specified");
return;
}
bool success = false;
user_output_name *uo;
owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) {
Con *ws = con_get_workspace(current->con);
@ -1073,41 +1124,7 @@ void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
}
Output *current_output = get_output_for_con(ws);
Output *target_output = NULL;
TAILQ_FOREACH (uo, &user_output_names, user_output_names) {
if (strcasecmp(output_primary_name(current_output), uo->name) == 0) {
/* The current output is in the user list */
while (true) {
/* This corrupts the outer loop but it is ok since we are
* going to break anyway. */
uo = TAILQ_NEXT(uo, user_output_names);
if (!uo) {
/* We reached the end of the list. We should use the
* first available output that, if it exists, is
* already saved in target_output. */
break;
}
Output *out = get_output_from_string(current_output, uo->name);
if (out) {
DLOG("Found next target for workspace %s from user list: %s\n", ws->name, uo->name);
target_output = out;
break;
}
}
break;
}
if (!target_output) {
/* The first available output from the list is used in 2 cases:
* 1. When we must wrap around the user list. For example, if
* user specifies outputs A B C and C is `current_output`.
* 2. When the current output is not in the user list. For
* example, user specifies A B C and D is `current_output`.
*/
DLOG("Found first target for workspace %s from user list: %s\n", ws->name, uo->name);
target_output = get_output_from_string(current_output, uo->name);
}
}
Output *target_output = user_output_names_find_next(&names, current_output);
if (target_output) {
if (move_workspace) {
workspace_move_to_output(ws, target_output);
@ -1117,13 +1134,7 @@ void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
success = true;
}
}
while (!TAILQ_EMPTY(&user_output_names)) {
uo = TAILQ_FIRST(&user_output_names);
free(uo->name);
TAILQ_REMOVE(&user_output_names, uo, user_output_names);
free(uo);
}
user_output_names_free(&names);
cmd_output->needs_tree_render = success;
if (success) {
@ -1757,6 +1768,17 @@ void cmd_open(I3_CMD) {
*
*/
void cmd_focus_output(I3_CMD, const char *name) {
static user_output_names_head names = TAILQ_HEAD_INITIALIZER(names);
if (name) {
user_output_names_add(&names, name);
return;
}
if (TAILQ_EMPTY(&names)) {
yerror("At least one output must be specified");
return;
}
HANDLE_EMPTY_MATCH;
if (TAILQ_EMPTY(&owindows)) {
@ -1765,25 +1787,29 @@ void cmd_focus_output(I3_CMD, const char *name) {
}
Output *current_output = get_output_for_con(TAILQ_FIRST(&owindows)->con);
Output *output = get_output_from_string(current_output, name);
Output *target_output = user_output_names_find_next(&names, current_output);
user_output_names_free(&names);
bool success = false;
if (target_output) {
success = true;
if (!output) {
yerror("Output %s not found.", name);
return;
/* get visible workspace on output */
Con *ws = NULL;
GREP_FIRST(ws, output_get_content(target_output->con), workspace_is_visible(child));
if (!ws) {
yerror("BUG: No workspace found on output.");
return;
}
workspace_show(ws);
}
/* get visible workspace on output */
Con *ws = NULL;
GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
if (!ws) {
yerror("BUG: No workspace found on output.");
return;
cmd_output->needs_tree_render = success;
if (success) {
ysuccess(true);
} else {
yerror("No output matched");
}
workspace_show(ws);
cmd_output->needs_tree_render = true;
ysuccess(true);
}
/*
@ -2038,20 +2064,40 @@ void cmd_title_format(I3_CMD, const char *format) {
}
/*
* Implementation of 'title_window_icon <yes|no>' and 'title_window_icon padding <px>'
* Implementation of 'title_window_icon <yes|no|toggle>' and 'title_window_icon padding <px>'
*
*/
void cmd_title_window_icon(I3_CMD, const char *enable, int padding) {
if (enable != NULL && !boolstr(enable)) {
padding = -1;
bool is_toggle = false;
if (enable != NULL) {
if (strcmp(enable, "toggle") == 0) {
is_toggle = true;
} else if (!boolstr(enable)) {
padding = -1;
}
}
DLOG("setting window_icon=%d\n", padding);
HANDLE_EMPTY_MATCH;
owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) {
DLOG("setting window_icon for %p / %s\n", current->con, current->con->name);
current->con->window_icon_padding = padding;
if (is_toggle) {
const int current_padding = current->con->window_icon_padding;
if (padding > 0) {
if (current_padding < 0) {
current->con->window_icon_padding = padding;
} else {
/* toggle off, but store padding given */
current->con->window_icon_padding = -(padding + 1);
}
} else {
/* Set to negative of (current value+1) to keep old padding when toggling */
current->con->window_icon_padding = -(current_padding + 1);
}
} else {
current->con->window_icon_padding = padding;
}
DLOG("Set window_icon for %p / %s to %d\n", current->con, current->con->name, current->con->window_icon_padding);
if (current->con->window != NULL) {
/* Make sure the window title is redrawn immediately. */

View File

@ -390,7 +390,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
free(possible_tokens);
/* Contains the same amount of characters as 'input' has, but with
* the unparseable part highlighted using ^ characters. */
* the unparsable part highlighted using ^ characters. */
char *position = smalloc(len + 1);
for (const char *copywalk = input; *copywalk != '\0'; copywalk++)
position[(copywalk - input)] = (copywalk >= walk ? '^' : ' ');

View File

@ -730,6 +730,41 @@ Con *con_by_mark(const char *mark) {
return NULL;
}
/*
* Start from a container and traverse the transient_for linked list. Returns
* true if target window is found in the list. Protects againsts potential
* cycles.
*
*/
bool con_find_transient_for_window(Con *start, xcb_window_t target) {
Con *transient_con = start;
int count = con_num_windows(croot);
while (transient_con != NULL &&
transient_con->window != NULL &&
transient_con->window->transient_for != XCB_NONE) {
DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, target = 0x%08x\n",
transient_con->window->id, transient_con->window->transient_for, target);
if (transient_con->window->transient_for == target) {
return true;
}
Con *next_transient = con_by_window_id(transient_con->window->transient_for);
if (next_transient == NULL) {
break;
}
/* Some clients (e.g. x11-ssh-askpass) actually set WM_TRANSIENT_FOR to
* their own window id, so break instead of looping endlessly. */
if (transient_con == next_transient) {
break;
}
transient_con = next_transient;
if (count-- <= 0) { /* Avoid cycles, see #4404 */
break;
}
}
return false;
}
/*
* Returns true if and only if the given containers holds the mark.
*
@ -852,8 +887,6 @@ void con_unmark(Con *con, const char *name) {
Con *con_for_window(Con *con, i3Window *window, Match **store_match) {
Con *child;
Match *match;
//DLOG("searching con for window %p starting at con %p\n", window, con);
//DLOG("class == %s\n", window->class_class);
TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
TAILQ_FOREACH (match, &(child->swallow_head), matches) {
@ -1012,8 +1045,8 @@ void con_fix_percent(Con *con) {
Con *child;
int children = con_num_children(con);
// calculate how much we have distributed and how many containers
// with a percentage set we have
/* calculate how much we have distributed and how many containers with a
* percentage set we have */
double total = 0.0;
int children_with_percent = 0;
TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
@ -1023,8 +1056,8 @@ void con_fix_percent(Con *con) {
}
}
// if there were children without a percentage set, set to a value that
// will make those children proportional to all others
/* if there were children without a percentage set, set to a value that
* will make those children proportional to all others */
if (children_with_percent != children) {
TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
if (child->percent <= 0.0) {
@ -1037,8 +1070,8 @@ void con_fix_percent(Con *con) {
}
}
// if we got a zero, just distribute the space equally, otherwise
// distribute according to the proportions we got
/* if we got a zero, just distribute the space equally, otherwise
* distribute according to the proportions we got */
if (total == 0.0) {
TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
child->percent = 1.0 / children;
@ -1356,17 +1389,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
return true;
}
/*
* Moves the given container to the given mark.
*
*/
bool con_move_to_mark(Con *con, const char *mark) {
Con *target = con_by_mark(mark);
if (target == NULL) {
DLOG("found no container with mark \"%s\"\n", mark);
return false;
}
bool con_move_to_target(Con *con, Con *target) {
/* For target containers in the scratchpad, we just send the window to the scratchpad. */
if (con_get_workspace(target) == workspace_get("__i3_scratch")) {
DLOG("target container is in the scratchpad, moving container to scratchpad.\n");
@ -1403,6 +1426,20 @@ bool con_move_to_mark(Con *con, const char *mark) {
return _con_move_to_con(con, target, false, true, false, false, true);
}
/*
* Moves the given container to the given mark.
*
*/
bool con_move_to_mark(Con *con, const char *mark) {
Con *target = con_by_mark(mark);
if (target == NULL) {
DLOG("found no container with mark \"%s\"\n", mark);
return false;
}
return con_move_to_target(con, target);
}
/*
* Moves the given container to the currently focused container on the given
* workspace.
@ -2202,7 +2239,6 @@ void con_set_urgency(Con *con, bool urgent) {
} else
DLOG("Discarding urgency WM_HINT because timer is running\n");
//CLIENT_LOG(con);
if (con->window) {
if (con->urgent) {
gettimeofday(&con->window->urgent, NULL);

View File

@ -197,6 +197,7 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff", "#484e50");
INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888", "#292d2e");
INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff", "#900000");
config.client.got_focused_tab_title = false;
/* border and indicator color are ignored for placeholder contents */
INIT_COLOR(config.client.placeholder, "#000000", "#0c0c0c", "#ffffff", "#000000");
@ -272,6 +273,15 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
set_font(&config.font);
}
/* Make bar config blocks without a configured font use the i3-wide font. */
Barconfig *current;
TAILQ_FOREACH (current, &barconfigs, configs) {
if (current->font != NULL) {
continue;
}
current->font = sstrdup(config.font.pattern);
}
if (load_type == C_RELOAD) {
translate_keysyms();
grab_all_keys(conn);

View File

@ -163,15 +163,9 @@ i3_event_state_mask_t event_state_from_str(const char *str) {
return result;
}
static char *font_pattern;
CFGFUN(font, const char *font) {
config.font = load_font(font, true);
set_font(&config.font);
/* Save the font pattern for using it as bar font later on */
FREE(font_pattern);
font_pattern = sstrdup(font);
}
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
@ -186,6 +180,11 @@ static char *current_mode;
static bool current_mode_pango_markup;
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
if (current_mode == NULL) {
/* When using an invalid mode name, e.g. “default” */
return;
}
configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, current_mode, current_mode_pango_markup);
}
@ -467,24 +466,32 @@ CFGFUN(color_single, const char *colorclass, const char *color) {
}
CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border) {
#define APPLY_COLORS(classname) \
do { \
if (strcmp(colorclass, "client." #classname) == 0) { \
config.client.classname.border = draw_util_hex_to_color(border); \
config.client.classname.background = draw_util_hex_to_color(background); \
config.client.classname.text = draw_util_hex_to_color(text); \
if (indicator != NULL) { \
config.client.classname.indicator = draw_util_hex_to_color(indicator); \
} \
if (child_border != NULL) { \
config.client.classname.child_border = draw_util_hex_to_color(child_border); \
} else { \
config.client.classname.child_border = config.client.classname.background; \
} \
} \
#define APPLY_COLORS(classname) \
do { \
if (strcmp(colorclass, "client." #classname) == 0) { \
if (strcmp("focused_tab_title", #classname) == 0) { \
config.client.got_focused_tab_title = true; \
if (indicator || child_border) { \
ELOG("indicator and child_border colors have no effect for client.focused_tab_title\n"); \
} \
} \
config.client.classname.border = draw_util_hex_to_color(border); \
config.client.classname.background = draw_util_hex_to_color(background); \
config.client.classname.text = draw_util_hex_to_color(text); \
if (indicator != NULL) { \
config.client.classname.indicator = draw_util_hex_to_color(indicator); \
} \
if (child_border != NULL) { \
config.client.classname.child_border = draw_util_hex_to_color(child_border); \
} else { \
config.client.classname.child_border = config.client.classname.background; \
} \
return; \
} \
} while (0)
APPLY_COLORS(focused_inactive);
APPLY_COLORS(focused_tab_title);
APPLY_COLORS(focused);
APPLY_COLORS(unfocused);
APPLY_COLORS(urgent);
@ -744,10 +751,6 @@ CFGFUN(bar_finish) {
config.number_barconfigs++;
/* If no font was explicitly set, we use the i3 font as default */
if (current_bar->font == NULL && font_pattern != NULL)
current_bar->font = sstrdup(font_pattern);
TAILQ_INSERT_TAIL(&barconfigs, current_bar, configs);
/* Simply reset the pointer, but don't free the resources. */
current_bar = NULL;

View File

@ -165,8 +165,6 @@ static void clear_stack(struct stack *ctx) {
static void next_state(const cmdp_token *token, struct parser_ctx *ctx) {
cmdp_state _next_state = token->next_state;
//printf("token = name %s identifier %s\n", token->name, token->identifier);
//printf("next_state = %d\n", token->next_state);
if (token->next_state == __CALL) {
struct ConfigResultIR subcommand_output = {
.ctx = ctx,
@ -254,7 +252,6 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
bool token_handled;
linecnt = 1;
// TODO: make this testable
#ifndef TEST_PARSER
struct ConfigResultIR subcommand_output = {
.ctx = ctx,
@ -270,8 +267,6 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
walk++;
//printf("remaining input: %s\n", walk);
cmdp_token_ptr *ptr = &(tokens[ctx->state]);
token_handled = false;
for (c = 0; c < ptr->n; c++) {
@ -378,7 +373,6 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
}
if (strcmp(token->name, "end") == 0) {
//printf("checking for end: *%s*\n", walk);
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
next_state(token, ctx);
token_handled = true;
@ -386,7 +380,6 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
* datastructure for commands which do *not* specify any
* criteria, we re-initialize the criteria system after
* every command. */
// TODO: make this testable
#ifndef TEST_PARSER
cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
#endif
@ -445,7 +438,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
const char *error_line = start_of_line(walk, input);
/* Contains the same amount of characters as 'input' has, but with
* the unparseable part highlighted using ^ characters. */
* the unparsable part highlighted using ^ characters. */
char *position = scalloc(strlen(error_line) + 1, 1);
const char *copywalk;
for (copywalk = error_line;
@ -1008,9 +1001,14 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi
char *next;
for (next = bufcopy;
next < (bufcopy + stbuf.st_size) &&
(next = strcasestr(next, current->key)) != NULL;
next += strlen(current->key)) {
*next = '_';
(next = strcasestr(next, current->key)) != NULL;) {
/* We need to invalidate variables completely (otherwise we may count
* the same variable more than once, thus causing buffer overflow or
* allocation failure) with spaces (variable names cannot contain spaces) */
char *end = next + strlen(current->key);
while (next < end) {
*next++ = ' ';
}
extra_bytes += extra;
}
}

View File

@ -69,7 +69,7 @@ static void print_config_path(const char *path, const char *role) {
/*
* Connects to i3 to find out the currently running version. Useful since it
* might be different from the version compiled into this binary (maybe the
* user didnt correctly install i3 or forgot te restart it).
* user didnt correctly install i3 or forgot to restart it).
*
* The output looks like this:
* Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804)

View File

@ -253,7 +253,7 @@ bool floating_enable(Con *con, bool automatic) {
}
/* Consider the part of the focus stack of our current workspace:
* [ ... S_{i-1} S_{i} S_{i+1} ... ]
* Where S_{x} is a container tree and the container 'con' that is beeing switched to
* Where S_{x} is a container tree and the container 'con' that is being switched to
* floating belongs in S_{i}. The new floating container, 'nc', will have the
* workspace as its parent so it needs to be placed in this stack. If C was focused
* we just need to call con_focus(). Otherwise, nc must be placed before or after S_{i}.

View File

@ -70,11 +70,9 @@ bool event_is_ignored(const int sequence, const int response_type) {
event->response_type != response_type)
continue;
/* instead of removing a sequence number we better wait until it gets
* garbage collected. it may generate multiple events (there are multiple
* enter_notifies for one configure_request, for example). */
//SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events);
//free(event);
/* Instead of removing & freeing a sequence number we better wait until
* it gets garbage collected. It may generate multiple events (there
* are multiple enter_notifies for one configure_request, for example). */
return true;
}
@ -270,7 +268,7 @@ static void handle_map_request(xcb_map_request_event_t *event) {
* Configure requests are received when the application wants to resize windows
* on their own.
*
* We generate a synthethic configure notify event to signalize the client its
* We generate a synthetic configure notify event to signalize the client its
* "new" position.
*
*/
@ -801,7 +799,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
} else if (event->type == A_WM_CHANGE_STATE) {
/* http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4 */
if (event->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) {
/* For compatiblity reasons, Wine will request iconic state and cannot ensure that the WM has agreed on it;
/* For compatibility reasons, Wine will request iconic state and cannot ensure that the WM has agreed on it;
* immediately revert to normal to avoid being stuck in a paused state. */
DLOG("Client has requested iconic state, rejecting. (window = %08x)\n", event->window);
long data[] = {XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE};
@ -1184,14 +1182,14 @@ static bool handle_machine_change(Con *con, xcb_get_property_reply_t *prop) {
}
/*
* Handles the _MOTIF_WM_HINTS property of specifing window deocration settings.
* Handles the _MOTIF_WM_HINTS property of specifying window deocration settings.
*
*/
static bool handle_motif_hints_change(Con *con, xcb_get_property_reply_t *prop) {
border_style_t motif_border_style;
window_update_motif_hints(con->window, prop, &motif_border_style);
bool has_mwm_hints = window_update_motif_hints(con->window, prop, &motif_border_style);
if (motif_border_style != con->border_style && motif_border_style != BS_NORMAL) {
if (has_mwm_hints && motif_border_style != con->border_style) {
DLOG("Update border style of con %p to %d\n", con, motif_border_style);
con_set_border_style(con, motif_border_style, con->current_border_width);
@ -1346,7 +1344,7 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom)
}
if (handler == NULL) {
//DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom);
/* DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom); */
return;
}
@ -1524,7 +1522,7 @@ void handle_event(int type, xcb_generic_event_t *event) {
break;
default:
//DLOG("Unhandled event of type %d\n", type);
/* DLOG("Unhandled event of type %d\n", type); */
break;
}
}

View File

@ -137,7 +137,11 @@ void open_logbuffer(void) {
* For 512 MiB of RAM this will lead to a 5 MiB log buffer.
* At the moment (2011-12-10), no testcase leads to an i3 log
* of more than ~ 600 KiB. */
logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size);
logbuffer_size = shmlog_size;
if (physical_mem_bytes * 0.01 < (long long)shmlog_size) {
logbuffer_size = physical_mem_bytes * 0.01;
}
#if defined(__FreeBSD__)
sasprintf(&shmlogname, "/tmp/i3-log-%d", getpid());
#else

View File

@ -683,6 +683,11 @@ int main(int argc, char *argv[]) {
else
config.ipc_socket_path = sstrdup(config.ipc_socket_path);
}
/* Create the UNIX domain socket for IPC */
int ipc_socket = create_socket(config.ipc_socket_path, &current_socketpath);
if (ipc_socket == -1) {
die("Could not create the IPC socket: %s", config.ipc_socket_path);
}
if (config.force_xinerama) {
force_xinerama = true;
@ -691,11 +696,11 @@ int main(int argc, char *argv[]) {
/* Acquire the WM_Sn selection. */
{
/* Get the WM_Sn atom */
char *atom_name = xcb_atom_name_by_screen("WM_S", conn_screen);
char *atom_name = xcb_atom_name_by_screen("WM", conn_screen);
wm_sn_selection_owner = xcb_generate_id(conn);
if (atom_name == NULL) {
ELOG("xcb_atom_name_by_screen(\"WM_S\", %d) failed, exiting\n", conn_screen);
ELOG("xcb_atom_name_by_screen(\"WM\", %d) failed, exiting\n", conn_screen);
return 1;
}
@ -988,15 +993,10 @@ int main(int argc, char *argv[]) {
tree_render();
/* Create the UNIX domain socket for IPC */
int ipc_socket = create_socket(config.ipc_socket_path, &current_socketpath);
if (ipc_socket == -1) {
ELOG("Could not create the IPC socket, IPC disabled\n");
} else {
struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
ev_io_start(main_loop, ipc_io);
}
/* Listen to the IPC socket for clients */
struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
ev_io_start(main_loop, ipc_io);
/* Chose a file name in /tmp/ based on the PID */
char *log_stream_socket_path = get_process_filename("log-stream-socket");

View File

@ -123,7 +123,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
geomc = xcb_get_geometry(conn, d);
/* Check if the window is mapped (it could be not mapped when intializing and
/* Check if the window is mapped (it could be not mapped when initializing and
calling manage_window() for every window) */
if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
DLOG("Could not get attributes\n");
@ -214,8 +214,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL));
bool urgency_hint;
window_update_hints(cwindow, xcb_get_property_reply(conn, wm_hints_cookie, NULL), &urgency_hint);
border_style_t motif_border_style = BS_NORMAL;
window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
border_style_t motif_border_style;
bool has_mwm_hints = window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
window_update_normal_hints(cwindow, xcb_get_property_reply(conn, wm_normal_hints_cookie, NULL), geom);
window_update_machine(cwindow, xcb_get_property_reply(conn, wm_machine_cookie, NULL));
xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
@ -485,34 +485,17 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
(cwindow->leader != XCB_NONE &&
cwindow->leader != cwindow->id &&
con_by_window_id(cwindow->leader) != NULL)) {
LOG("This window is transient for another window, setting floating\n");
DLOG("This window is transient for another window, setting floating\n");
want_floating = true;
if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN &&
fs != NULL) {
LOG("There is a fullscreen window, leaving fullscreen mode\n");
DLOG("There is a fullscreen window, leaving fullscreen mode\n");
con_toggle_fullscreen(fs, CF_OUTPUT);
} else if (config.popup_during_fullscreen == PDF_SMART &&
fs != NULL &&
fs->window != NULL) {
i3Window *transient_win = cwindow;
while (transient_win != NULL &&
transient_win->transient_for != XCB_NONE) {
if (transient_win->transient_for == fs->window->id) {
LOG("This floating window belongs to the fullscreen window (popup_during_fullscreen == smart)\n");
set_focus = true;
break;
}
Con *next_transient = con_by_window_id(transient_win->transient_for);
if (next_transient == NULL)
break;
/* Some clients (e.g. x11-ssh-askpass) actually set
* WM_TRANSIENT_FOR to their own window id, so break instead of
* looping endlessly. */
if (transient_win == next_transient->window)
break;
transient_win = next_transient->window;
}
set_focus = con_find_transient_for_window(nc, fs->window->id);
}
}
@ -528,7 +511,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
if (nc->geometry.width == 0)
nc->geometry = (Rect){geom->x, geom->y, geom->width, geom->height};
if (motif_border_style != BS_NORMAL) {
if (has_mwm_hints) {
DLOG("MOTIF_WM_HINTS specifies decorations (border_style = %d)\n", motif_border_style);
if (want_floating) {
con_set_border_style(nc, motif_border_style, config.default_floating_border_width);

View File

@ -228,10 +228,19 @@ static void move_to_output_directed(Con *con, direction_t direction) {
* the focused container, con, is now a child of ws. To work around this
* and still produce the correct workspace focus events (see
* 517-regress-move-direction-ipc.t) we need to temporarily set focused
* to the old workspace. */
* to the old workspace.
*
* The following happen:
* 1. Focus con to push it on the top of the focus stack in its new
* workspace
* 2. Set focused to the old workspace to force workspace_show to
* execute
* 3. workspace_show will descend focus and target our con for
* focusing. This also ensures that the mouse warps correctly.
* See: #3518. */
con_focus(con);
focused = old_ws;
workspace_show(ws);
con_focus(con);
}
/* force re-painting the indicators */
@ -321,6 +330,9 @@ void tree_move(Con *con, direction_t direction) {
TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes);
}
/* redraw parents to ensure all parent split container titles are updated correctly */
con_force_split_parents_redraw(con);
ipc_send_window_event("move", con);
return;
}

View File

@ -665,11 +665,12 @@ static bool randr_query_outputs_15(void) {
new->primary = monitor_info->primary;
new->changed =
update_if_necessary(&(new->rect.x), monitor_info->x) |
update_if_necessary(&(new->rect.y), monitor_info->y) |
update_if_necessary(&(new->rect.width), monitor_info->width) |
update_if_necessary(&(new->rect.height), monitor_info->height);
const bool update_x = update_if_necessary(&(new->rect.x), monitor_info->x);
const bool update_y = update_if_necessary(&(new->rect.y), monitor_info->y);
const bool update_w = update_if_necessary(&(new->rect.width), monitor_info->width);
const bool update_h = update_if_necessary(&(new->rect.height), monitor_info->height);
new->changed = update_x || update_y || update_w || update_h;
DLOG("name %s, x %d, y %d, width %d px, height %d px, width %d mm, height %d mm, primary %d, automatic %d\n",
name,
@ -743,10 +744,11 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
return;
}
bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
update_if_necessary(&(new->rect.y), crtc->y) |
update_if_necessary(&(new->rect.width), crtc->width) |
update_if_necessary(&(new->rect.height), crtc->height);
const bool update_x = update_if_necessary(&(new->rect.x), crtc->x);
const bool update_y = update_if_necessary(&(new->rect.y), crtc->y);
const bool update_w = update_if_necessary(&(new->rect.width), crtc->width);
const bool update_h = update_if_necessary(&(new->rect.height), crtc->height);
const bool updated = update_x || update_y || update_w || update_h;
free(crtc);
new->active = (new->rect.width != 0 && new->rect.height != 0);
if (!new->active) {
@ -943,9 +945,11 @@ void randr_query_outputs(void) {
uint32_t width = min(other->rect.width, output->rect.width);
uint32_t height = min(other->rect.height, output->rect.height);
if (update_if_necessary(&(output->rect.width), width) |
update_if_necessary(&(output->rect.height), height))
const bool update_w = update_if_necessary(&(output->rect.width), width);
const bool update_h = update_if_necessary(&(output->rect.height), height);
if (update_w || update_h) {
output->changed = true;
}
update_if_necessary(&(other->rect.width), width);
update_if_necessary(&(other->rect.height), height);

View File

@ -20,34 +20,23 @@
*
*/
struct regex *regex_new(const char *pattern) {
const char *error;
int errorcode, offset;
int errorcode;
PCRE2_SIZE offset;
struct regex *re = scalloc(1, sizeof(struct regex));
re->pattern = sstrdup(pattern);
int options = PCRE_UTF8;
uint32_t options = PCRE2_UTF;
/* We use PCRE_UCP so that \B, \b, \D, \d, \S, \s, \W, \w and some POSIX
* character classes play nicely with Unicode */
options |= PCRE_UCP;
while (!(re->regex = pcre_compile2(pattern, options, &errorcode, &error, &offset, NULL))) {
/* If the error is that PCRE was not compiled with UTF-8 support we
* disable it and try again */
if (errorcode == 32) {
options &= ~PCRE_UTF8;
continue;
}
ELOG("PCRE regular expression compilation failed at %d: %s\n",
offset, error);
options |= PCRE2_UCP;
if (!(re->regex = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, options, &errorcode, &offset, NULL))) {
PCRE2_UCHAR buffer[256];
pcre2_get_error_message(errorcode, buffer, sizeof(buffer));
ELOG("PCRE regular expression compilation failed at %lu: %s\n",
offset, buffer);
regex_free(re);
return NULL;
}
re->extra = pcre_study(re->regex, 0, &error);
/* If an error happened, we print the error message, but continue.
* Studying the regular expression leads to faster matching, but its not
* absolutely necessary. */
if (error) {
ELOG("PCRE regular expression studying failed: %s\n", error);
}
return re;
}
@ -60,7 +49,6 @@ void regex_free(struct regex *regex) {
return;
FREE(regex->pattern);
FREE(regex->regex);
FREE(regex->extra);
FREE(regex);
}
@ -71,17 +59,22 @@ void regex_free(struct regex *regex) {
*
*/
bool regex_matches(struct regex *regex, const char *input) {
pcre2_match_data *match_data;
int rc;
match_data = pcre2_match_data_create_from_pattern(regex->regex, NULL);
/* We use strlen() because pcre_exec() expects the length of the input
* string in bytes */
if ((rc = pcre_exec(regex->regex, regex->extra, input, strlen(input), 0, 0, NULL, 0)) == 0) {
rc = pcre2_match(regex->regex, (PCRE2_SPTR)input, strlen(input), 0, 0, match_data, NULL);
pcre2_match_data_free(match_data);
if (rc > 0) {
LOG("Regular expression \"%s\" matches \"%s\"\n",
regex->pattern, input);
return true;
}
if (rc == PCRE_ERROR_NOMATCH) {
if (rc == PCRE2_ERROR_NOMATCH) {
LOG("Regular expression \"%s\" does not match \"%s\"\n",
regex->pattern, input);
return false;

View File

@ -226,34 +226,12 @@ static void render_root(Con *con, Con *fullscreen) {
}
Con *floating_child = con_descend_focused(child);
Con *transient_con = floating_child;
bool is_transient_for = false;
while (transient_con != NULL &&
transient_con->window != NULL &&
transient_con->window->transient_for != XCB_NONE) {
DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, fullscreen_id = 0x%08x\n",
transient_con->window->id, transient_con->window->transient_for, fullscreen->window->id);
if (transient_con->window->transient_for == fullscreen->window->id) {
is_transient_for = true;
break;
}
Con *next_transient = con_by_window_id(transient_con->window->transient_for);
if (next_transient == NULL)
break;
/* Some clients (e.g. x11-ssh-askpass) actually set
* WM_TRANSIENT_FOR to their own window id, so break instead of
* looping endlessly. */
if (transient_con == next_transient)
break;
transient_con = next_transient;
}
if (!is_transient_for)
continue;
else {
if (con_find_transient_for_window(floating_child, fullscreen->window->id)) {
DLOG("Rendering floating child even though in fullscreen mode: "
"floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n",
floating_child->window->transient_for, fullscreen->window->id);
} else {
continue;
}
}
DLOG("floating child at (%d,%d) with %d x %d\n",

385
src/tiling_drag.c Normal file
View File

@ -0,0 +1,385 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* tiling_drag.c: Reposition tiled windows by dragging.
*
*/
#include "all.h"
static xcb_window_t create_drop_indicator(Rect rect);
/*
* Includes decoration (container title) to the container's rect. This way we
* can find the correct drop target if the mouse is on a container's
* decoration.
*
*/
static Rect con_rect_plus_deco_height(Con *con) {
Rect rect = con->rect;
rect.height += con->deco_rect.height;
if (rect.y < con->deco_rect.height) {
rect.y = 0;
} else {
rect.y -= con->deco_rect.height;
}
return rect;
}
/*
* Return an appropriate target at given coordinates.
*
*/
static Con *find_drop_target(uint32_t x, uint32_t y) {
Con *con;
TAILQ_FOREACH (con, &all_cons, all_cons) {
Rect rect = con_rect_plus_deco_height(con);
if (rect_contains(rect, x, y) &&
con_has_managed_window(con) &&
!con_is_floating(con) &&
!con_is_hidden(con)) {
Con *ws = con_get_workspace(con);
if (!workspace_is_visible(ws)) {
continue;
}
Con *fs = con_get_fullscreen_covering_ws(ws);
return fs ? fs : con;
}
}
/* Couldn't find leaf container, get a workspace. */
Output *output = get_output_containing(x, y);
if (!output) {
return NULL;
}
Con *content = output_get_content(output->con);
/* Still descend because you can drag to the bar on an non-empty workspace. */
return con_descend_tiling_focused(content);
}
typedef enum { DT_SIBLING,
DT_CENTER,
DT_PARENT
} drop_type_t;
struct callback_params {
xcb_window_t *indicator;
Con **target;
direction_t *direction;
drop_type_t *drop_type;
};
static Rect adjust_rect(Rect rect, direction_t direction, uint32_t threshold) {
switch (direction) {
case D_LEFT:
rect.width = threshold;
break;
case D_UP:
rect.height = threshold;
break;
case D_RIGHT:
rect.x += (rect.width - threshold);
rect.width = threshold;
break;
case D_DOWN:
rect.y += (rect.height - threshold);
rect.height = threshold;
break;
}
return rect;
}
static bool con_on_side_of_parent(Con *con, direction_t direction) {
const orientation_t orientation = orientation_from_direction(direction);
direction_t reverse_direction;
switch (direction) {
case D_LEFT:
reverse_direction = D_RIGHT;
break;
case D_RIGHT:
reverse_direction = D_LEFT;
break;
case D_UP:
reverse_direction = D_DOWN;
break;
case D_DOWN:
reverse_direction = D_UP;
break;
}
return (con_orientation(con->parent) != orientation ||
con->parent->layout == L_STACKED || con->parent->layout == L_TABBED ||
con_descend_direction(con->parent, reverse_direction) == con);
}
/*
* The callback that is executed on every mouse move while dragging. On each
* invocation we determine the drop target and the direction in which to insert
* the dragged container. The indicator window is updated to show the new
* position of the dragged container. The target container and direction are
* passed out using the callback params.
*
*/
DRAGGING_CB(drag_callback) {
/* 30% of the container (minus the parent indicator) is used to drop the
* dragged container as a sibling to the target */
const double sibling_indicator_percent_of_rect = 0.3;
/* Use the base decoration height and add a few pixels. This makes the
* outer indicator generally thin but at least thick enough to cover
* container titles */
const double parent_indicator_max_size = render_deco_height() + logical_px(5);
Con *target = find_drop_target(new_x, new_y);
if (target == NULL) {
return;
}
Rect rect = con_rect_plus_deco_height(target);
direction_t direction = 0;
drop_type_t drop_type = DT_CENTER;
bool draw_window = true;
const struct callback_params *params = extra;
if (target->type == CT_WORKSPACE) {
goto create_indicator;
}
/* Define the thresholds in pixels. The drop type depends on the cursor
* position. */
const uint32_t min_rect_dimension = min(rect.width, rect.height);
const uint32_t sibling_indicator_size = max(logical_px(2), (uint32_t)(sibling_indicator_percent_of_rect * min_rect_dimension));
const uint32_t parent_indicator_size = min(
parent_indicator_max_size,
/* For small containers, start where the sibling indicator finishes.
* This is always at least 1 pixel. We use min() to not override the
* sibling indicator: */
sibling_indicator_size - 1);
/* Find which edge the cursor is closer to. */
const uint32_t d_left = new_x - rect.x;
const uint32_t d_top = new_y - rect.y;
const uint32_t d_right = rect.x + rect.width - new_x;
const uint32_t d_bottom = rect.y + rect.height - new_y;
const uint32_t d_min = min(min(d_left, d_right), min(d_top, d_bottom));
/* And move the container towards that direction. */
if (d_left == d_min) {
direction = D_LEFT;
} else if (d_top == d_min) {
direction = D_UP;
} else if (d_right == d_min) {
direction = D_RIGHT;
} else if (d_bottom == d_min) {
direction = D_DOWN;
} else {
/* Keep the compiler happy */
ELOG("min() is broken\n");
assert(false);
}
const bool target_parent = (d_min < parent_indicator_size &&
con_on_side_of_parent(target, direction));
const bool target_sibling = (d_min < sibling_indicator_size);
drop_type = target_parent ? DT_PARENT : (target_sibling ? DT_SIBLING : DT_CENTER);
/* target == con makes sense only when we are moving away from target's parent. */
if (drop_type != DT_PARENT && target == con) {
draw_window = false;
xcb_destroy_window(conn, *(params->indicator));
*(params->indicator) = 0;
goto create_indicator;
}
switch (drop_type) {
case DT_PARENT:
while (target->parent->type != CT_WORKSPACE && con_on_side_of_parent(target->parent, direction)) {
target = target->parent;
}
rect = adjust_rect(target->parent->rect, direction, parent_indicator_size);
break;
case DT_CENTER:
rect = target->rect;
rect.x += sibling_indicator_size;
rect.y += sibling_indicator_size;
rect.width -= sibling_indicator_size * 2;
rect.height -= sibling_indicator_size * 2;
break;
case DT_SIBLING:
rect = adjust_rect(target->rect, direction, sibling_indicator_size);
break;
}
create_indicator:
if (draw_window) {
if (*(params->indicator) == 0) {
*(params->indicator) = create_drop_indicator(rect);
} else {
const uint32_t values[4] = {rect.x, rect.y, rect.width, rect.height};
const uint32_t mask = XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT;
xcb_configure_window(conn, *(params->indicator), mask, values);
}
}
x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
xcb_flush(conn);
*(params->target) = target;
*(params->direction) = direction;
*(params->drop_type) = drop_type;
}
/*
* Returns a new drop indicator window with the given initial coordinates.
*
*/
static xcb_window_t create_drop_indicator(Rect rect) {
uint32_t mask = 0;
uint32_t values[2];
mask = XCB_CW_BACK_PIXEL;
values[0] = config.client.focused.indicator.colorpixel;
mask |= XCB_CW_OVERRIDE_REDIRECT;
values[1] = 1;
xcb_window_t indicator = create_window(conn, rect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_MOVE, false, mask, values);
/* Change the window class to "i3-drag", so that it can be matched in a
* compositor configuration. Note that the class needs to be changed before
* mapping the window. */
xcb_change_property(conn,
XCB_PROP_MODE_REPLACE,
indicator,
XCB_ATOM_WM_CLASS,
XCB_ATOM_STRING,
8,
(strlen("i3-drag") + 1) * 2,
"i3-drag\0i3-drag\0");
xcb_map_window(conn, indicator);
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, indicator);
return indicator;
}
/*
* Initiates a mouse drag operation on a tiled window.
*
*/
void tiling_drag(Con *con, xcb_button_press_event_t *event) {
DLOG("Start dragging tiled container: con = %p\n", con);
bool set_focus = (con == focused);
bool set_fs = con->fullscreen_mode != CF_NONE;
/* Don't change focus while dragging. */
x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
xcb_flush(conn);
/* Indicate drop location while dragging. This blocks until the drag is completed. */
Con *target = NULL;
direction_t direction;
drop_type_t drop_type;
xcb_window_t indicator = 0;
const struct callback_params params = {&indicator, &target, &direction, &drop_type};
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP, XCURSOR_CURSOR_MOVE, drag_callback, &params);
/* Dragging is done. We don't need the indicator window any more. */
xcb_destroy_window(conn, indicator);
if (drag_result == DRAG_REVERT ||
target == NULL ||
(target == con && drop_type != DT_PARENT) ||
!con_exists(target)) {
DLOG("drop aborted\n");
return;
}
const orientation_t orientation = orientation_from_direction(direction);
const position_t position = position_from_direction(direction);
const layout_t layout = orientation == VERT ? L_SPLITV : L_SPLITH;
con_disable_fullscreen(con);
switch (drop_type) {
case DT_CENTER:
/* Also handles workspaces.*/
DLOG("drop to center of %p\n", target);
con_move_to_target(con, target);
break;
case DT_SIBLING:
DLOG("drop %s %p\n", position_to_string(position), target);
if (con_orientation(target->parent) != orientation) {
/* If con and target are the only children of the same parent, we can just change
* the parent's layout manually and then move con to the correct position.
* tree_split checks for a parent with only one child so it would create a new
* parent with the new layout. */
if (con->parent == target->parent && con_num_children(target->parent) == 2) {
target->parent->layout = layout;
} else {
tree_split(target, orientation);
}
}
insert_con_into(con, target, position);
ipc_send_window_event("move", con);
break;
case DT_PARENT: {
const bool parent_tabbed_or_stacked = (target->parent->layout == L_TABBED || target->parent->layout == L_STACKED);
DLOG("drop %s (%s) of %s%p\n",
direction_to_string(direction),
position_to_string(position),
parent_tabbed_or_stacked ? "tabbed/stacked " : "",
target);
if (parent_tabbed_or_stacked) {
/* When dealing with tabbed/stacked the target can be in the
* middle of the container. Thus, after a directional move, con
* will still be bound to the tabbed/stacked parent. */
if (position == BEFORE) {
target = TAILQ_FIRST(&(target->parent->nodes_head));
} else {
target = TAILQ_LAST(&(target->parent->nodes_head), nodes_head);
}
}
if (con != target) {
insert_con_into(con, target, position);
}
/* tree_move can change the focus */
Con *old_focus = focused;
tree_move(con, direction);
if (focused != old_focus) {
con_activate(old_focus);
}
break;
}
}
/* Warning: target might not exist anymore */
target = NULL;
/* Manage fullscreen status. */
if (set_focus || set_fs) {
Con *fs = con_get_fullscreen_covering_ws(con_get_workspace(con));
if (fs == con) {
ELOG("dragged container somehow got fullscreen again.\n");
assert(false);
} else if (fs && set_focus && set_fs) {
/* con was focused & fullscreen, disable other fullscreen container. */
con_disable_fullscreen(fs);
} else if (fs) {
/* con was not focused, prefer other fullscreen container. */
set_fs = set_focus = false;
} else if (!set_focus) {
/* con was not focused. If it was fullscreen and we are moving it to the focused
* workspace we must focus it. */
set_focus = (set_fs && con_get_workspace(focused) == con_get_workspace(con));
}
}
if (set_fs) {
con_enable_fullscreen(con, CF_OUTPUT);
}
if (set_focus) {
workspace_show(con_get_workspace(con));
con_focus(con);
}
tree_render();
}

View File

@ -476,3 +476,35 @@ direction_t direction_from_orientation_position(orientation_t orientation, posit
return position == BEFORE ? D_UP : D_DOWN;
}
}
/*
* Converts direction to a string representation.
*
*/
const char *direction_to_string(direction_t direction) {
switch (direction) {
case D_LEFT:
return "left";
case D_RIGHT:
return "right";
case D_UP:
return "up";
case D_DOWN:
return "down";
}
return "invalid";
}
/*
* Converts position to a string representation.
*
*/
const char *position_to_string(position_t position) {
switch (position) {
case BEFORE:
return "before";
case AFTER:
return "after";
}
return "invalid";
}

View File

@ -415,12 +415,10 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
* it is still in use by popular widget toolkits such as GTK+ and Java AWT.
*
*/
void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
/* This implementation simply mirrors Gnome's Metacity. Official
* documentation of this hint is nowhere to be found.
* For more information see:
* https://people.gnome.org/~tthurman/docs/metacity/xprops_8h-source.html
* https://stackoverflow.com/questions/13787553/detect-if-a-x11-window-has-decorations
bool window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
/* See `man VendorShell' for more info, `XmNmwmDecorations' section:
* https://linux.die.net/man/3/vendorshell
* The following constants are adapted from <Xm/MwmUtil.h>.
*/
#define MWM_HINTS_FLAGS_FIELD 0
#define MWM_HINTS_DECORATIONS_FIELD 2
@ -435,7 +433,7 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
FREE(prop);
return;
return false;
}
/* The property consists of an array of 5 uint32_t's. The first value is a
@ -461,6 +459,7 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
}
FREE(prop);
return true;
#undef MWM_HINTS_FLAGS_FIELD
#undef MWM_HINTS_DECORATIONS_FIELD

View File

@ -299,16 +299,16 @@ Con *create_workspace_on_output(Output *output, Con *content) {
/*
* Returns true if the workspace is currently visible. Especially important for
* multi-monitor environments, as they can have multiple currenlty active
* multi-monitor environments, as they can have multiple currently active
* workspaces.
*
*/
bool workspace_is_visible(Con *ws) {
Con *output = con_get_output(ws);
if (output == NULL)
if (output == NULL) {
return false;
}
Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
return (fs == ws);
}

38
src/x.c
View File

@ -490,14 +490,20 @@ void x_draw_decoration(Con *con) {
struct deco_render_params *p = scalloc(1, sizeof(struct deco_render_params));
/* find out which colors to use */
if (con->urgent)
if (con->urgent) {
p->color = &config.client.urgent;
else if (con == focused || con_inside_focused(con))
} else if (con == focused || con_inside_focused(con)) {
p->color = &config.client.focused;
else if (con == TAILQ_FIRST(&(parent->focus_head)))
p->color = &config.client.focused_inactive;
else
} else if (con == TAILQ_FIRST(&(parent->focus_head))) {
if (config.client.got_focused_tab_title && !leaf && con_descend_focused(con) == focused) {
/* Stacked/tabbed parent of focused container */
p->color = &config.client.focused_tab_title;
} else {
p->color = &config.client.focused_inactive;
}
} else {
p->color = &config.client.unfocused;
}
p->border_style = con_border_style(con);
@ -875,7 +881,6 @@ void x_push_node(Con *con) {
con_state *state;
Rect rect = con->rect;
//DLOG("Pushing changes for node %p / %s\n", con, con->name);
state = state_for_frame(con->frame.id);
if (state->name != NULL) {
@ -988,14 +993,16 @@ void x_push_node(Con *con) {
win_depth = con->window->depth;
/* Ensure we have valid dimensions for our surface. */
// TODO This is probably a bug in the condition above as we should never enter this path
// for height == 0. Also, we should probably handle width == 0 the same way.
/* TODO: This is probably a bug in the condition above as we should
* never enter this path for height == 0. Also, we should probably
* handle width == 0 the same way. */
int width = MAX((int32_t)rect.width, 1);
int height = MAX((int32_t)rect.height, 1);
xcb_create_pixmap(conn, win_depth, con->frame_buffer.id, con->frame.id, width, height);
draw_util_surface_init(conn, &(con->frame_buffer), con->frame_buffer.id,
get_visualtype_by_id(get_visualid_by_depth(win_depth)), width, height);
draw_util_clear_surface(&(con->frame_buffer), (color_t){.red = 0.0, .green = 0.0, .blue = 0.0});
/* For the graphics context, we disable GraphicsExposure events.
* Those will be sent when a CopyArea request cannot be fulfilled
@ -1008,8 +1015,8 @@ void x_push_node(Con *con) {
con->pixmap_recreated = true;
/* Dont render the decoration for windows inside a stack which are
* not visible right now */
// TODO Should this work the same way for L_TABBED?
* not visible right now
* TODO: Should this work the same way for L_TABBED? */
if (!con->parent ||
con->parent->layout != L_STACKED ||
TAILQ_FIRST(&(con->parent->focus_head)) == con)
@ -1120,7 +1127,6 @@ static void x_push_node_unmaps(Con *con) {
Con *current;
con_state *state;
//DLOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name);
state = state_for_frame(con->frame.id);
/* map/unmap if map state changed, also ensure that the child window
@ -1196,7 +1202,6 @@ void x_push_changes(Con *con) {
}
DLOG("-- PUSHING WINDOW STACK --\n");
//DLOG("Disabling EnterNotify\n");
/* We need to keep SubstructureRedirect around, otherwise clients can send
* ConfigureWindow requests and get them applied directly instead of having
* them become ConfigureRequests that i3 handles. */
@ -1205,7 +1210,6 @@ void x_push_changes(Con *con) {
if (state->mapped)
xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
}
//DLOG("Done, EnterNotify disabled\n");
bool order_changed = false;
bool stacking_changed = false;
@ -1235,14 +1239,12 @@ void x_push_changes(Con *con) {
if (con_has_managed_window(state->con))
memcpy(walk++, &(state->con->window->id), sizeof(xcb_window_t));
//DLOG("stack: 0x%08x\n", state->id);
con_state *prev = CIRCLEQ_PREV(state, state);
con_state *old_prev = CIRCLEQ_PREV(state, old_state);
if (prev != old_prev)
order_changed = true;
if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) {
stacking_changed = true;
//DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id);
uint32_t mask = 0;
mask |= XCB_CONFIG_WINDOW_SIBLING;
mask |= XCB_CONFIG_WINDOW_STACK_MODE;
@ -1295,13 +1297,11 @@ void x_push_changes(Con *con) {
warp_to = NULL;
}
//DLOG("Re-enabling EnterNotify\n");
values[0] = FRAME_EVENT_MASK;
CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) {
if (state->mapped)
xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
}
//DLOG("Done, EnterNotify re-enabled\n");
x_deco_recurse(con);
@ -1387,9 +1387,6 @@ void x_push_changes(Con *con) {
CIRCLEQ_REMOVE(&old_state_head, state, old_state);
CIRCLEQ_INSERT_TAIL(&old_state_head, state, old_state);
}
//CIRCLEQ_FOREACH(state, &old_state_head, old_state) {
// DLOG("old stack: 0x%08x\n", state->id);
//}
xcb_flush(conn);
}
@ -1402,7 +1399,6 @@ void x_push_changes(Con *con) {
void x_raise_con(Con *con) {
con_state *state;
state = state_for_frame(con->frame.id);
//DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id);
CIRCLEQ_REMOVE(&state_head, state, state);
CIRCLEQ_INSERT_HEAD(&state_head, state, state);

View File

@ -433,7 +433,7 @@ int main(int argc, char *argv[]) {
bound = true;
/* Let the user know bind() was successful, so that they know the
* error messages can be disregarded. */
fprintf(stderr, "Successfuly bound to %s\n", addr.sun_path);
fprintf(stderr, "Successfully bound to %s\n", addr.sun_path);
sun_path = sstrdup(addr.sun_path);
break;
}

View File

@ -42,6 +42,7 @@ our @EXPORT = qw(
exit_forcefully
workspace_exists
focused_ws
focused_output
get_socket_path
launch_with_config
get_i3_log
@ -664,6 +665,25 @@ sub workspace_exists {
(scalar grep { $_ eq $name } @{get_workspace_names()}) > 0;
}
=head2 focused_output
Returns the name of the currently focused output.
is(focused_output, 'fake-0', 'i3 starts on output 0');
=cut
sub _focused_output {
my $i3 = i3(get_socket_path());
my $tree = $i3->get_tree->recv;
my $focused = $tree->{focus}->[0];
my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
return $output;
}
sub focused_output {
return _focused_output->{name}
}
=head2 focused_ws
Returns the name of the currently focused workspace.
@ -672,11 +692,9 @@ Returns the name of the currently focused workspace.
is($ws, '1', 'i3 starts on workspace 1');
=cut
sub focused_ws {
my $i3 = i3(get_socket_path());
my $tree = $i3->get_tree->recv;
my $focused = $tree->{focus}->[0];
my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
my $output = _focused_output;
my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
return $first->{name}
@ -780,6 +798,8 @@ sub exit_gracefully {
my ($pid, $socketpath) = @_;
$socketpath ||= get_socket_path();
$SIG{CHLD} = undef;
my $exited = 0;
eval {
say "Exiting i3 cleanly...";
@ -818,6 +838,8 @@ sub exit_forcefully {
my ($pid, $signal) = @_;
$signal ||= 'TERM';
$SIG{CHLD} = undef;
# Send the given signal to the i3 instance and wait for up to 10s
# for it to terminate.
kill($signal, $pid)
@ -941,6 +963,18 @@ sub launch_with_config {
return ${^CHILD_ERROR_NATIVE};
}
$SIG{CHLD} = sub {
# don't change $! and $? outside handler
local ($!, $?);
my $child = waitpid -1, POSIX::WNOHANG;
warn "SIGCHLD, waitpid() = $child";
if ($child == $i3_pid) {
warn "i3 died, exiting!";
exit 1;
}
};
# force update of the cached socket path in lib/i3test
# as soon as i3 has started
$cv->cb(sub { get_socket_path(0) });
@ -972,6 +1006,8 @@ sub kill_all_windows {
# Sync in case not all windows are managed by i3 just yet.
sync_with_i3;
cmd '[title=".*"] kill';
# Sync to make sure x_window_kill() calls have taken effect.
sync_with_i3;
}
=head2 events_for($subscribecb, [ $rettype ], [ $eventcbs ])

View File

@ -14,7 +14,7 @@
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Tests whether we can switch to a non-existant workspace
# Tests whether we can switch to a non-existent workspace
# (necessary for further tests)
#
use List::Util qw(first);

View File

@ -267,7 +267,7 @@ like($output->[2], qr|^bindsym Mod1\+f resize shrink right 20 px$|, 'resize righ
like($output->[3], qr|^bindsym Mod1\+f resize grow down 23 px$|, 'resize bottom changed');
#####################################################################
# also resizing, but with indention this time
# also resizing, but with indentation this time
#####################################################################
like($output->[4], qr|^bindsym Mod1\+f resize grow left 10 px$|, 'resize left changed');

View File

@ -476,16 +476,18 @@ is(parser_calls($config),
################################################################################
$config = <<'EOT';
client.focused #4c7899 #285577 #ffffff #2e9ef4 #b34d4c
client.focused_inactive #333333 #5f676a #ffffff #484e50
client.unfocused #333333 #222222 #888888 #292d2e
client.urgent #2f343a #900000 #ffffff #900000 #c00000
client.placeholder #000000 #0c0c0c #ffffff #000000
client.focused #4c7899 #285577 #ffffff #2e9ef4 #b34d4c
client.focused_inactive #333333 #5f676a #ffffff #484e50
client.focused_tab_title #444444 #555555 #ffffff
client.unfocused #333333 #222222 #888888 #292d2e
client.urgent #2f343a #900000 #ffffff #900000 #c00000
client.placeholder #000000 #0c0c0c #ffffff #000000
EOT
$expected = <<'EOT';
cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4, #b34d4c)
cfg_color(client.focused_inactive, #333333, #5f676a, #ffffff, #484e50, NULL)
cfg_color(client.focused_tab_title, #444444, #555555, #ffffff, NULL, NULL)
cfg_color(client.unfocused, #333333, #222222, #888888, #292d2e, NULL)
cfg_color(client.urgent, #2f343a, #900000, #ffffff, #900000, #c00000)
cfg_color(client.placeholder, #000000, #0c0c0c, #ffffff, #000000, NULL)
@ -551,6 +553,7 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
exec
client.background
client.focused_inactive
client.focused_tab_title
client.focused
client.unfocused
client.urgent

View File

@ -71,7 +71,7 @@ cmd 'focus left';
cmd 'mark left';
#
# get_marks replys an array of marks, whose order is undefined,
# get_marks replies an array of marks, whose order is undefined,
# so we use sort to be able to compare the output
#

View File

@ -18,7 +18,7 @@ use i3test i3_config => <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# fake-1 under fake-0 to not interfere with left/right wraping
# fake-1 under fake-0 to not interfere with left/right wrapping
fake-outputs 1024x768+0+0,1024x768+0+1024
workspace X output fake-1
EOT
@ -56,7 +56,11 @@ sub kill_subtest {
my $focus = AnyEvent->condvar;
my @events = events_for(
sub { cmd $cmd },
sub {
cmd $cmd;
# Sync to make sure x_window_kill() calls have taken effect.
sync_with_i3;
},
'window');
is(scalar @events, 1, 'Received 1 event');

View File

@ -62,7 +62,7 @@ $config = <<'EOT';
font \
-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# Use line contiuation with too many lines (>4096 characters).
# Use line continuation with too many lines (>4096 characters).
# This config is invalid. Use it to ensure no buffer overflow.
bindsym Mod1+b \
0001-This is a very very very very very very very very very very very very very very very very very long cmd \

View File

@ -21,7 +21,9 @@
#
# Ticket: #2318
# Bug still in: 4.12-46-g2123888
use i3test;
use i3test i3_autostart => 0;
my $pid = launch_with_config('-default');
# We cannot use events_for in this test as we cannot send events after
# issuing the restart/shutdown command.
@ -59,7 +61,7 @@ $i3->subscribe({
}
})->recv;
cmd 'exit';
exit_gracefully($pid);
$e = $cv->recv;

View File

@ -14,7 +14,7 @@
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Verify that the corrent focus stack order is preserved after various
# Verify that the current focus stack order is preserved after various
# operations.
use i3test i3_config => <<EOT;
# i3 config file (v4)

View File

@ -43,9 +43,9 @@ void init_ctx(void *connptr) {
* | C |
* +-------+
*
* - Zone A is completly opaque.
* - Zone A is completely opaque.
* - Zone B is clickable through (input shape).
* - Zone C is completly transparent (bounding shape).
* - Zone C is completely transparent (bounding shape).
*/
void set_shape(long window_id) {
xcb_rectangle_t bounding_rectangle = { 0, 0, 100, 50 };

View File

@ -22,7 +22,7 @@ use i3test i3_config => <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# fake-1 under fake-0 to not interfere with left/right wraping
# fake-1 under fake-0 to not interfere with left/right wrapping
fake-outputs 1024x768+0+0,1024x768+0+1024
workspace X output fake-1
EOT

30
testcases/t/314-window-icon-padding.t Normal file → Executable file
View File

@ -38,6 +38,36 @@ is(window_icon_padding($tmp), -1, 'window_icon_padding defaults to -1');
cmd 'title_window_icon on';
isnt(window_icon_padding($tmp), -1, 'window_icon_padding no longer -1');
cmd 'title_window_icon toggle';
is(window_icon_padding($tmp), -1, 'window_icon_padding back to -1');
cmd 'title_window_icon toggle';
isnt(window_icon_padding($tmp), -1, 'window_icon_padding no longer -1 again');
cmd 'title_window_icon off';
is(window_icon_padding($tmp), -1, 'window_icon_padding back to -1');
cmd 'title_window_icon padding 3px';
is(window_icon_padding($tmp), 3, 'window_icon_padding set to 3');
cmd 'title_window_icon toggle';
ok(window_icon_padding($tmp) < 0, 'window_icon_padding toggled off');
cmd 'title_window_icon toggle';
is(window_icon_padding($tmp), 3, 'window_icon_padding toggled back to 3');
cmd 'title_window_icon toggle 5px';
ok(window_icon_padding($tmp) < 0, 'window_icon_padding toggled off');
cmd 'title_window_icon toggle 5px';
is(window_icon_padding($tmp), 5, 'window_icon_padding toggled on to 5px');
cmd 'title_window_icon toggle 5px';
ok(window_icon_padding($tmp) < 0, 'window_icon_padding toggled off');
cmd 'title_window_icon toggle 4px';
is(window_icon_padding($tmp), 4, 'window_icon_padding toggled on to 4px');
exit_gracefully($pid);
################################################################################

View File

@ -0,0 +1,46 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • https://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • https://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • https://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Test that commands with more than 10 non-identified words doesn't works
# 10 is the magic number chosen for the stack size which is why it's used here
# Ticket: #2968
# Bug still in: 4.19.2-103-gfc65ca36
use i3test;
######################################################################
# 1) run a long command
######################################################################
my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
my $floatwin = open_floating_window;
my ($absolute_before, $top_before) = $floatwin->rect;
cmd 'move window container to window container to window container to left';
sync_with_i3;
my ($absolute, $top) = $floatwin->rect;
is($absolute->x, ($absolute_before->x - 10), 'moved 10 px to the left');
is($absolute->y, $absolute_before->y, 'y not changed');
is($absolute->width, $absolute_before->width, 'width not changed');
is($absolute->height, $absolute_before->height, 'height not changed');
done_testing;

View File

@ -0,0 +1,321 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • https://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • https://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • https://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Test dragging containers.
my ($width, $height) = (1000, 500);
my $config = <<"EOT";
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
focus_follows_mouse no
floating_modifier Mod1
# 2 side by side outputs
fake-outputs ${width}x${height}+0+0P,${width}x${height}+${width}+0
bar {
output primary
}
EOT
use i3test i3_autostart => 0;
use i3test::XTEST;
my $pid = launch_with_config($config);
sub start_drag {
my ($pos_x, $pos_y) = @_;
die "Drag outside of bounds!" unless $pos_x < $width * 2 && $pos_y < $height;
$x->root->warp_pointer($pos_x, $pos_y);
sync_with_i3;
xtest_key_press(64); # Alt_L
xtest_button_press(1, $pos_x, $pos_y);
xtest_sync_with_i3;
}
sub end_drag {
my ($pos_x, $pos_y) = @_;
die "Drag outside of bounds!" unless $pos_x < $width * 2 && $pos_y < $height;
$x->root->warp_pointer($pos_x, $pos_y);
sync_with_i3;
xtest_button_release(1, $pos_x, $pos_y);
xtest_key_release(64); # Alt_L
xtest_sync_with_i3;
}
my ($ws1, $ws2);
my ($A, $B, $tmp);
my ($A_id, $B_id);
sub move_subtest {
my ($cb, $win) = @_;
my @events = events_for($cb, 'window');
my @move = grep { $_->{change} eq 'move' } @events;
is(scalar @move, 1, 'Received 1 window::move event');
is($move[0]->{container}->{window}, $A->{id}, "window id matches");
}
###############################################################################
# Drag floating container onto an empty workspace.
###############################################################################
$ws2 = fresh_workspace(output => 1);
$ws1 = fresh_workspace(output => 0);
$A = open_floating_window(rect => [ 30, 30, 50, 50 ]);
start_drag(40, 40);
end_drag(1050, 50);
is($x->input_focus, $A->id, 'Floating window moved to the right workspace');
is($ws2, focused_ws, 'Empty workspace focused after floating window dragged to it');
###############################################################################
# Drag tiling container onto an empty workspace.
###############################################################################
subtest "Draging tiling container onto an empty workspace produces move event", \&move_subtest,
sub {
$ws2 = fresh_workspace(output => 1);
$ws1 = fresh_workspace(output => 0);
$A = open_window;
start_drag(50, 50);
end_drag(1050, 50);
is($x->input_focus, $A->id, 'Tiling window moved to the right workspace');
is($ws2, focused_ws, 'Empty workspace focused after tiling window dragged to it');
};
###############################################################################
# Drag tiling container onto a container that closes before the drag is
# complete.
###############################################################################
$ws1 = fresh_workspace(output => 0);
$A = open_window;
open_window;
start_drag(600, 300); # Start dragging the second window.
# Try to place it on the first window.
$x->root->warp_pointer(50, 50);
sync_with_i3;
cmd '[id=' . $A->id . '] kill';
sync_with_i3;
end_drag(50, 50);
is(@{get_ws_content($ws1)}, 1, 'One container left in ws1');
###############################################################################
# Drag tiling container onto a tiling container on an other workspace.
###############################################################################
subtest "Draging tiling container onto a tiling container on an other workspace produces move event", \&move_subtest,
sub {
$ws2 = fresh_workspace(output => 1);
open_window;
$B_id = get_focused($ws2);
$ws1 = fresh_workspace(output => 0);
$A = open_window;
$A_id = get_focused($ws1);
start_drag(50, 50);
end_drag(1500, 250); # Center of right output, inner region.
is($ws2, focused_ws, 'Workspace focused after tiling window dragged to it');
$ws2 = get_ws($ws2);
is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
is($ws2->{focus}[1], $B_id, 'B focused second');
};
###############################################################################
# Drag tiling container onto a floating container on an other workspace.
###############################################################################
subtest "Draging tiling container onto a floating container on an other workspace produces move event", \&move_subtest,
sub {
$ws2 = fresh_workspace(output => 1);
open_floating_window;
$B_id = get_focused($ws2);
$ws1 = fresh_workspace(output => 0);
$A = open_window;
$A_id = get_focused($ws1);
start_drag(50, 50);
end_drag(1500, 250);
is($ws2, focused_ws, 'Workspace with one floating container focused after tiling window dragged to it');
$ws2 = get_ws($ws2);
is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
is($ws2->{floating_nodes}[0]->{nodes}[0]->{id}, $B_id, 'B exists & floating');
};
###############################################################################
# Drag tiling container onto a bar.
###############################################################################
subtest "Draging tiling container onto a bar produces move event", \&move_subtest,
sub {
$ws1 = fresh_workspace(output => 0);
open_window;
$B_id = get_focused($ws1);
$ws2 = fresh_workspace(output => 1);
$A = open_window;
$A_id = get_focused($ws2);
start_drag(1500, 250);
end_drag(1, 498); # Bar on bottom of left output.
is($ws1, focused_ws, 'Workspace focused after tiling window dragged to its bar');
$ws1 = get_ws($ws1);
is($ws1->{focus}[0], $A_id, 'B focused first, dragged container kept focus');
is($ws1->{focus}[1], $B_id, 'A focused second');
};
###############################################################################
# Drag an unfocused tiling container onto it's self.
###############################################################################
$ws1 = fresh_workspace(output => 0);
open_window;
$A_id = get_focused($ws1);
open_window;
$B_id = get_focused($ws1);
start_drag(50, 50);
end_drag(450, 450);
$ws1 = get_ws($ws1);
is($ws1->{focus}[0], $B_id, 'B focused first, kept focus');
is($ws1->{focus}[1], $A_id, 'A focused second, unfocused dragged container didn\'t gain focus');
###############################################################################
# Drag an unfocused tiling container onto an occupied workspace.
###############################################################################
subtest "Draging unfocused tiling container onto an occupied workspace produces move event", \&move_subtest,
sub {
$ws1 = fresh_workspace(output => 0);
$A = open_window;
$A_id = get_focused($ws1);
$ws2 = fresh_workspace(output => 1);
open_window;
$B_id = get_focused($ws2);
start_drag(50, 50);
end_drag(1500, 250); # Center of right output, inner region.
is($ws2, focused_ws, 'Workspace remained focused after dragging unfocused container');
$ws2 = get_ws($ws2);
is($ws2->{focus}[0], $B_id, 'B focused first, kept focus');
is($ws2->{focus}[1], $A_id, 'A focused second, unfocused container didn\'t steal focus');
};
###############################################################################
# Drag fullscreen container onto window in same workspace.
###############################################################################
$ws1 = fresh_workspace(output => 0);
open_window;
$A = open_window;
cmd 'fullscreen enable';
start_drag(900, 100); # Second window
end_drag(50, 50); # To first window
is($ws1, focused_ws, 'Workspace remained focused after dragging fullscreen container');
is_num_fullscreen($ws1, 1, 'Container still fullscreened');
is($x->input_focus, $A->id, 'Fullscreen container still focused');
###############################################################################
# Drag unfocused fullscreen container onto window in other workspace.
###############################################################################
subtest "Draging unfocused fullscreen container onto window in other workspace produces move event", \&move_subtest,
sub {
$ws1 = fresh_workspace(output => 0);
$A = open_window;
cmd 'fullscreen enable';
$ws2 = fresh_workspace(output => 1);
open_window;
open_window;
start_drag(900, 100);
end_drag(1000 + 500 * 0.15 + 10, 200); # left of leftmost window
is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it');
is_num_fullscreen($ws1, 0, 'No fullscreen container in first workspace');
is_num_fullscreen($ws2, 1, 'Moved container still fullscreened');
is($x->input_focus, $A->id, 'Fullscreen container now focused');
$ws2 = get_ws($ws2);
is($ws2->{nodes}->[0]->{window}, $A->id, 'Fullscreen container now leftmost window in second workspace');
};
###############################################################################
# Drag unfocused fullscreen container onto left outter region of window in
# other workspace. The container shouldn't end up in $ws2 because it was
# dragged onto the outter region of the leftmost window. We must also check
# that the focus remains on the other window.
###############################################################################
subtest "Draging unfocused fullscreen container onto left outter region of window in other workspace produces move event", \&move_subtest,
sub {
$ws1 = fresh_workspace(output => 0);
open_window for (1..3);
$A = open_window;
$tmp = get_focused($ws1);
cmd 'fullscreen enable';
$ws2 = fresh_workspace(output => 1);
$B = open_window;
start_drag(990, 100); # rightmost of $ws1
end_drag(1004, 100); # outter region of window of $ws2
is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it');
is_num_fullscreen($ws1, 1, 'Fullscreen container still in first workspace');
is_num_fullscreen($ws2, 0, 'No fullscreen container in second workspace');
is($x->input_focus, $B->id, 'Window of second workspace still has focus');
is(get_focused($ws1), $tmp, 'Fullscreen container still focused in first workspace');
$ws1 = get_ws($ws1);
is($ws1->{nodes}->[3]->{window}, $A->id, 'Fullscreen container still rightmost window in first workspace');
};
exit_gracefully($pid);
###############################################################################
done_testing;

Some files were not shown because too many files have changed in this diff Show More