diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index a9cfbd47..f2c55972 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,18 +1,68 @@
-Output of `i3 --moreversion 2>&- || i3 --version`:
+
-_REPLACE: i3 version output_
+## I'm submitting a…
+
+
+[ ] Bug
+[ ] Feature Request
+[ ] Documentation Request
+[ ] Other (Please describe in detail)
+
-URL to a logfile as per https://i3wm.org/docs/debugging.html:
+## Current Behavior
+
-_REPLACE: URL to logfile_
+## Expected Behavior
+
-**What I did:**
+## Reproduction Instructions
+
-**What I saw:**
+## Environment
+
+Output of `i3 --moreversion 2>&-`:
+
+i3 version:
+
-_REPLACE: e.g. "i3 changed focus to the window ABOVE the current window"_
+
+
+
+
+
+
+Logfile URL:
+
+
+
+
+- Linux Distribution & Version:
+- Are you using a compositor (e.g., xcompmgr or compton):
+
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..910b0fca
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,71 @@
+---
+name: Bug report
+about: Create a report to help us improve
+---
+
+
+
+## I'm submitting a…
+
+
+[x] Bug
+[ ] Feature Request
+[ ] Documentation Request
+[ ] Other (Please describe in detail)
+
+
+## Current Behavior
+
+
+## Expected Behavior
+
+
+## Reproduction Instructions
+
+
+## Environment
+
+Output of `i3 --moreversion 2>&-`:
+
+i3 version:
+
+
+
+
+
+
+
+
+Logfile URL:
+
+
+
+
+- Linux Distribution & Version:
+- Are you using a compositor (e.g., xcompmgr or compton):
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..e1c169a5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,47 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+---
+
+
+
+## I'm submitting a…
+
+
+[ ] Bug
+[x] Feature Request
+[ ] Documentation Request
+[ ] Other (Please describe in detail)
+
+
+## Current Behavior
+
+
+## Desired Behavior
+
+
+## Environment
+
+Output of `i3 --moreversion 2>&-`:
+
+i3 version:
+
+
+
+
+- Linux Distribution & Version:
+- Are you using a compositor (e.g., xcompmgr or compton):
+
diff --git a/.travis.yml b/.travis.yml
index 87c996fb..8098035c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -35,7 +35,7 @@ install:
script:
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-safe-wrappers.sh
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-formatting.sh
- - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'autoreconf -fi && mkdir -p build && cd build && (../configure || (cat config.log; false)) && make -j CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror"'
+ - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'autoreconf -fi && mkdir -p build && cd build && (../configure || (cat config.log; false)) && make -j CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror"'
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-spelling.pl
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} ./travis/run-tests.sh
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/debian-build.sh deb/debian-amd64/DIST
diff --git a/AnyEvent-I3/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm
index 198c41c9..ae9e5bea 100644
--- a/AnyEvent-I3/lib/AnyEvent/I3.pm
+++ b/AnyEvent-I3/lib/AnyEvent/I3.pm
@@ -100,11 +100,12 @@ use constant TYPE_GET_VERSION => 7;
use constant TYPE_GET_BINDING_MODES => 8;
use constant TYPE_GET_CONFIG => 9;
use constant TYPE_SEND_TICK => 10;
+use constant TYPE_SYNC => 11;
our %EXPORT_TAGS = ( 'all' => [
qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION
- TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK)
+ TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK TYPE_SYNC)
] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
@@ -534,6 +535,19 @@ sub send_tick {
$self->message(TYPE_SEND_TICK, $payload);
}
+=head2 sync
+
+Sends an i3 sync event. Requires i3 >= 4.16
+
+=cut
+sub sync {
+ my ($self, $payload) = @_;
+
+ $self->_ensure_connection;
+
+ $self->message(TYPE_SYNC, $payload);
+}
+
=head2 command($content)
Makes i3 execute the given command
diff --git a/DEPENDS b/DEPENDS
index 1e26afa2..9a92f99e 100644
--- a/DEPENDS
+++ b/DEPENDS
@@ -4,29 +4,29 @@
"min" means minimum required version
"lkgv" means last known good version
-┌──────────────┬────────┬────────┬───────────────────────────────────────────────────────────┐
-│ dependency │ min. │ lkgv │ URL │
-├──────────────┼────────┼────────┼───────────────────────────────────────────────────────────┤
-│ pkg-config │ 0.25 │ 0.29 │ https://pkgconfig.freedesktop.org/ │
-│ libxcb │ 1.1.93 │ 1.12 │ https://xcb.freedesktop.org/dist/ │
-│ xcb-util │ 0.3.3 │ 0.4.1 │ https://xcb.freedesktop.org/dist/ │
-│ xkbcommon │ 0.4.0 │ 0.6.1 │ https://xkbcommon.org/ │
-│ xkbcommon-x11│ 0.4.0 │ 0.6.1 │ https://xkbcommon.org/ │
-│ util-cursor³⁴│ 0.0.99 │ 0.1.3 │ https://xcb.freedesktop.org/dist/ │
-│ util-wm⁴ │ 0.3.8 │ 0.3.8 │ https://xcb.freedesktop.org/dist/ │
-│ util-keysyms⁴│ 0.3.8 │ 0.4.0 │ https://xcb.freedesktop.org/dist/ │
-│ util-xrm⁴ │ 1.0.0 │ 1.0.0 │ https://github.com/Airblader/xcb-util-xrm │
-│ libev │ 4.0 │ 4.19 │ http://libev.schmorp.de/ │
-│ yajl │ 2.0.1 │ 2.1.0 │ https://lloyd.github.com/yajl/ │
-│ asciidoc │ 8.3.0 │ 8.6.9 │ http://www.methods.co.nz/asciidoc/ │
-│ xmlto │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/ │
-│ Pod::Simple² │ 3.22 │ 3.22 │ http://search.cpan.org/~dwheeler/Pod-Simple-3.23/ │
-│ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │
-│ PCRE │ 8.12 │ 8.38 │ https://www.pcre.org/ │
-│ libsn¹ │ 0.10 │ 0.12 │ https://freedesktop.org/wiki/Software/startup-notification │
-│ pango │ 1.30.0 | 1.40.1 │ http://www.pango.org/ │
-│ cairo │ 1.14.4 │ 1.14.6 │ https://cairographics.org/ │
-└──────────────┴────────┴────────┴───────────────────────────────────────────────────────────┘
+┌──────────────┬────────┬────────┬─────────────────────────────────────────────────────────────┐
+│ dependency │ min. │ lkgv │ URL │
+├──────────────┼────────┼────────┼─────────────────────────────────────────────────────────────┤
+│ pkg-config │ 0.25 │ 0.29 │ https://pkgconfig.freedesktop.org/ │
+│ libxcb │ 1.1.93 │ 1.12 │ https://xcb.freedesktop.org/dist/ │
+│ xcb-util │ 0.3.3 │ 0.4.1 │ https://xcb.freedesktop.org/dist/ │
+│ xkbcommon │ 0.4.0 │ 0.6.1 │ https://xkbcommon.org/ │
+│ xkbcommon-x11│ 0.4.0 │ 0.6.1 │ https://xkbcommon.org/ │
+│ util-cursor³⁴│ 0.0.99 │ 0.1.3 │ https://xcb.freedesktop.org/dist/ │
+│ util-wm⁴ │ 0.3.8 │ 0.3.8 │ https://xcb.freedesktop.org/dist/ │
+│ util-keysyms⁴│ 0.3.8 │ 0.4.0 │ https://xcb.freedesktop.org/dist/ │
+│ util-xrm⁴ │ 1.0.0 │ 1.0.0 │ https://github.com/Airblader/xcb-util-xrm/ │
+│ libev │ 4.0 │ 4.19 │ http://libev.schmorp.de/ │
+│ yajl │ 2.0.1 │ 2.1.0 │ https://lloyd.github.com/yajl/ │
+│ asciidoc │ 8.3.0 │ 8.6.9 │ http://www.methods.co.nz/asciidoc/ │
+│ xmlto │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/ │
+│ Pod::Simple² │ 3.22 │ 3.22 │ http://search.cpan.org/dist/Pod-Simple/ │
+│ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │
+│ PCRE │ 8.12 │ 8.38 │ https://www.pcre.org/ │
+│ libsn¹ │ 0.10 │ 0.12 │ https://freedesktop.org/wiki/Software/startup-notification/ │
+│ pango │ 1.30.0 │ 1.40.1 │ http://www.pango.org/ │
+│ cairo │ 1.14.4 │ 1.14.6 │ https://cairographics.org/ │
+└──────────────┴────────┴────────┴─────────────────────────────────────────────────────────────┘
¹ libsn = libstartup-notification
² Pod::Simple is a Perl module required for converting the testsuite
documentation to HTML. See https://michael.stapelberg.de/cpan/#Pod::Simple
diff --git a/I3_VERSION b/I3_VERSION
index 0d5ece58..a39209b2 100644
--- a/I3_VERSION
+++ b/I3_VERSION
@@ -1 +1 @@
-4.15-non-git
+4.16-non-git
diff --git a/Makefile.am b/Makefile.am
index 184b0734..537fc6a0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -118,7 +118,7 @@ EXTRA_DIST = \
I3_VERSION \
LICENSE \
PACKAGE-MAINTAINER \
- RELEASE-NOTES-4.15 \
+ RELEASE-NOTES-4.16 \
generate-command-parser.pl \
parser-specs/commands.spec \
parser-specs/config.spec \
@@ -301,6 +301,7 @@ libi3_a_SOURCES = \
libi3/fake_configure_notify.c \
libi3/font.c \
libi3/format_placeholders.c \
+ libi3/g_utf8_make_valid.c \
libi3/get_colorpixel.c \
libi3/get_config_path.c \
libi3/get_exe_path.c \
@@ -357,10 +358,12 @@ i3_msg_i3_msg_SOURCES = \
i3_nagbar_i3_nagbar_CFLAGS = \
$(AM_CFLAGS) \
+ $(LIBSN_CFLAGS) \
$(libi3_CFLAGS)
i3_nagbar_i3_nagbar_LDADD = \
$(libi3_LIBS) \
+ $(LIBSN_LIBS) \
$(XCB_UTIL_CURSOR_LIBS)
i3_nagbar_i3_nagbar_SOURCES = \
@@ -414,10 +417,12 @@ i3bar_i3bar_SOURCES = \
i3_config_wizard_i3_config_wizard_CFLAGS = \
$(AM_CFLAGS) \
$(libi3_CFLAGS) \
+ $(LIBSN_CFLAGS) \
$(XKBCOMMON_CFLAGS)
i3_config_wizard_i3_config_wizard_LDADD = \
$(libi3_LIBS) \
+ $(LIBSN_LIBS) \
$(XCB_UTIL_KEYSYMS_LIBS) \
$(XKBCOMMON_LIBS)
@@ -521,6 +526,7 @@ i3_SOURCES = \
include/shmlog.h \
include/sighandler.h \
include/startup.h \
+ include/sync.h \
include/tree.h \
include/util.h \
include/window.h \
@@ -562,6 +568,7 @@ i3_SOURCES = \
src/sd-daemon.c \
src/sighandler.c \
src/startup.c \
+ src/sync.c \
src/tree.c \
src/util.c \
src/version.c \
diff --git a/README.md b/README.md
index 21b2e24b..6ac67541 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
=====================================================
[![Build Status](https://travis-ci.org/i3/i3.svg?branch=next)](https://travis-ci.org/i3/i3)
-[![Issue Stats](http://www.issuestats.com/github/i3/i3/badge/issue?style=flat)](http://www.issuestats.com/github/i3/i3)
-[![Pull Request Stats](http://www.issuestats.com/github/i3/i3/badge/pr?style=flat)](http://www.issuestats.com/github/i3/i3)
+[![Issue Stats](https://img.shields.io/github/issues/i3/i3.svg)](https://github.com/i3/i3/issues)
+[![Pull Request Stats](https://img.shields.io/github/issues-pr/i3/i3.svg)](https://github.com/i3/i3/pulls)
i3 is a tiling window manager for X11.
diff --git a/RELEASE-NOTES-4.15 b/RELEASE-NOTES-4.15
deleted file mode 100644
index 0e1f81e4..00000000
--- a/RELEASE-NOTES-4.15
+++ /dev/null
@@ -1,113 +0,0 @@
-
- ┌────────────────────────────┐
- │ Release notes for i3 v4.15 │
- └────────────────────────────┘
-
-This is i3 v4.15. This version is considered stable. All users of i3 are
-strongly encouraged to upgrade.
-
-Aside from a number of fixes and documentation improvements, a number of
-commands have been extended to be more complete (e.g. “assign”, “resize”).
-
- ┌────────────────────────────┐
- │ Changes in i3 v4.15 │
- └────────────────────────────┘
-
- • build: AnyEvent::I3 moved to the i3 repository, so that its main consumer,
- the i3 testsuite, can use new features immediately (such as the tick event,
- in this case).
- • docs/hacking-howto: promote “using git / sending patches” and “how to
- build?” sections
- • docs/i3bar-protocol: document that pango markup only works with pango fonts
- • docs/ipc: document focus, nodes, floating_nodes
- • docs/ipc: urgent: complete the list of container types
- • docs/ipc: document how to detect i3’s byte order in memory-safe languages
- • docs/ipc: document the GET_CONFIG request
- • docs/userguide: fix formatting issue
- • docs/userguide: explain why Mod4 is usually preferred as a modifier
- • docs/userguide: use more idiomatic english (full-size, so-called)
- • docs/userguide: switch from removed goto command to focus
- • docs/userguide: mention in focus
- • docs/userguide: remove outdated 2013 last-modified date
- • dump-asy: add prerequisite checks
- • dump-asy: fix warnings about empty container names
- • i3-dump-log: enable shmlog on demand
- • i3-sensible-terminal: add “kitty”, “guake”, “tilda”
- • i3-sensible-editor: add “gvim”
- • i3bar: add --release flag for bindsym in bar blocks
- • i3bar: add relative coordinates in JSON for click events
- • ipc: rename COMMAND to RUN_COMMAND for consistency
- • ipc: implement tick event for less flaky tests
- • ipc: add error reply to “focus ”
- • ipc: send success response for nop
- • default config: add $mod+r to toggle resize mode
- • default config: use variables for workspace names to avoid repetition
- • introduce “assign [→] [workspace] [number] ”
- • introduce “assign [→] output left|right|up|down|primary|”
- • introduce a “focus_wrapping” option (subsumes “force_focus_wrapping”)
- • introduce percentage point resizing for floating containers:
- “resize set [px | ppt] [px | ppt]”
- • introduce “resize set ppt ppt” for tiling windows
- • rename “new_window” and “new_float” to “default_border” and
- “default_floating_border” (the old names keep working)
- • output names (e.g. “DP2”) can now be used as synonyms for monitor names
- (e.g. “Dell UP2414Q”).
- • the “swap” command now works with fullscreen windows
- • raise floating windows to top when they are focused programmatically
- • _NET_ACTIVE_WINDOW: invalidate focus to force SetInputFocus call
- • make focus handling consistent when changing focus between outputs
- • round non-integer Xft.dpi values
- • tiling resize: remove minimum size
-
- ┌────────────────────────────┐
- │ Bugfixes │
- └────────────────────────────┘
-
- • i3bar: fix various memory leaks
- • i3bar: fix crash when no status_command is provided
- • fix uninitialized variables in init_dpi_end, tree_restore
- • fix incorrectly set up signal handling
- • fix “swap” debug log message
- • fix crash when specifying invalid con_id for “swap”
- • fix crash upon restart with window marks
- • fix crash when config file does not end in a newline
- • fix crash in append_layout
- • fix crash in layout toggle command
- • fix crash when switching monitors
- • fix use-after-free in randr_init error path
- • fix move accidentally moving windows across outputs
- • fix crash when floating window is tiled while being resized
- • fix out-of-bounds memory read
- • fix memory leak when config conversion fails
- • fix layout toggle split, which didn’t work until enabling tabbed/stack mode
- once
- • move XCB event handling into xcb_prepare_cb
- • avert endless loop on unexpected EOF in ipc messages
- • perform proper cleanup for signals with Term action
- • don’t match containers in the scratchpad with criteria
- • fix “workspace show” related issues
- • fix config file conversion with long variable names
- • fix config file conversion memory initialization
- • prevent access of freed workspace in _workspace_show
- • disable fullscreen when required when programmatically focusing windows
- • free last_motion_notify
- • don’t raise floating windows when focused because of focus_follows_mouse
- • correctly set EWMH atoms when closing a workspace
- • don’t raise floating windows when workspace is shown
- • keep focus order when encapsulating workspaces
- • validate layout files before loading
-
- ┌────────────────────────────┐
- │ Thanks! │
- └────────────────────────────┘
-
-Thanks for testing, bugfixes, discussions and everything I forgot go out to:
-
- Alex Lu, Ben Creasy, Bennett Piater, Cast, chressie, clonejo, Dan Elkouby,
- Daniel Mueller, DebianWall, Diki Ananta, Edward Betts, hwangcc23, Ingo Bürk,
- Jan Alexander Steffens, Johannes Lange, Kent Fredric, livanh, Martin
- T. H. Sandsmark, Michael Siegel, Orestis Floros, Pallav Agarwal, Pawel
- S. Veselov, Pietro Cerutti, Theo Buehler, Thomas Praxl, Tyler Brazier,
- Vladimir Panteleev, walker0643, Wes Roberts, xzfc
-
--- Michael Stapelberg, 2018-03-10
diff --git a/RELEASE-NOTES-4.16 b/RELEASE-NOTES-4.16
new file mode 100644
index 00000000..e5505cbf
--- /dev/null
+++ b/RELEASE-NOTES-4.16
@@ -0,0 +1,145 @@
+
+ ┌────────────────────────────┐
+ │ Release notes for i3 v4.16 │
+ └────────────────────────────┘
+
+This is i3 v4.16. This version is considered stable. All users of i3 are
+strongly encouraged to upgrade.
+
+This release contains a number of assorted fixes and improvements across pretty
+much all individual components of i3.
+
+ ┌────────────────────────────┐
+ │ Changes in i3 v4.16 │
+ └────────────────────────────┘
+
+ • build: add conditionals for building docs/mans
+ • docs/i3bar-protocol: mention skipping blocks with empty full_text
+ • docs/ipc: add window_properties to tree node
+ • docs/layout-saving: clarify JSON non-compliance
+ • docs/userguide: clarify X resource value format
+ • docs/userguide: fix move_to_outputs link
+ • docs/userguide: link workspace_auto_back_and_forth from workspace
+ command
+ • docs/userguide: mention known issues for assign
+ • docs/userguide: use anchor for list_of_commands
+ • docs/userguide: add the default keybinding for focus parent
+ • man/*: fix title markers (for asciidoctor)
+ • man/i3-msg.man: add get_config and send_tick
+ • ipc: kill misbehaving subscribed clients instead of hanging
+ • ipc: introduce the sync IPC command for synchronization with i3bar
+ • ipc: scratchpad show now returns correct success
+ • ipc: send_tick now sets the already-documented “first” field
+ • i3bar-protocol: add modifiers to events sent by i3bar
+ • dump-asy: use Pod::Usage for --help and perldoc
+ • dump-asy: introduce -gv flag to disable opening ghostview
+ • dump-asy: introduce -save flag to store the rendered tree in a file
+ • dump-asy: add marks
+ • dump-asy: include floating containers
+ • i3bar: add --verbose flag
+ • i3bar: make modifier accept combinations (like floating_modifier)
+ • i3-config-wizard: add --modifier flag to allow for headless config
+ • i3-config-wizard: support startup notifications
+ • i3-msg: only print input + error position if they are set
+ • i3-msg: check replies also in quiet mode (-q)
+ • i3-msg: add support for the SUBSCRIBE message type
+ • i3-nagbar: support startup notifications
+ • i3-nagbar: add option for button that runs commands without a terminal
+ • i3-save-tree: exclude unsupported transient_for property
+ • i3-sensible-terminal: add alacritty
+ • i3-sensible-terminal: add hyper
+ • introduce strip_workspace_name alongside strip_workspace_numbers
+ • introduce title_align config directive
+ • “border toggle” now accepts an optional pixel argument
+ • “resize set” now interprets 0 as “no change”
+ • “resize set” now accepts the “width” and “height” keywords
+ • “resize” with pixel values now works for tiling containers
+ • the optional “absolute” method is now silently ignored in “move position”
+ commands, where it did not cause a visible difference anyway
+ • the _NET_WM_STATE_FOCUSED atom is now supported, resulting in e.g.
+ GTK applications displaying the correct window decoration
+ • moving fullscreen containers now moves them across outputs
+ • floating windows can now be used with a geometry of e.g. +1+1, i.e.
+ their top-left corner can be outside any output as long as the window
+ is contained partially by one
+ • prefer floating fullscreen containers when switching focus
+ • moving containers to an active workspace no longer changes focus
+ • the rename workspace command no longer confuses directions (e.g. “left”)
+ with output names
+ • prefer $XDG_CONFIG_HOME/i3/config over ~/.i3/config
+ • allow multiple assignments of workspaces to output
+ • respect maximum size in WM_NORMAL_HINTS
+ • reject requests for WM_STATE_ICONIC, which avoids e.g. wine
+ applications being stuck in paused state
+ • a number of code refactorings and cleanups, some of which tool-assisted
+
+ ┌────────────────────────────┐
+ │ Bugfixes │
+ └────────────────────────────┘
+
+ • build: fix static linking
+ • i3bar: fix various memory leaks
+ • i3bar: fix crash when no status_command is provided
+ • i3bar: fix chopping the first character on the very left when using the
+ full width of the output
+ • i3bar: fix relative_x and width properties of click events
+ • i3bar: fix the tray disappearing in some cases when using "tray_output"
+ • fix various memory leaks and memory correctness issues
+ • refocus focused window on FOCUS_IN events for the root window. This
+ fixes incorrect behavior with steam and some tk apps
+ • fix focus bugs when moving unfocused containers
+ • fix incorrect urgent window state edge case
+ • moving an unfocused container from inside a split container to another
+ workspace doesn’t focus siblings
+ • toggling and killing floating windows now maintains focus order
+ • don’t incorrectly focus siblings when scrolling on window decorations
+ • fix crash when moving a container to a marked workspace
+ • fix swap when first is behind a fullscreen window
+ • fix crash when renaming an existing workspace to a name assigned to the
+ focused output
+ • reframe swallowed windows if depth doesn’t match
+ • use detectable autorepeat so that --release bindings are run only when
+ the key is actually released (and not when it is repeated)
+ • fix border artifacts when moving windows
+ • correctly handle bindings for the same mod key with and without --release
+ • reset B_UPON_KEYRELEASE_IGNORE_MODS bindings when switching modes
+ • fix height offset calculation in pango text drawing
+ • fix detection of libiconv on OpenBSD
+ • free workspace assignments on reload
+ • fix mouse position at startup with multiple outputs
+ • no longer allow dragging global fullscreen floating containers
+ • fix rendering artifacts with global fullscreen containers
+ • fix disabling floating for scratchpad windows
+ • fix a crash when renaming an unfocused empty workspace matching an
+ assignment
+ • ensure containers have a size of at least 1px after resize
+ • permit invalid UTF-8 in layout JSON files (e.g. for window titles)
+ • correct invalid UTF-8 characters in window and container titles
+ • fix a crash when moving to a child of a floating container
+ • fix a crash when matching __focused__ with no window open
+ • fix no_focus when only using floating windows
+ • fix max_aspect calculation
+ • moving an unfocused container from another output now maintains
+ the correct focus order
+ • don’t change focus order when swapping containers
+ • correctly update _NET_CURRENT_DESKTOP when moving containers between outputs
+ using the directional move command
+ • don’t produce move events after attempting to directionally move a container
+ towards a direction it can’t go
+ • fix sticky focus when switching to workspace on different output
+
+
+ ┌────────────────────────────┐
+ │ Thanks! │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+ Adrian Cybulski, Aestek, Alan Barr, Andriy Yablonskyy, Cassandra Fox,
+ Christian Duerr, Dan Elkouby, downzer0, Elouan Martinet, Felix Buehler,
+ Gravemind, Harry Lawrence, Hritik Vijay, hwangcc23, Ingo Bürk, Joona, Klorax,
+ lasers, Łukasz Adamczak, Martin, Michael Stapelberg, Oliver Graff,
+ Orestis Floros, Soumya, Takashi Iwai, Thomas Fischer, Todd Walton, Tony
+ Crisci, Uli Schlachter, Vivien Didelot
+
+-- Michael Stapelberg, 2018-11-04
diff --git a/configure.ac b/configure.ac
index 8dce4f9f..7ae01422 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Run autoreconf -fi to generate a configure script from this file.
AC_PREREQ([2.69])
-AC_INIT([i3], [4.15], [https://github.com/i3/i3/issues])
+AC_INIT([i3], [4.16], [https://github.com/i3/i3/issues])
# For AX_EXTEND_SRCDIR
AX_ENABLE_BUILDDIR
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
@@ -81,9 +81,10 @@ AC_SEARCH_LIBS([floor], [m], , [AC_MSG_FAILURE([cannot find the required floor()
# libev does not ship with a pkg-config file :(.
AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_run() function despite trying to link with -lev])])
-AC_SEARCH_LIBS([shm_open], [rt])
+AC_SEARCH_LIBS([shm_open], [rt], [], [], [-pthread])
-AC_SEARCH_LIBS([iconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])])
+AC_SEARCH_LIBS([iconv_open], [iconv], ,
+AC_SEARCH_LIBS([libiconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])]))
AX_PTHREAD
@@ -109,12 +110,27 @@ AC_PROG_MAKE_SET
AC_PROG_RANLIB
AC_PROG_LN_S
-AC_PATH_PROG([PATH_ASCIIDOC], [asciidoc])
-AC_PATH_PROG([PATH_XMLTO], [xmlto])
-AC_PATH_PROG([PATH_POD2MAN], [pod2man])
-
-AM_CONDITIONAL([BUILD_MANS], [test x$PATH_ASCIIDOC != x && test x$PATH_XMLTO != x && test x$PATH_POD2MAN != x])
-AM_CONDITIONAL([BUILD_DOCS], [test x$PATH_ASCIIDOC != x])
+AC_ARG_ENABLE(docs,
+ AS_HELP_STRING(
+ [--disable-docs],
+ [disable building documentation]),
+ [ax_docs=$enableval],
+ [ax_docs=yes])
+AC_ARG_ENABLE(mans,
+ AS_HELP_STRING(
+ [--disable-mans],
+ [disable building manual pages]),
+ [ax_mans=$enableval],
+ [ax_mans=yes])
+AS_IF([test x$ax_docs = xyes || test x$ax_mans = xyes], [
+ AC_PATH_PROG([PATH_ASCIIDOC], [asciidoc])
+])
+AS_IF([test x$ax_mans = xyes], [
+ AC_PATH_PROG([PATH_XMLTO], [xmlto])
+ AC_PATH_PROG([PATH_POD2MAN], [pod2man])
+])
+AM_CONDITIONAL([BUILD_MANS], [test x$ax_mans = xyes && test x$PATH_ASCIIDOC != x && test x$PATH_XMLTO != x && test x$PATH_POD2MAN != x])
+AM_CONDITIONAL([BUILD_DOCS], [test x$ax_docs = xyes && test x$PATH_ASCIIDOC != x])
AM_PROG_AR
diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl
index 9bb2db3a..5af4c72f 100755
--- a/contrib/dump-asy.pl
+++ b/contrib/dump-asy.pl
@@ -1,26 +1,35 @@
#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
-# renders the layout tree using asymptote
-#
-# ./dump-asy.pl
-# will render the entire tree
-# ./dump-asy.pl 'name'
-# will render the tree starting from the node with the specified name,
-# e.g. ./dump-asy.pl 2 will render workspace 2 and below
use strict;
use warnings;
use Data::Dumper;
+use Getopt::Long;
+use Pod::Usage;
use AnyEvent::I3;
use File::Temp;
+use File::Spec;
use File::Basename;
use v5.10;
use IPC::Cmd qw[can_run];
+my %options = (
+ gv => 1,
+ save => undef,
+ help => 0,
+);
+my $result = GetOptions(
+ "gv!" => \$options{gv},
+ "save=s" => \$options{save},
+ "help|?" => \$options{help},
+);
+
+pod2usage(-verbose => 2, -exitcode => 0) if $options{help};
+
# prerequisites check so we can be specific about failures caused
# by not having these tools in the path
can_run('asy') or die 'Please install asymptote';
-can_run('gv') or die 'Please install gv';
+can_run('gv') or die 'Please install gv' unless !$options{gv};
my $i3 = i3();
@@ -37,7 +46,7 @@ sub dump_node {
my $o = ($n->{orientation} eq 'none' ? "u" : ($n->{orientation} eq 'horizontal' ? "h" : "v"));
my $w = (defined($n->{window}) ? $n->{window} : "N");
- my $na = ($n->{name} or "[Empty]");
+ my $na = ($n->{name} or ($n->{type} eq "floating_con" ? "[Floating con]" : "[Empty]"));
$na =~ s/#/\\#/g;
$na =~ s/\$/\\\$/g;
$na =~ s/&/\\&/g;
@@ -47,14 +56,16 @@ sub dump_node {
if (!defined($n->{window})) {
$type = $n->{layout};
}
- my $name = qq|``$na'' ($type)|;
+ my $marks = $n->{marks} ? ' [' . join('][', @{$n->{marks}}) . ']' : '';
+ my $name = qq|``$na'' ($type)$marks|;
print $tmp "TreeNode n" . $n->{id} . " = makeNode(";
print $tmp "n" . $parent->{id} . ", " if defined($parent);
print $tmp "\"" . $name . "\");\n";
- dump_node($_, $n) for @{$n->{nodes}};
+ dump_node($_, $n) for @{$n->{nodes}};
+ dump_node($_, $n) for @{$n->{floating_nodes}};
}
sub find_node_with_name {
@@ -82,5 +93,60 @@ say $tmp "draw(n" . $root->{id} . ", (0, 0));";
close($tmp);
my $rep = "$tmp";
$rep =~ s/asy$/eps/;
-my $tmp_dir = dirname($rep);
-system("cd $tmp_dir && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep");
+if ($options{gv}) {
+ my $tmp_dir = dirname($rep);
+ $options{save} = File::Spec->rel2abs($options{save}) if $options{save};
+ chdir($tmp_dir);
+} else {
+ $rep = basename($rep); # Output in current dir.
+}
+system("asy $tmp"); # Create the .eps file.
+system("gv --scale=-1000 --noresize --widgetless $rep") if $options{gv};
+if ($options{save}) {
+ system("mv $rep ${options{save}}");
+} elsif ($options{gv}) {
+ system("rm $rep");
+}
+system("rm $tmp");
+
+__END__
+
+=head1 NAME
+
+dump-asy.pl - Render the layout tree using asymptote
+
+=head1 SYNOPSIS
+
+dump-asy.pl [workspace]
+
+=head1 EXAMPLE
+
+Render the entire tree, run:
+
+ ./dump-asy.pl
+
+Render the tree starting from the node with the specified name, run:
+
+ ./dump-asy.pl 'name'
+
+Render the entire tree, save in file 'file.eps' and show using gv, run:
+
+ ./dump-asy.pl --save file.eps
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<--gv>
+
+=item B<--no-gv>
+
+Enable or disable showing the result through gv. If disabled, an .eps file will
+be saved in the current working directory. Enabled by default.
+
+=item B<--save>
+
+Save result using the specified file-name. If omitted, no file will be saved
+when using '--gv' or a random name will be used when using '--no-gv'.
+
+=back
diff --git a/debian/changelog b/debian/changelog
index 76fe1577..35b3f9f6 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+i3-wm (4.15.1-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Michael Stapelberg Sat, 10 Mar 2018 17:27:26 +0100
+
i3-wm (4.15-1) unstable; urgency=medium
* New upstream release.
diff --git a/debian/control b/debian/control
index 46e35dfe..71a2599c 100644
--- a/debian/control
+++ b/debian/control
@@ -40,7 +40,7 @@ Description: metapackage (i3 window manager, screen locker, menu, statusbar)
Package: i3-wm
Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, x11-utils
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
Provides: x-window-manager
Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl (>= 0.12), libjson-xs-perl, rxvt-unicode | x-terminal-emulator
Description: improved dynamic tiling window manager
diff --git a/docs/debugging b/docs/debugging
index dd26f98d..562a11f2 100644
--- a/docs/debugging
+++ b/docs/debugging
@@ -160,7 +160,8 @@ flood kicks.
== Debugging i3bar
-To debug i3bar problems, add +verbose yes+ to all +bar {}+ blocks in your i3
+To debug i3bar problems, use the +--verbose+ commandline parameter,
+or add +verbose yes+ to all +bar {}+ blocks in your i3
config, reload your config and then restart all i3bar instances like this:
---------------------------------------------------------------------
diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol
index cf86531c..826cae53 100644
--- a/docs/i3bar-protocol
+++ b/docs/i3bar-protocol
@@ -107,7 +107,7 @@ stop_signal::
processing.
The default value (if none is specified) is SIGSTOP.
cont_signal::
- Specify to i3bar the signal (as an integer)to send to continue your
+ Specify to i3bar the signal (as an integer) to send to continue your
processing.
The default value (if none is specified) is SIGCONT.
click_events::
@@ -118,7 +118,8 @@ click_events::
full_text::
The +full_text+ will be displayed by i3bar on the status line. This is the
- only required key.
+ only required key. If +full_text+ is an empty string, the block will be
+ skipped.
short_text::
Where appropriate, the +short_text+ (string) entry should also be
provided. It will be used in case the status line needs to be shortened
@@ -242,6 +243,9 @@ relative_x, relative_y::
of the block
width, height::
Width and height (in px) of the block
+modifiers::
+ An array of the modifiers active when the click occurred. The order in which
+ modifiers are listed is not guaranteed.
*Example*:
------------------------------------------
@@ -249,6 +253,7 @@ width, height::
"name": "ethernet",
"instance": "eth0",
"button": 1,
+ "modifiers": ["Shift", "Mod1"],
"x": 1320,
"y": 1400,
"relative_x": 12,
diff --git a/docs/ipc b/docs/ipc
index 8b767ade..bcf8df1a 100644
--- a/docs/ipc
+++ b/docs/ipc
@@ -65,6 +65,7 @@ to do that).
| 8 | +GET_BINDING_MODES+ | <<_binding_modes_reply,BINDING_MODES>> | Gets the names of all currently configured binding modes.
| 9 | +GET_CONFIG+ | <<_config_reply,CONFIG>> | Returns the last loaded i3 config.
| 10 | +SEND_TICK+ | <<_tick_reply,TICK>> | Sends a tick event with the specified payload.
+| 11 | +SYNC+ | <<_sync_reply,SYNC>> | Sends an i3 sync event with the specified random value to the specified window.
|======================================================
So, a typical message could look like this:
@@ -327,6 +328,8 @@ window (integer)::
This field is set to null for split containers or otherwise empty
containers. This ID corresponds to what xwininfo(1) and other
X11-related tools display (usually in hex).
+window_properties (map)::
+ X11 window properties title, instance, class, window_role and transient_for.
urgent (bool)::
Whether this container (window, split container, floating container or
workspace) has the urgency hint set, directly or indirectly. All parent
@@ -422,6 +425,12 @@ JSON dump:
"width": 1280,
"height": 782
},
+ "window_properties": {
+ "class": "Evince",
+ "instance": "evince",
+ "title": "Properties",
+ "transient_for": 52428808
+ },
"floating_nodes": [],
"nodes": [
@@ -654,6 +663,18 @@ events generated prior to the +SEND_TICK+ message (happened-before relation).
{ "success": true }
-------------------
+[[_sync_reply]]
+=== SYNC reply
+
+The reply is a map containing the "success" member. After the reply was
+received, the https://i3wm.org/docs/testsuite.html#i3_sync[i3 sync message] was
+responded to.
+
+*Example:*
+-------------------
+{ "success": true }
+-------------------
+
== Events
[[events]]
@@ -672,6 +693,14 @@ program does not want to cope which such kinds of race conditions (an
event based library may not have a problem here), I suggest you create a
separate connection to receive events.
+If an event message needs to be sent and the socket is not writeable (write
+returns EAGAIN, happens when the socket doesn't have enough buffer space for
+writing new data) then i3 uses a queue system to store outgoing messages for
+each client. This is combined with a timer: if the message queue for a client is
+not empty and no data where successfully written in the past 10 seconds, the
+connection is killed. Practically, this means that your client should try to
+always read events from the socket to avoid having its connection closed.
+
=== Subscribing to events
By sending a message of type SUBSCRIBE with a JSON-encoded array as payload
diff --git a/docs/layout-saving b/docs/layout-saving
index e90ecada..f31b5e21 100644
--- a/docs/layout-saving
+++ b/docs/layout-saving
@@ -219,13 +219,15 @@ the window which matches any of the criteria. As an example:
A layout file as generated by +i3-save-tree(1)+ is not strictly valid JSON:
-1. Layout files contain multiple “JSON documents” on the top level, whereas the
- JSON standard only allows precisely one “document” (array or hash).
+1. Layout files contain multiple “JSON texts” at the top level. The JSON
+ standard doesn't prohibit this, but in practice most JSON parsers only
+ allow precisely one “text” per document/file, and will mark multiple texts
+ as invalid JSON.
-2. Layout files contain comments which are not standardized, but understood by
- many parsers.
+2. Layout files contain comments which are not allowed by the JSON standard,
+ but are understood by many parsers.
-Both deviations from the JSON standard are to make manual editing by humans
+Both of these deviations from the norm are to make manual editing by humans
easier. In case you are writing a more elaborate tool for manipulating these
layouts, you can either use a JSON parser that supports these deviations (for
example libyajl), transform the layout file to a JSON-conforming file, or
diff --git a/docs/userguide b/docs/userguide
index ba314af1..91060ab2 100644
--- a/docs/userguide
+++ b/docs/userguide
@@ -245,9 +245,11 @@ you open a new terminal, it will open below the current one.
So, how can you open a new terminal window to the *right* of the current one?
The solution is to use +focus parent+, which will focus the +Parent Container+ of
-the current +Container+. In this case, you would focus the +Vertical Split
-Container+ which is *inside* the horizontally oriented workspace. Thus, now new
-windows will be opened to the right of the +Vertical Split Container+:
+the current +Container+. In default configuration, use +$mod+a+ to navigate one
++Container+ up the tree (you can repeat this multiple times until you get to the
++Workspace Container+). In this case, you would focus the +Vertical Split Container+
+which is *inside* the horizontally oriented workspace. Thus, now new windows will be
+opened to the right of the +Vertical Split Container+:
image::tree-shot3.png["shot3",title="Focus parent, then open new terminal"]
@@ -585,6 +587,16 @@ workspace_layout default|stacking|tabbed
workspace_layout tabbed
---------------------
+=== Window title alignment
+
+This option determines the window title's text alignment.
+Default is +left+
+
+*Syntax*:
+---------------------------------------------
+title_align left|center|right
+---------------------------------------------
+
=== Default border style for new windows
This option determines which border style new windows will have. The default is
@@ -730,7 +742,8 @@ resource database to achieve an easily maintainable, consistent color theme
across many X applications.
Defining a resource will load this resource from the resource database and
-assign its value to the specified variable. A fallback must be specified in
+assign its value to the specified variable. This is done verbatim and the value
+must therefore be in the format that i3 uses. A fallback must be specified in
case the resource cannot be loaded from the database.
*Syntax*:
@@ -754,14 +767,23 @@ set_from_resource $black i3wm.color0 #000000
To automatically make a specific window show up on a specific workspace, you
can use an *assignment*. You can match windows by using any criteria,
-see <>. It is recommended that you match on window classes
-(and instances, when appropriate) instead of window titles whenever possible
-because some applications first create their window, and then worry about
-setting the correct title. Firefox with Vimperator comes to mind. The window
-starts up being named Firefox, and only when Vimperator is loaded does the
-title change. As i3 will get the title as soon as the application maps the
-window (mapping means actually displaying it on the screen), you’d need to have
-to match on 'Firefox' in this case.
+see <>. The difference between +assign+ and
++for_window move to workspace+ is that the former will only be
+executed when the application maps the window (mapping means actually displaying
+it on the screen) but the latter will be executed whenever a window changes its
+properties to something that matches the specified criteria.
+
+Thus, it is recommended that you match on window classes (and instances, when
+appropriate) instead of window titles whenever possible because some
+applications first create their window, and then worry about setting the correct
+title. Firefox with Vimperator comes to mind. The window starts up being named
+Firefox, and only when Vimperator is loaded does the title change. As i3 will
+get the title as soon as the application maps the window, you’d need to have to
+match on 'Firefox' in this case.
+Another known issue is with Spotify, which doesn't set the class hints when
+mapping the window, meaning you'll have to use a +for_window+ rule to assign
+Spotify to a specific workspace.
+Finally, using +assign [tiling]+ and +assign [floating]+ is not supported.
You can also assign a window to show up on a specific output. You can use RandR
names such as +VGA1+ or names relative to the output with the currently focused
@@ -887,7 +909,7 @@ the second screen and so on).
*Syntax*:
-------------------------------------
-workspace output
+workspace output [output2]…
-------------------------------------
The 'output' is the name of the RandR output you attach your screen to. On a
@@ -906,12 +928,15 @@ monitor name is “Dell UP2414Q”.
entire monitor, i3 will still use the entire area of the containing monitor
rather than that of just the output's.)
+You can specify multiple outputs. The first available will be used.
+
If you use named workspaces, they must be quoted:
*Examples*:
---------------------------
workspace 1 output LVDS1
-workspace 5 output VGA1
+workspace 2 output primary
+workspace 5 output VGA1 LVDS1
workspace "2: vim" output VGA1
---------------------------
@@ -991,7 +1016,7 @@ ipc-socket ~/.i3/i3-ipc.sock
----------------------------
You can then use the +i3-msg+ application to perform any command listed in
-the next section.
+<>.
=== Focus follows mouse
@@ -1117,6 +1142,7 @@ force_xinerama yes
Also note that your output names are not descriptive (like +HDMI1+) when using
Xinerama, instead they are counted up, starting at 0: +xinerama-0+, +xinerama-1+, …
+[[workspace_auto_back_and_forth]]
=== Automatic back-and-forth when switching to the current workspace
This configuration directive enables automatic +workspace back_and_forth+ (see
@@ -1594,7 +1620,7 @@ bar {
}
------------------------
-=== Strip workspace numbers
+=== Strip workspace numbers/name
Specifies whether workspace numbers should be displayed within the workspace
buttons. This is useful if you want to have a named workspace that stays in
@@ -1605,11 +1631,15 @@ the form "[n]:[NAME]" will display only the name. You could use this, for
instance, to display Roman numerals rather than digits by naming your
workspaces to "1:I", "2:II", "3:III", "4:IV", ...
+When +strip_workspace_name+ is set to +yes+, any workspace that has a name of
+the form "[n]:[NAME]" will display only the number.
+
The default is to display the full name within the workspace button.
*Syntax*:
------------------------------
strip_workspace_numbers yes|no
+strip_workspace_name yes|no
------------------------------
*Example*:
@@ -1707,6 +1737,7 @@ bar {
}
--------------------------------------
+[[list_of_commands]]
== List of commands
Commands are what you bind to specific keypresses. You can also issue commands
@@ -2025,10 +2056,13 @@ Use the +move+ command to move a container.
# defaults to 10 pixels.
move [ px]
-# Moves the container either to a specific location
-# or to the center of the screen. If 'absolute' is
-# used, it is moved to the center of all outputs.
-move [absolute] position [px] [px]
+# Moves the container to the specified pos_x and pos_y
+# coordinates on the screen.
+move position [px] [px]
+
+# Moves the container to the center of the screen.
+# If 'absolute' is used, it is moved to the center of
+# all outputs.
move [absolute] position center
# Moves the container to the current position of the
@@ -2113,8 +2147,8 @@ for_window [instance=notepad] sticky enable
To change to a specific workspace, use the +workspace+ command, followed by the
number or name of the workspace. Pass the optional flag
-+--no-auto-back-and-forth+ to disable <> for this specific call
-only.
++--no-auto-back-and-forth+ to disable <> for this
+specific call only.
To move containers to specific workspaces, use +move container to workspace+.
@@ -2253,8 +2287,7 @@ See <> for how to move a container/workspace to a different
RandR output.
[[move_to_outputs]]
-[[_moving_containers_workspaces_to_randr_outputs]]
-=== Moving containers/workspaces to RandR outputs
+=== [[_moving_containers_workspaces_to_randr_outputs]]Moving containers/workspaces to RandR outputs
To move a container to another RandR output (addressed by names like +LVDS1+ or
+VGA1+) or to a RandR output identified by a specific direction (like +left+,
@@ -2313,20 +2346,21 @@ If you want to resize containers/windows using your keyboard, you can use the
*Syntax*:
-------------------------------------------------------
resize grow|shrink [ px [or ppt]]
-resize set [px | ppt] [px | ppt]
+resize set [width] [px | ppt]
+resize set height [px | ppt]
+resize set [width] [px | ppt] [height] [px | ppt]
-------------------------------------------------------
Direction can either be one of +up+, +down+, +left+ or +right+. Or you can be
-less specific and use +width+ or +height+, in which case i3 will take/give
-space from all the other containers. The optional pixel argument specifies by
-how many pixels a *floating container* should be grown or shrunk (the default
-is 10 pixels). The ppt argument means percentage points and specifies by how
-many percentage points a *tiling container* should be grown or shrunk (the
-default is 10 percentage points).
+less specific and use +width+ or +height+, in which case i3 will take/give space
+from all the other containers. The optional pixel argument specifies by how many
+pixels a container should be grown or shrunk (the default is 10 pixels). The
+optional ppt argument means "percentage points", and if specified it indicates
+that a *tiling container* should be grown or shrunk by that many points, instead
+of by the +px+ value.
-Notes about +resize set+: a value of 0 for or means "do
-not resize in this direction", and resizing a tiling container by +px+ is not
-implemented.
+Note about +resize set+: a value of 0 for or means "do not
+resize in this direction".
It is recommended to define bindings for resizing in a dedicated binding mode.
See <> and the example in the i3
@@ -2469,7 +2503,9 @@ To change the border of the current client, you can use +border normal+ to use t
border (including window title), +border pixel 1+ to use a 1-pixel border (no window title)
and +border none+ to make the client borderless.
-There is also +border toggle+ which will toggle the different border styles.
+There is also +border toggle+ which will toggle the different border styles. The
+optional pixel argument can be used to specify the border width when switching
+to the normal and pixel styles.
Note that "pixel" refers to logical pixel. On HiDPI displays, a logical pixel
may be represented by multiple physical pixels, so +pixel 1+ might not
@@ -2477,8 +2513,8 @@ necessarily translate into a single pixel row wide border.
*Syntax*:
-----------------------------------------------
-border normal|pixel []
-border none|toggle
+border normal|pixel|toggle []
+border none
# legacy syntax, equivalent to "border pixel 1"
border 1pixel
diff --git a/etc/config b/etc/config
index 3be9831d..da51d570 100644
--- a/etc/config
+++ b/etc/config
@@ -147,7 +147,7 @@ bindsym Mod1+Shift+c reload
# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
bindsym Mod1+Shift+r restart
# exit i3 (logs you out of your X session)
-bindsym Mod1+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
+bindsym Mod1+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -B 'Yes, exit i3' 'i3-msg exit'"
# resize window (you can also use the mouse for that)
mode "resize" {
diff --git a/etc/config.keycodes b/etc/config.keycodes
index 2d56876c..6fc19426 100644
--- a/etc/config.keycodes
+++ b/etc/config.keycodes
@@ -133,7 +133,7 @@ bindcode $mod+Shift+54 reload
# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
bindcode $mod+Shift+27 restart
# exit i3 (logs you out of your X session)
-bindcode $mod+Shift+26 exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
+bindcode $mod+Shift+26 exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -B 'Yes, exit i3' 'i3-msg exit'"
# resize window (you can also use the mouse for that)
mode "resize" {
diff --git a/generate-command-parser.pl b/generate-command-parser.pl
index 4c45b6ed..052e4c66 100755
--- a/generate-command-parser.pl
+++ b/generate-command-parser.pl
@@ -77,7 +77,7 @@ for my $line (@lines) {
($line =~ /
^\s* # skip leading whitespace
([a-z_]+ \s* = \s*|) # optional identifier
- (.*?) -> \s* # token
+ (.*?) -> \s* # token
(.*) # optional action
/x);
diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c
index b368921f..4b556657 100644
--- a/i3-config-wizard/main.c
+++ b/i3-config-wizard/main.c
@@ -48,6 +48,9 @@
#include
#include
+#define SN_API_NOT_YET_FROZEN 1
+#include
+
#include
#include
#include
@@ -101,7 +104,7 @@ static struct xkb_keymap *xkb_keymap;
static uint8_t xkb_base_event;
static uint8_t xkb_base_error;
-static void finish();
+static void finish(void);
#include "GENERATED_config_enums.h"
@@ -213,7 +216,7 @@ static const char *get_string(const char *identifier) {
static void clear_stack(void) {
for (int c = 0; c < 10; c++) {
- if (stack[c].type == STACK_STR && stack[c].val.str != NULL)
+ if (stack[c].type == STACK_STR)
free(stack[c].val.str);
stack[c].identifier = NULL;
stack[c].val.str = NULL;
@@ -293,6 +296,7 @@ static char *next_state(const cmdp_token *token) {
}
sasprintf(&res, "bindsym %s%s%s %s%s\n", (modifiers == NULL ? "" : modrep), (modifiers == NULL ? "" : "+"), str, (release == NULL ? "" : release), get_string("command"));
clear_stack();
+ free(modrep);
return res;
}
@@ -478,7 +482,7 @@ static void txt(int col, int row, char *text, color_t fg, color_t bg) {
* Handles expose events, that is, draws the window contents.
*
*/
-static int handle_expose() {
+static int handle_expose(void) {
const color_t black = draw_util_hex_to_color("#000000");
const color_t white = draw_util_hex_to_color("#FFFFFF");
const color_t green = draw_util_hex_to_color("#00FF00");
@@ -631,15 +635,13 @@ static void handle_button_press(xcb_button_press_event_t *event) {
modifier = MOD_Mod1;
handle_expose();
}
-
- return;
}
/*
* Creates the config file and tells i3 to reload.
*
*/
-static void finish() {
+static void finish(void) {
printf("creating \"%s\"...\n", config_path);
struct xkb_context *xkb_context;
@@ -745,10 +747,12 @@ int main(int argc, char *argv[]) {
char *pattern = "pango:monospace 8";
char *patternbold = "pango:monospace bold 8";
int o, option_index = 0;
+ bool headless_run = false;
static struct option long_options[] = {
{"socket", required_argument, 0, 's'},
{"version", no_argument, 0, 'v'},
+ {"modifier", required_argument, 0, 'm'},
{"limit", required_argument, 0, 'l'},
{"prompt", required_argument, 0, 'P'},
{"prefix", required_argument, 0, 'p'},
@@ -756,7 +760,7 @@ int main(int argc, char *argv[]) {
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}};
- char *options_string = "s:vh";
+ char *options_string = "sm:vh";
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
switch (o) {
@@ -767,9 +771,18 @@ int main(int argc, char *argv[]) {
case 'v':
printf("i3-config-wizard " I3_VERSION "\n");
return 0;
+ case 'm':
+ headless_run = true;
+ if (strcmp(optarg, "alt") == 0)
+ modifier = MOD_Mod1;
+ else if (strcmp(optarg, "win") == 0)
+ modifier = MOD_Mod4;
+ else
+ err(EXIT_FAILURE, "Invalid modifier key %s", optarg);
+ break;
case 'h':
printf("i3-config-wizard " I3_VERSION "\n");
- printf("i3-config-wizard [-s ] [-v]\n");
+ printf("i3-config-wizard [-s ] [-m win|alt] [-v] [-h]\n");
return 0;
}
}
@@ -826,12 +839,22 @@ int main(int argc, char *argv[]) {
modmap_cookie = xcb_get_modifier_mapping(conn);
symbols = xcb_key_symbols_alloc(conn);
+ if (headless_run) {
+ finish();
+ return 0;
+ }
+
/* Place requests for the atoms we need as soon as possible */
#define xmacro(atom) \
xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
#include "atoms.xmacro"
#undef xmacro
+ /* Init startup notification. */
+ SnDisplay *sndisplay = sn_xcb_display_new(conn, NULL, NULL);
+ SnLauncheeContext *sncontext = sn_launchee_context_new_from_environment(sndisplay, screen);
+ sn_display_unref(sndisplay);
+
root_screen = xcb_aux_get_screen(conn, screen);
root = root_screen->root;
@@ -864,6 +887,9 @@ int main(int argc, char *argv[]) {
0, /* back pixel: black */
XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_BUTTON_PRESS});
+ if (sncontext) {
+ sn_launchee_context_setup_window(sncontext, win);
+ }
/* Map the window (make it visible) */
xcb_map_window(conn, win);
@@ -925,6 +951,12 @@ int main(int argc, char *argv[]) {
exit(-1);
}
+ /* Startup complete. */
+ if (sncontext) {
+ sn_launchee_context_complete(sncontext);
+ sn_launchee_context_unref(sncontext);
+ }
+
xcb_flush(conn);
xcb_generic_event_t *event;
@@ -937,13 +969,12 @@ int main(int argc, char *argv[]) {
/* Strip off the highest bit (set if the event is generated) */
int type = (event->response_type & 0x7F);
+ /* TODO: handle mappingnotify */
switch (type) {
case XCB_KEY_PRESS:
handle_key_press(NULL, conn, (xcb_key_press_event_t *)event);
break;
- /* TODO: handle mappingnotify */
-
case XCB_BUTTON_PRESS:
handle_button_press((xcb_button_press_event_t *)event);
break;
diff --git a/i3-input/main.c b/i3-input/main.c
index efb7b20c..d1a2efd7 100644
--- a/i3-input/main.c
+++ b/i3-input/main.c
@@ -156,7 +156,7 @@ static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_rel
return 1;
}
-static void finish_input() {
+static void finish_input(void) {
char *command = (char *)concat_strings(glyphs_utf8, input_position);
/* count the occurrences of %s in the string */
diff --git a/i3-msg/main.c b/i3-msg/main.c
index 91a714e5..fe111416 100644
--- a/i3-msg/main.c
+++ b/i3-msg/main.c
@@ -95,8 +95,10 @@ static int reply_start_map_cb(void *params) {
static int reply_end_map_cb(void *params) {
if (!last_reply.success) {
- fprintf(stderr, "ERROR: Your command: %s\n", last_reply.input);
- fprintf(stderr, "ERROR: %s\n", last_reply.errorposition);
+ if (last_reply.input) {
+ fprintf(stderr, "ERROR: Your command: %s\n", last_reply.input);
+ fprintf(stderr, "ERROR: %s\n", last_reply.errorposition);
+ }
fprintf(stderr, "ERROR: %s\n", last_reply.error);
}
return 1;
@@ -164,16 +166,18 @@ int main(int argc, char *argv[]) {
uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND;
char *payload = NULL;
bool quiet = false;
+ bool monitor = false;
static struct option long_options[] = {
{"socket", required_argument, 0, 's'},
{"type", required_argument, 0, 't'},
{"version", no_argument, 0, 'v'},
{"quiet", no_argument, 0, 'q'},
+ {"monitor", no_argument, 0, 'm'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}};
- char *options_string = "s:t:vhq";
+ char *options_string = "s:t:vhqm";
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
if (o == 's') {
@@ -202,25 +206,34 @@ int main(int argc, char *argv[]) {
message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
} else if (strcasecmp(optarg, "send_tick") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_SEND_TICK;
+ } else if (strcasecmp(optarg, "subscribe") == 0) {
+ message_type = I3_IPC_MESSAGE_TYPE_SUBSCRIBE;
} else {
printf("Unknown message type\n");
- printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config, send_tick\n");
+ printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config, send_tick, subscribe\n");
exit(EXIT_FAILURE);
}
} else if (o == 'q') {
quiet = true;
+ } else if (o == 'm') {
+ monitor = true;
} else if (o == 'v') {
printf("i3-msg " I3_VERSION "\n");
return 0;
} else if (o == 'h') {
printf("i3-msg " I3_VERSION "\n");
- printf("i3-msg [-s ] [-t ] \n");
+ printf("i3-msg [-s ] [-t ] [-m] \n");
return 0;
} else if (o == '?') {
exit(EXIT_FAILURE);
}
}
+ if (monitor && message_type != I3_IPC_MESSAGE_TYPE_SUBSCRIBE) {
+ fprintf(stderr, "The monitor option -m is used with -t SUBSCRIBE exclusively.\n");
+ exit(EXIT_FAILURE);
+ }
+
/* Use all arguments, separated by whitespace, as payload.
* This way, you don’t have to do i3-msg 'mark foo', you can use
* i3-msg mark foo */
@@ -244,9 +257,6 @@ int main(int argc, char *argv[]) {
err(EXIT_FAILURE, "IPC: write()");
free(payload);
- if (quiet)
- return 0;
-
uint32_t reply_length;
uint32_t reply_type;
uint8_t *reply;
@@ -273,8 +283,9 @@ int main(int argc, char *argv[]) {
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
}
- /* NB: We still fall-through and print the reply, because even if one
- * command failed, that doesn’t mean that all commands failed. */
+ if (!quiet) {
+ printf("%.*s\n", reply_length, reply);
+ }
} else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) {
yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
@@ -287,12 +298,30 @@ int main(int argc, char *argv[]) {
case yajl_status_error:
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
}
+ } else if (reply_type == I3_IPC_REPLY_TYPE_SUBSCRIBE) {
+ do {
+ free(reply);
+ if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
+ if (ret == -1)
+ err(EXIT_FAILURE, "IPC: read()");
+ exit(1);
+ }
- goto exit;
+ if (!(reply_type & I3_IPC_EVENT_MASK)) {
+ errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected an event", reply_type);
+ }
+
+ if (!quiet) {
+ fprintf(stdout, "%.*s\n", reply_length, reply);
+ fflush(stdout);
+ }
+ } while (monitor);
+ } else {
+ if (!quiet) {
+ printf("%.*s\n", reply_length, reply);
+ }
}
- printf("%.*s\n", reply_length, reply);
-exit:
free(reply);
close(sockfd);
diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c
index e4628e30..4ce74939 100644
--- a/i3-nagbar/main.c
+++ b/i3-nagbar/main.c
@@ -32,6 +32,9 @@
#include
#include
+#define SN_API_NOT_YET_FROZEN 1
+#include
+
#include "i3-nagbar.h"
/** This is the equivalent of XC_left_ptr. I’m not sure why xcb doesn’t have a
@@ -52,6 +55,7 @@ typedef struct {
char *action;
int16_t x;
uint16_t width;
+ bool terminal;
} button_t;
static xcb_window_t win;
@@ -99,10 +103,10 @@ void debuglog(char *fmt, ...) {
}
/*
- * Starts the given application by passing it through a shell. We use double fork
- * to avoid zombie processes. As the started application’s parent exits (immediately),
- * the application is reparented to init (process-id 1), which correctly handles
- * childs, so we don’t have to do it :-).
+ * Starts the given application by passing it through a shell. We use double
+ * fork to avoid zombie processes. As the started application’s parent exits
+ * (immediately), the application is reparented to init (process-id 1), which
+ * correctly handles children, so we don’t have to do it :-).
*
* The shell is determined by looking for the SHELL environment variable. If it
* does not exist, /bin/sh is used.
@@ -115,7 +119,7 @@ static void start_application(const char *command) {
setsid();
if (fork() == 0) {
/* This is the child */
- execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void *)NULL);
+ execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL);
/* not reached */
}
exit(0);
@@ -184,7 +188,11 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
}
char *terminal_cmd;
- sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path);
+ if (button->terminal) {
+ sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path);
+ } else {
+ terminal_cmd = sstrdup(link_path);
+ }
printf("argv0 = %s\n", argv0);
printf("terminal_cmd = %s\n", terminal_cmd);
@@ -358,12 +366,13 @@ int main(int argc, char *argv[]) {
{"version", no_argument, 0, 'v'},
{"font", required_argument, 0, 'f'},
{"button", required_argument, 0, 'b'},
+ {"button-no-terminal", required_argument, 0, 'B'},
{"help", no_argument, 0, 'h'},
{"message", required_argument, 0, 'm'},
{"type", required_argument, 0, 't'},
{0, 0, 0, 0}};
- char *options_string = "b:f:m:t:vh";
+ char *options_string = "b:B:f:m:t:vh";
prompt = i3string_from_utf8("Please do not run this program.");
@@ -385,12 +394,14 @@ int main(int argc, char *argv[]) {
break;
case 'h':
printf("i3-nagbar " I3_VERSION "\n");
- printf("i3-nagbar [-m ] [-b ] [-t warning|error] [-f ] [-v]\n");
+ printf("i3-nagbar [-m ] [-b ] [-B ] [-t warning|error] [-f ] [-v]\n");
return 0;
case 'b':
+ case 'B':
buttons = srealloc(buttons, sizeof(button_t) * (buttoncnt + 1));
buttons[buttoncnt].label = i3string_from_utf8(optarg);
buttons[buttoncnt].action = argv[optind];
+ buttons[buttoncnt].terminal = (o == 'b');
printf("button with label *%s* and action *%s*\n",
i3string_as_utf8(buttons[buttoncnt].label),
buttons[buttoncnt].action);
@@ -415,6 +426,11 @@ int main(int argc, char *argv[]) {
#include "atoms.xmacro"
#undef xmacro
+ /* Init startup notification. */
+ SnDisplay *sndisplay = sn_xcb_display_new(conn, NULL, NULL);
+ SnLauncheeContext *sncontext = sn_launchee_context_new_from_environment(sndisplay, screens);
+ sn_display_unref(sndisplay);
+
root_screen = xcb_aux_get_screen(conn, screens);
root = root_screen->root;
@@ -484,6 +500,9 @@ int main(int argc, char *argv[]) {
XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_BUTTON_RELEASE,
cursor});
+ if (sncontext) {
+ sn_launchee_context_setup_window(sncontext, win);
+ }
/* Map the window (make it visible) */
xcb_map_window(conn, win);
@@ -544,6 +563,12 @@ int main(int argc, char *argv[]) {
/* Initialize the drawable bar */
draw_util_surface_init(conn, &bar, win, get_visualtype(root_screen), win_pos.width, win_pos.height);
+ /* Startup complete. */
+ if (sncontext) {
+ sn_launchee_context_complete(sncontext);
+ sn_launchee_context_unref(sncontext);
+ }
+
/* Grab the keyboard to get all input */
xcb_flush(conn);
diff --git a/i3-save-tree b/i3-save-tree
index 1e56a045..da5e6ded 100755
--- a/i3-save-tree
+++ b/i3-save-tree
@@ -123,9 +123,7 @@ sub strip_containers {
delete $tree->{current_border_width} if $tree->{current_border_width} == -1;
for my $key (keys %$tree) {
- next if exists($allowed_keys{$key});
-
- delete $tree->{$key};
+ delete $tree->{$key} unless exists($allowed_keys{$key});
}
for my $key (qw(nodes floating_nodes)) {
@@ -169,7 +167,8 @@ sub dump_containers {
if (leaf_node($tree)) {
my $swallows = {};
for my $property (keys %{$tree->{window_properties}}) {
- $swallows->{$property} = '^' . quotemeta($tree->{window_properties}->{$property}) . '$';
+ $swallows->{$property} = '^' . quotemeta($tree->{window_properties}->{$property}) . '$'
+ if $property ne 'transient_for';
}
$tree->{swallows} = [ $swallows ];
}
diff --git a/i3-sensible-terminal b/i3-sensible-terminal
index f1eb256e..c3a2e4e0 100755
--- a/i3-sensible-terminal
+++ b/i3-sensible-terminal
@@ -8,7 +8,7 @@
# We welcome patches that add distribution-specific mechanisms to find the
# preferred terminal emulator. On Debian, there is the x-terminal-emulator
# symlink for example.
-for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda; do
+for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper; do
if command -v "$terminal" > /dev/null 2>&1; then
exec "$terminal" "$@"
fi
diff --git a/i3bar/include/child.h b/i3bar/include/child.h
index 9479fac1..3afed819 100644
--- a/i3bar/include/child.h
+++ b/i3bar/include/child.h
@@ -31,7 +31,7 @@ typedef struct {
*/
int stop_signal;
/**
- * The signal requested by the client to inform it of theun hidden state of i3bar
+ * The signal requested by the client to inform it of the unhidden state of i3bar
*/
int cont_signal;
@@ -85,4 +85,4 @@ bool child_want_click_events(void);
* Generates a click event, if enabled.
*
*/
-void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int width, int height);
+void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int width, int height, int mods);
diff --git a/i3bar/include/configuration.h b/i3bar/include/configuration.h
index 61cac7f6..b86da2e0 100644
--- a/i3bar/include/configuration.h
+++ b/i3bar/include/configuration.h
@@ -41,17 +41,18 @@ typedef struct tray_output_t {
} tray_output_t;
typedef struct config_t {
- int modifier;
+ uint32_t modifier;
TAILQ_HEAD(bindings_head, binding_t)
bindings;
position_t position;
- int verbose;
+ bool verbose;
struct xcb_color_strings_t colors;
bool disable_binding_mode_indicator;
bool disable_ws;
bool strip_ws_numbers;
+ bool strip_ws_name;
char *bar_id;
char *command;
char *fontname;
diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h
index 7783e877..760ebcdb 100644
--- a/i3bar/include/xcb.h
+++ b/i3bar/include/xcb.h
@@ -60,7 +60,7 @@ int separator_symbol_width;
* depend on 'config'.
*
*/
-char *init_xcb_early();
+char *init_xcb_early(void);
/**
* Initialization which depends on 'config' being usable. Called after the
diff --git a/i3bar/src/child.c b/i3bar/src/child.c
index 1cd7d512..7c527dc3 100644
--- a/i3bar/src/child.c
+++ b/i3bar/src/child.c
@@ -8,6 +8,7 @@
*
*/
#include "common.h"
+#include "yajl_utils.h"
#include
#include
@@ -27,6 +28,8 @@
#include
#include
+#include
+
/* Global variables for child_*() */
i3bar_child child;
@@ -133,7 +136,7 @@ finish:
* Stop and free() the stdin- and SIGCHLD-watchers
*
*/
-void cleanup(void) {
+static void cleanup(void) {
if (stdin_io != NULL) {
ev_io_stop(main_loop, stdin_io);
FREE(stdin_io);
@@ -400,7 +403,7 @@ static bool read_json_input(unsigned char *input, int length) {
* in statusline
*
*/
-void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+static void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
int rec;
unsigned char *buffer = get_buffer(watcher, &rec);
if (buffer == NULL)
@@ -420,7 +423,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
* whether this is JSON or plain text
*
*/
-void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
int rec;
unsigned char *buffer = get_buffer(watcher, &rec);
if (buffer == NULL)
@@ -457,7 +460,7 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
* anymore
*
*/
-void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
+static void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
int exit_status = WEXITSTATUS(watcher->rstatus);
ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
@@ -477,7 +480,7 @@ void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
draw_bars(false);
}
-void child_write_output(void) {
+static void child_write_output(void) {
if (child.click_events) {
const unsigned char *output;
size_t size;
@@ -580,7 +583,7 @@ void start_child(char *command) {
atexit(kill_child_at_exit);
}
-void child_click_events_initialize(void) {
+static void child_click_events_initialize(void) {
if (!child.click_events_init) {
yajl_gen_array_open(gen);
child_write_output();
@@ -588,15 +591,11 @@ void child_click_events_initialize(void) {
}
}
-void child_click_events_key(const char *key) {
- yajl_gen_string(gen, (const unsigned char *)key, strlen(key));
-}
-
/*
* Generates a click event, if enabled.
*
*/
-void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int width, int height) {
+void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int width, int height, int mods) {
if (!child.click_events) {
return;
}
@@ -606,34 +605,52 @@ void send_block_clicked(int button, const char *name, const char *instance, int
yajl_gen_map_open(gen);
if (name) {
- child_click_events_key("name");
- yajl_gen_string(gen, (const unsigned char *)name, strlen(name));
+ ystr("name");
+ ystr(name);
}
if (instance) {
- child_click_events_key("instance");
- yajl_gen_string(gen, (const unsigned char *)instance, strlen(instance));
+ ystr("instance");
+ ystr(instance);
}
- child_click_events_key("button");
+ ystr("button");
yajl_gen_integer(gen, button);
- child_click_events_key("x");
+ ystr("modifiers");
+ yajl_gen_array_open(gen);
+ if (mods & XCB_MOD_MASK_SHIFT)
+ ystr("Shift");
+ if (mods & XCB_MOD_MASK_CONTROL)
+ ystr("Control");
+ if (mods & XCB_MOD_MASK_1)
+ ystr("Mod1");
+ if (mods & XCB_MOD_MASK_2)
+ ystr("Mod2");
+ if (mods & XCB_MOD_MASK_3)
+ ystr("Mod3");
+ if (mods & XCB_MOD_MASK_4)
+ ystr("Mod4");
+ if (mods & XCB_MOD_MASK_5)
+ ystr("Mod5");
+ yajl_gen_array_close(gen);
+
+ ystr("x");
yajl_gen_integer(gen, x);
- child_click_events_key("y");
+ ystr("y");
yajl_gen_integer(gen, y);
- child_click_events_key("relative_x");
+ ystr("relative_x");
yajl_gen_integer(gen, x_rel);
- child_click_events_key("relative_y");
+ ystr("relative_y");
yajl_gen_integer(gen, y_rel);
- child_click_events_key("width");
+ ystr("width");
yajl_gen_integer(gen, width);
- child_click_events_key("height");
+ ystr("height");
yajl_gen_integer(gen, height);
yajl_gen_map_close(gen);
diff --git a/i3bar/src/config.c b/i3bar/src/config.c
index a58b9bf8..c2325629 100644
--- a/i3bar/src/config.c
+++ b/i3bar/src/config.c
@@ -119,6 +119,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
return 1;
}
+ /* Kept for backwards compatibility. */
if (!strcmp(cur_key, "modifier")) {
DLOG("modifier = %.*s\n", len, val);
if (len == strlen("none") && !strncmp((const char *)val, "none", strlen("none"))) {
@@ -297,9 +298,17 @@ static int config_boolean_cb(void *params_, int val) {
return 1;
}
+ if (!strcmp(cur_key, "strip_workspace_name")) {
+ DLOG("strip_workspace_name = %d\n", val);
+ config.strip_ws_name = val;
+ return 1;
+ }
+
if (!strcmp(cur_key, "verbose")) {
- DLOG("verbose = %d\n", val);
- config.verbose = val;
+ if (!config.verbose) {
+ DLOG("verbose = %d\n", val);
+ config.verbose = val;
+ }
return 1;
}
@@ -330,6 +339,12 @@ static int config_integer_cb(void *params_, long long val) {
return 1;
}
+ if (!strcmp(cur_key, "modifier")) {
+ DLOG("modifier = %lld\n", val);
+ config.modifier = (uint32_t)val;
+ return 1;
+ }
+
return 0;
}
diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c
index cc3563ec..df5a12cf 100644
--- a/i3bar/src/ipc.c
+++ b/i3bar/src/ipc.c
@@ -34,7 +34,7 @@ typedef void (*handler_t)(char *);
* Since i3 does not give us much feedback on commands, we do not much
*
*/
-void got_command_reply(char *reply) {
+static void got_command_reply(char *reply) {
/* TODO: Error handling for command replies */
}
@@ -42,7 +42,7 @@ void got_command_reply(char *reply) {
* Called, when we get a reply with workspaces data
*
*/
-void got_workspace_reply(char *reply) {
+static void got_workspace_reply(char *reply) {
DLOG("Got workspace data!\n");
parse_workspaces_json(reply);
draw_bars(false);
@@ -53,7 +53,7 @@ void got_workspace_reply(char *reply) {
* Since i3 does not give us much feedback on commands, we do not much
*
*/
-void got_subscribe_reply(char *reply) {
+static void got_subscribe_reply(char *reply) {
DLOG("Got subscribe reply: %s\n", reply);
/* TODO: Error handling for subscribe commands */
}
@@ -62,7 +62,7 @@ void got_subscribe_reply(char *reply) {
* Called, when we get a reply with outputs data
*
*/
-void got_output_reply(char *reply) {
+static void got_output_reply(char *reply) {
DLOG("Clearing old output configuration...\n");
free_outputs();
@@ -87,7 +87,7 @@ void got_output_reply(char *reply) {
* Called when we get the configuration for our bar instance
*
*/
-void got_bar_config(char *reply) {
+static void got_bar_config(char *reply) {
DLOG("Received bar config \"%s\"\n", reply);
/* We initiate the main function by requesting infos about the outputs and
* workspaces. Everything else (creating the bars, showing the right workspace-
@@ -114,20 +114,25 @@ void got_bar_config(char *reply) {
/* Data structure to easily call the reply handlers later */
handler_t reply_handlers[] = {
- &got_command_reply,
- &got_workspace_reply,
- &got_subscribe_reply,
- &got_output_reply,
- NULL,
- NULL,
- &got_bar_config,
+ &got_command_reply, /* I3_IPC_REPLY_TYPE_COMMAND */
+ &got_workspace_reply, /* I3_IPC_REPLY_TYPE_WORKSPACES */
+ &got_subscribe_reply, /* I3_IPC_REPLY_TYPE_SUBSCRIBE */
+ &got_output_reply, /* I3_IPC_REPLY_TYPE_OUTPUTS */
+ NULL, /* I3_IPC_REPLY_TYPE_TREE */
+ NULL, /* I3_IPC_REPLY_TYPE_MARKS */
+ &got_bar_config, /* I3_IPC_REPLY_TYPE_BAR_CONFIG */
+ NULL, /* I3_IPC_REPLY_TYPE_VERSION */
+ NULL, /* I3_IPC_REPLY_TYPE_BINDING_MODES */
+ NULL, /* I3_IPC_REPLY_TYPE_CONFIG */
+ NULL, /* I3_IPC_REPLY_TYPE_TICK */
+ NULL, /* I3_IPC_REPLY_TYPE_SYNC */
};
/*
* Called, when a workspace event arrives (i.e. the user changed the workspace)
*
*/
-void got_workspace_event(char *event) {
+static void got_workspace_event(char *event) {
DLOG("Got workspace event!\n");
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
}
@@ -136,7 +141,7 @@ void got_workspace_event(char *event) {
* Called, when an output event arrives (i.e. the screen configuration changed)
*
*/
-void got_output_event(char *event) {
+static void got_output_event(char *event) {
DLOG("Got output event!\n");
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
if (!config.disable_ws) {
@@ -148,7 +153,7 @@ void got_output_event(char *event) {
* Called, when a mode event arrives (i3 changed binding mode).
*
*/
-void got_mode_event(char *event) {
+static void got_mode_event(char *event) {
DLOG("Got mode event!\n");
parse_mode_json(event);
draw_bars(false);
@@ -158,7 +163,7 @@ void got_mode_event(char *event) {
* Called, when a barconfig_update event arrives (i.e. i3 changed the bar hidden_state or mode)
*
*/
-void got_bar_config_update(char *event) {
+static void got_bar_config_update(char *event) {
/* check whether this affect this bar instance by checking the bar_id */
char *expected_id;
sasprintf(&expected_id, "\"id\":\"%s\"", config.bar_id);
@@ -208,7 +213,7 @@ handler_t event_handlers[] = {
* Called, when we get a message from i3
*
*/
-void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
+static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
DLOG("Got data!\n");
int fd = watcher->fd;
@@ -273,8 +278,8 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
buffer[size] = '\0';
/* And call the callback (indexed by the type) */
- if (type & (1 << 31)) {
- type ^= 1 << 31;
+ if (type & (1UL << 31)) {
+ type ^= 1UL << 31;
event_handlers[type](buffer);
} else {
if (reply_handlers[type])
@@ -304,7 +309,7 @@ int i3_send_msg(uint32_t type, const char *payload) {
char *buffer = smalloc(to_write);
char *walk = buffer;
- strncpy(buffer, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC));
+ memcpy(buffer, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC));
walk += strlen(I3_IPC_MAGIC);
memcpy(walk, &len, sizeof(uint32_t));
walk += sizeof(uint32_t);
diff --git a/i3bar/src/main.c b/i3bar/src/main.c
index 069803d4..a818dd97 100644
--- a/i3bar/src/main.c
+++ b/i3bar/src/main.c
@@ -44,7 +44,7 @@ void debuglog(char *fmt, ...) {
* Glob path, i.e. expand ~
*
*/
-char *expand_path(char *path) {
+static char *expand_path(char *path) {
static glob_t globbuf;
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) {
ELOG("glob() failed\n");
@@ -55,13 +55,14 @@ char *expand_path(char *path) {
return result;
}
-void print_usage(char *elf_name) {
+static void print_usage(char *elf_name) {
printf("Usage: %s -b bar_id [-s sock_path] [-h] [-v]\n", elf_name);
printf("\n");
printf("-b, --bar_id \tBar ID for which to get the configuration\n");
printf("-s, --socket \tConnect to i3 via \n");
printf("-h, --help Display this help message and exit\n");
printf("-v, --version Display version number and exit\n");
+ printf("-V, --verbose Enable verbose mode\n");
printf("\n");
printf(" PLEASE NOTE that i3bar will be automatically started by i3\n"
" as soon as there is a 'bar' configuration block in your\n"
@@ -75,7 +76,7 @@ void print_usage(char *elf_name) {
* in main() with that
*
*/
-void sig_cb(struct ev_loop *loop, ev_signal *watcher, int revents) {
+static void sig_cb(struct ev_loop *loop, ev_signal *watcher, int revents) {
switch (watcher->signum) {
case SIGTERM:
DLOG("Got a SIGTERM, stopping\n");
@@ -106,9 +107,10 @@ int main(int argc, char **argv) {
{"bar_id", required_argument, 0, 'b'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'},
+ {"verbose", no_argument, 0, 'V'},
{NULL, 0, 0, 0}};
- while ((opt = getopt_long(argc, argv, "b:s:hv", long_opt, &option_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, "b:s:hvV", long_opt, &option_index)) != -1) {
switch (opt) {
case 's':
socket_path = expand_path(optarg);
@@ -120,6 +122,9 @@ int main(int argc, char **argv) {
case 'b':
config.bar_id = sstrdup(optarg);
break;
+ case 'V':
+ config.verbose = true;
+ break;
default:
print_usage(argv[0]);
exit(EXIT_SUCCESS);
diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c
index 23324989..7285d150 100644
--- a/i3bar/src/workspaces.c
+++ b/i3bar/src/workspaces.c
@@ -106,8 +106,8 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
const char *ws_name = (const char *)val;
params->workspaces_walk->canonical_name = sstrndup(ws_name, len);
- if (config.strip_ws_numbers && params->workspaces_walk->num >= 0) {
- /* Special case: strip off the workspace number */
+ if ((config.strip_ws_numbers || config.strip_ws_name) && params->workspaces_walk->num >= 0) {
+ /* Special case: strip off the workspace number/name */
static char ws_num[10];
snprintf(ws_num, sizeof(ws_num), "%d", params->workspaces_walk->num);
@@ -119,11 +119,14 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
if (offset && ws_name[offset] == ':')
offset += 1;
- /* Offset may be equal to length, in which case display the number */
- params->workspaces_walk->name = (offset < len
- ? i3string_from_markup_with_length(ws_name + offset, len - offset)
- : i3string_from_markup(ws_num));
-
+ if (config.strip_ws_numbers) {
+ /* Offset may be equal to length, in which case display the number */
+ params->workspaces_walk->name = (offset < len
+ ? i3string_from_markup_with_length(ws_name + offset, len - offset)
+ : i3string_from_markup(ws_num));
+ } else {
+ params->workspaces_walk->name = i3string_from_markup(ws_num);
+ }
} else {
/* Default case: just save the name */
params->workspaces_walk->name = i3string_from_markup_with_length(ws_name, len);
diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c
index 542c86c3..31ae08f0 100644
--- a/i3bar/src/xcb.c
+++ b/i3bar/src/xcb.c
@@ -79,7 +79,7 @@ int bar_height;
/* These are only relevant for XKB, which we only need for grabbing modifiers */
int xkb_base;
-int mod_pressed = 0;
+bool mod_pressed = 0;
/* Event watchers, to interact with the user */
ev_prepare *xcb_prep;
@@ -92,6 +92,9 @@ static mode binding;
/* Indicates whether a new binding mode was recently activated */
bool activated_mode = false;
+/* The output in which the tray should be displayed. */
+static i3_output *output_for_tray;
+
/* The parsed colors */
struct xcb_colors_t {
color_t bar_fg;
@@ -146,13 +149,13 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
return 0;
}
-uint32_t get_sep_offset(struct status_block *block) {
+static uint32_t get_sep_offset(struct status_block *block) {
if (!block->no_separator && block->sep_block_width > 0)
return block->sep_block_width / 2 + block->sep_block_width % 2;
return 0;
}
-int get_tray_width(struct tc_head *trayclients) {
+static int get_tray_width(struct tc_head *trayclients) {
trayclient *trayclient;
int tray_width = 0;
TAILQ_FOREACH_REVERSE(trayclient, trayclients, tc_head, tailq) {
@@ -193,7 +196,7 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
}
}
-uint32_t predict_statusline_length(bool use_short_text) {
+static uint32_t predict_statusline_length(bool use_short_text) {
uint32_t width = 0;
struct status_block *block;
@@ -245,7 +248,7 @@ uint32_t predict_statusline_length(bool use_short_text) {
/*
* Redraws the statusline to the output's statusline_buffer
*/
-void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_colors, bool use_short_text) {
+static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_colors, bool use_short_text) {
struct status_block *block;
color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
@@ -330,7 +333,7 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color
* Hides all bars (unmaps them)
*
*/
-void hide_bars(void) {
+static void hide_bars(void) {
if ((config.hide_on_modifier == M_DOCK) || (config.hidden_state == S_SHOW && config.hide_on_modifier == M_HIDE)) {
return;
}
@@ -349,7 +352,7 @@ void hide_bars(void) {
* Unhides all bars (maps them)
*
*/
-void unhide_bars(void) {
+static void unhide_bars(void) {
if (config.hide_on_modifier != M_HIDE) {
return;
}
@@ -457,7 +460,7 @@ static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_relea
* wheel was used and change the workspace appropriately
*
*/
-void handle_button(xcb_button_press_event_t *event) {
+static void handle_button(xcb_button_press_event_t *event) {
/* Determine, which bar was clicked */
i3_output *walk;
xcb_window_t bar = event->event;
@@ -500,13 +503,12 @@ void handle_button(xcb_button_press_event_t *event) {
/* If the child asked for click events,
* check if a status block has been clicked. */
int tray_width = get_tray_width(walk->trayclients);
- int block_x = 0, last_block_x;
- int offset = walk->rect.w - walk->statusline_width - tray_width - logical_px(sb_hoff_px);
+ int last_block_x = 0;
+ int offset = walk->rect.w - walk->statusline_width - tray_width - logical_px((tray_width > 0) * sb_hoff_px);
int32_t statusline_x = x - offset;
if (statusline_x >= 0 && statusline_x < walk->statusline_width) {
struct status_block *block;
- int sep_offset_remainder = 0;
TAILQ_FOREACH(block, &statusline_head, blocks) {
i3String *text = block->full_text;
@@ -519,16 +521,15 @@ void handle_button(xcb_button_press_event_t *event) {
if (i3string_get_num_bytes(text) == 0)
continue;
- last_block_x = block_x;
- block_x += render->width + render->x_offset + render->x_append + get_sep_offset(block) + sep_offset_remainder;
-
- if (statusline_x <= block_x && statusline_x >= last_block_x) {
+ const int relative_x = statusline_x - last_block_x;
+ if (relative_x >= 0 && (uint32_t)relative_x <= render->width) {
send_block_clicked(event->detail, block->name, block->instance,
- event->root_x, event->root_y, statusline_x - last_block_x, event->event_y, block_x - last_block_x, bar_height);
+ event->root_x, event->root_y, relative_x, event->event_y, render->width, bar_height,
+ event->state);
return;
}
- sep_offset_remainder = block->sep_block_width - get_sep_offset(block);
+ last_block_x += render->width + render->x_append + render->x_offset + block->sep_block_width;
}
}
}
@@ -604,7 +605,7 @@ void handle_button(xcb_button_press_event_t *event) {
const size_t len = namelen + strlen("workspace \"\"") + 1;
char *buffer = scalloc(len + num_quotes, 1);
- strncpy(buffer, "workspace \"", strlen("workspace \""));
+ memcpy(buffer, "workspace \"", strlen("workspace \""));
size_t inpos, outpos;
for (inpos = 0, outpos = strlen("workspace \"");
inpos < namelen;
@@ -694,21 +695,12 @@ static void handle_client_message(xcb_client_message_event_t *event) {
if (event->type == atoms[I3_SYNC]) {
xcb_window_t window = event->data.data32[0];
uint32_t rnd = event->data.data32[1];
- DLOG("[i3 sync protocol] Forwarding random value %d, X11 window 0x%08x to i3\n", rnd, window);
-
- void *reply = scalloc(32, 1);
- xcb_client_message_event_t *ev = reply;
-
- ev->response_type = XCB_CLIENT_MESSAGE;
- ev->window = window;
- ev->type = atoms[I3_SYNC];
- ev->format = 32;
- ev->data.data32[0] = window;
- ev->data.data32[1] = rnd;
-
- xcb_send_event(conn, false, xcb_root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)ev);
- xcb_flush(conn);
- free(reply);
+ /* Forward the request to i3 via the IPC interface so that all pending
+ * IPC messages are guaranteed to be handled. */
+ char *payload = NULL;
+ sasprintf(&payload, "{\"rnd\":%d, \"window\":%d}", rnd, window);
+ i3_send_msg(I3_IPC_MESSAGE_TYPE_SYNC, payload);
+ free(payload);
} else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
event->format == 32) {
DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
@@ -770,58 +762,16 @@ static void handle_client_message(xcb_client_message_event_t *event) {
}
DLOG("X window %08x requested docking\n", client);
- i3_output *output = NULL;
- i3_output *walk = NULL;
- tray_output_t *tray_output = NULL;
- /* We need to iterate through the tray_output assignments first in
- * order to prioritize them. Otherwise, if this bar manages two
- * outputs and both are assigned as tray_output as well, the first
- * output in our list would receive the tray rather than the first
- * one defined via tray_output. */
- TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) {
- SLIST_FOREACH(walk, outputs, slist) {
- if (!walk->active)
- continue;
- if (strcasecmp(walk->name, tray_output->output) == 0) {
- DLOG("Found tray_output assignment for output %s.\n", walk->name);
- output = walk;
- break;
- }
-
- if (walk->primary && strcasecmp("primary", tray_output->output) == 0) {
- DLOG("Found tray_output assignment on primary output %s.\n", walk->name);
- output = walk;
- break;
- }
- }
-
- /* If we found an output, we're done. */
- if (output != NULL)
- break;
- }
-
- /* If no tray_output has been specified, we fall back to the first
- * available output. */
- if (output == NULL && TAILQ_EMPTY(&(config.tray_outputs))) {
- SLIST_FOREACH(walk, outputs, slist) {
- if (!walk->active)
- continue;
- DLOG("Falling back to output %s because no primary output is configured\n", walk->name);
- output = walk;
- break;
- }
- }
-
- if (output == NULL) {
- ELOG("No output found\n");
+ if (output_for_tray == NULL) {
+ ELOG("No output found for tray\n");
return;
}
xcb_void_cookie_t rcookie = xcb_reparent_window(xcb_connection,
client,
- output->bar.id,
- output->rect.w - icon_size - logical_px(config.tray_padding),
+ output_for_tray->bar.id,
+ output_for_tray->rect.w - icon_size - logical_px(config.tray_padding),
logical_px(config.tray_padding));
if (xcb_request_failed(rcookie, "Could not reparent window. Maybe it is using an incorrect depth/visual?"))
return;
@@ -848,7 +798,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
ev->format = 32;
ev->data.data32[0] = XCB_CURRENT_TIME;
ev->data.data32[1] = XEMBED_EMBEDDED_NOTIFY;
- ev->data.data32[2] = output->bar.id;
+ ev->data.data32[2] = output_for_tray->bar.id;
ev->data.data32[3] = xe_version;
xcb_send_event(xcb_connection,
0,
@@ -868,7 +818,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
tc->win = client;
tc->xe_version = xe_version;
tc->mapped = false;
- TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
+ TAILQ_INSERT_TAIL(output_for_tray->trayclients, tc, tailq);
if (map_it) {
DLOG("Mapping dock client\n");
@@ -1092,7 +1042,7 @@ static void handle_resize_request(xcb_resize_request_event_t *event) {
* events from X11, handle them, then flush our outgoing queue.
*
*/
-void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
+static void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
xcb_generic_event_t *event;
if (xcb_connection_has_error(xcb_connection)) {
@@ -1117,49 +1067,18 @@ void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
DLOG("received an xkb event\n");
xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event;
- if (state->xkbType == XCB_XKB_STATE_NOTIFY && config.modifier != XCB_NONE) {
- int modstate = state->mods & config.modifier;
-
-#define DLOGMOD(modmask, status) \
- do { \
- switch (modmask) { \
- case ShiftMask: \
- DLOG("ShiftMask got " #status "!\n"); \
- break; \
- case ControlMask: \
- DLOG("ControlMask got " #status "!\n"); \
- break; \
- case Mod1Mask: \
- DLOG("Mod1Mask got " #status "!\n"); \
- break; \
- case Mod2Mask: \
- DLOG("Mod2Mask got " #status "!\n"); \
- break; \
- case Mod3Mask: \
- DLOG("Mod3Mask got " #status "!\n"); \
- break; \
- case Mod4Mask: \
- DLOG("Mod4Mask got " #status "!\n"); \
- break; \
- case Mod5Mask: \
- DLOG("Mod5Mask got " #status "!\n"); \
- break; \
- } \
- } while (0)
-
- if (modstate != mod_pressed) {
- if (modstate == 0) {
- DLOGMOD(config.modifier, released);
- if (!activated_mode)
- hide_bars();
- } else {
- DLOGMOD(config.modifier, pressed);
+ const uint32_t mod = (config.modifier & 0xFFFF);
+ const bool new_mod_pressed = (mod != 0 && (state->mods & mod) == mod);
+ if (new_mod_pressed != mod_pressed) {
+ mod_pressed = new_mod_pressed;
+ if (state->xkbType == XCB_XKB_STATE_NOTIFY && config.modifier != XCB_NONE) {
+ if (mod_pressed) {
activated_mode = false;
unhide_bars();
+ } else if (!activated_mode) {
+ hide_bars();
}
- mod_pressed = modstate;
}
-#undef DLOGMOD
}
free(event);
@@ -1223,7 +1142,7 @@ void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
* are triggered
*
*/
-void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+static void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
}
/*
@@ -1231,7 +1150,7 @@ void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
* depend on 'config'.
*
*/
-char *init_xcb_early() {
+char *init_xcb_early(void) {
/* FIXME: xcb_connect leaks memory */
xcb_connection = xcb_connect(NULL, &screen);
if (xcb_connection_has_error(xcb_connection)) {
@@ -1294,7 +1213,7 @@ char *init_xcb_early() {
* in xcb.
*
*/
-void register_xkb_keyevents() {
+static void register_xkb_keyevents(void) {
const xcb_query_extension_reply_t *extreply;
extreply = xcb_get_extension_data(conn, &xcb_xkb_id);
if (!extreply->present) {
@@ -1318,7 +1237,7 @@ void register_xkb_keyevents() {
* Deregister from xkb keyevents.
*
*/
-void deregister_xkb_keyevents() {
+static void deregister_xkb_keyevents(void) {
xcb_xkb_select_events(conn,
XCB_XKB_ID_USE_CORE_KBD,
0,
@@ -1384,7 +1303,7 @@ static void send_tray_clientmessage(void) {
* atom. Afterwards, tray clients will send ClientMessages to our window.
*
*/
-void init_tray(void) {
+static void init_tray(void) {
DLOG("Initializing system tray functionality\n");
/* request the tray manager atom for the X11 display we are running on */
char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
@@ -1613,7 +1532,7 @@ void destroy_window(i3_output *output) {
/* Strut partial tells i3 where to reserve space for i3bar. This is determined
* by the `position` bar config directive. */
-xcb_void_cookie_t config_strut_partial(i3_output *output) {
+static xcb_void_cookie_t config_strut_partial(i3_output *output) {
/* A local struct to save the strut_partial property */
struct {
uint32_t left;
@@ -1655,6 +1574,56 @@ xcb_void_cookie_t config_strut_partial(i3_output *output) {
&strut_partial);
}
+/*
+ * Returns the output which should hold the tray, if one exists.
+ *
+ * An output is returned in these scenarios:
+ * 1. A specific output was listed in tray_outputs which is also in the list
+ * of outputs managed by this bar.
+ * 2. No tray_output directive was specified. In this case, we use the first
+ * available output.
+ * 3. 'tray_output primary' was specified. In this case we use the primary
+ * output.
+ *
+ * Three scenarios in which we specifically don't want to use a tray:
+ * 1. 'tray_output none' was specified.
+ * 2. A specific output was listed as a tray_output, but is not one of the
+ * outputs managed by this bar. For example, consider tray_outputs == [VGA-1],
+ * but outputs == [HDMI-1].
+ * 3. 'tray_output primary' was specified and no output in the list is
+ * primary.
+ */
+static i3_output *get_tray_output(void) {
+ i3_output *output = NULL;
+ if (TAILQ_EMPTY(&(config.tray_outputs))) {
+ /* No tray_output specified, use first active output. */
+ SLIST_FOREACH(output, outputs, slist) {
+ if (output->active) {
+ return output;
+ }
+ }
+ return NULL;
+ } else if (strcasecmp(TAILQ_FIRST(&(config.tray_outputs))->output, "none") == 0) {
+ /* Check for "tray_output none" */
+ return NULL;
+ }
+
+ /* If one or more tray_output assignments were specified, we ensure that at
+ * least one of them is actually an output managed by this instance. */
+ tray_output_t *tray_output;
+ TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) {
+ SLIST_FOREACH(output, outputs, slist) {
+ if (output->active &&
+ (strcasecmp(output->name, tray_output->output) == 0 ||
+ (strcasecmp(tray_output->output, "primary") == 0 && output->primary))) {
+ return output;
+ }
+ }
+ }
+
+ return NULL;
+}
+
/*
* Reconfigure all bars and create new bars for recently activated outputs
*
@@ -1662,7 +1631,6 @@ xcb_void_cookie_t config_strut_partial(i3_output *output) {
void reconfig_windows(bool redraw_bars) {
uint32_t mask;
uint32_t values[6];
- static bool tray_configured = false;
i3_output *walk;
SLIST_FOREACH(walk, outputs, slist) {
@@ -1790,58 +1758,6 @@ void reconfig_windows(bool redraw_bars) {
exit(EXIT_FAILURE);
}
- /* Unless "tray_output none" was specified, we need to initialize the tray. */
- bool no_tray = false;
- if (!(TAILQ_EMPTY(&(config.tray_outputs)))) {
- no_tray = strcasecmp(TAILQ_FIRST(&(config.tray_outputs))->output, "none") == 0;
- }
-
- /*
- * There are three scenarios in which we need to initialize the tray:
- * 1. A specific output was listed in tray_outputs which is also
- * in the list of outputs managed by this bar.
- * 2. No tray_output directive was specified. In this case, we
- * use the first available output.
- * 3. 'tray_output primary' was specified. In this case we use the
- * primary output.
- *
- * Three scenarios in which we specifically don't want to
- * initialize the tray are:
- * 1. 'tray_output none' was specified.
- * 2. A specific output was listed as a tray_output, but is not
- * one of the outputs managed by this bar. For example, consider
- * tray_outputs == [VGA-1], but outputs == [HDMI-1].
- * 3. 'tray_output primary' was specified and no output in the list
- * is primary.
- */
- if (!tray_configured && !no_tray) {
- /* If no tray_output was specified, we go ahead and initialize the tray as
- * we will be using the first available output. */
- if (TAILQ_EMPTY(&(config.tray_outputs))) {
- init_tray();
- }
-
- /* If one or more tray_output assignments were specified, we ensure that at least one of
- * them is actually an output managed by this instance. */
- tray_output_t *tray_output;
- TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) {
- i3_output *output;
- bool found = false;
- SLIST_FOREACH(output, outputs, slist) {
- if (strcasecmp(output->name, tray_output->output) == 0 ||
- (strcasecmp(tray_output->output, "primary") == 0 && output->primary)) {
- found = true;
- init_tray();
- break;
- }
- }
-
- if (found)
- break;
- }
-
- tray_configured = true;
- }
} else {
/* We already have a bar, so we just reconfigure it */
mask = XCB_CONFIG_WINDOW_X |
@@ -1935,6 +1851,19 @@ void reconfig_windows(bool redraw_bars) {
}
}
}
+
+ /* Finally, check if we want to initialize the tray or destroy the selection
+ * window. The result of get_tray_output() is cached. */
+ output_for_tray = get_tray_output();
+ if (output_for_tray) {
+ if (selwin == XCB_NONE) {
+ init_tray();
+ }
+ } else if (selwin != XCB_NONE) {
+ DLOG("Destroying tray selection window\n");
+ xcb_destroy_window(xcb_connection, selwin);
+ selwin = XCB_NONE;
+ }
}
/*
@@ -2048,7 +1977,8 @@ void draw_bars(bool unhide) {
DLOG("Printing statusline!\n");
int tray_width = get_tray_width(outputs_walk->trayclients);
- uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - 2 * logical_px(sb_hoff_px);
+ uint32_t hoff = logical_px(((workspace_width > 0) + (tray_width > 0)) * sb_hoff_px);
+ uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - hoff;
uint32_t clip_left = 0;
uint32_t statusline_width = full_statusline_width;
bool use_short_text = false;
@@ -2062,7 +1992,7 @@ void draw_bars(bool unhide) {
}
int16_t visible_statusline_width = MIN(statusline_width, max_statusline_width);
- int x_dest = outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width;
+ int x_dest = outputs_walk->rect.w - tray_width - logical_px((tray_width > 0) * sb_hoff_px) - visible_statusline_width;
draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text);
draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,
@@ -2110,5 +2040,4 @@ void set_current_mode(struct mode *current) {
I3STRING_FREE(binding.name);
binding = *current;
activated_mode = binding.name != NULL;
- return;
}
diff --git a/include/all.h b/include/all.h
index ecc875d0..e93b066b 100644
--- a/include/all.h
+++ b/include/all.h
@@ -82,4 +82,5 @@
#include "fake_outputs.h"
#include "display_version.h"
#include "restore_layout.h"
+#include "sync.h"
#include "main.h"
diff --git a/include/atoms_NET_SUPPORTED.xmacro b/include/atoms_NET_SUPPORTED.xmacro
index a7b9676d..a81948a9 100644
--- a/include/atoms_NET_SUPPORTED.xmacro
+++ b/include/atoms_NET_SUPPORTED.xmacro
@@ -8,6 +8,7 @@ xmacro(_NET_WM_STATE_FULLSCREEN)
xmacro(_NET_WM_STATE_DEMANDS_ATTENTION)
xmacro(_NET_WM_STATE_MODAL)
xmacro(_NET_WM_STATE_HIDDEN)
+xmacro(_NET_WM_STATE_FOCUSED)
xmacro(_NET_WM_STATE)
xmacro(_NET_WM_WINDOW_TYPE)
xmacro(_NET_WM_WINDOW_TYPE_NORMAL)
diff --git a/include/atoms_rest.xmacro b/include/atoms_rest.xmacro
index d461dc08..b65a81d8 100644
--- a/include/atoms_rest.xmacro
+++ b/include/atoms_rest.xmacro
@@ -17,3 +17,4 @@ xmacro(I3_FLOATING_WINDOW)
xmacro(_NET_REQUEST_FRAME_EXTENTS)
xmacro(_NET_FRAME_EXTENTS)
xmacro(_MOTIF_WM_HINTS)
+xmacro(WM_CHANGE_STATE)
diff --git a/include/commands.h b/include/commands.h
index 1057f021..0137460f 100644
--- a/include/commands.h
+++ b/include/commands.h
@@ -216,7 +216,7 @@ void cmd_sticky(I3_CMD, const char *action);
* Implementation of 'move [ [px]]'.
*
*/
-void cmd_move_direction(I3_CMD, const char *direction, long move_px);
+void cmd_move_direction(I3_CMD, const char *direction_str, long move_px);
/**
* Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
@@ -264,7 +264,7 @@ void cmd_focus_output(I3_CMD, const char *name);
* Implementation of 'move [window|container] [to] [absolute] position [px] [px]
*
*/
-void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y);
+void cmd_move_window_to_position(I3_CMD, long x, long y);
/**
* Implementation of 'move [window|container] [to] [absolute] position center
@@ -314,13 +314,13 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name);
*/
void cmd_bar(I3_CMD, const char *bar_type, const char *bar_value, const char *bar_id);
-/*
+/**
* Implementation of 'shmlog |toggle|on|off'
*
*/
void cmd_shmlog(I3_CMD, const char *argument);
-/*
+/**
* Implementation of 'debuglog toggle|on|off'
*
*/
diff --git a/include/commands_parser.h b/include/commands_parser.h
index 88b3f6d0..b65ae93f 100644
--- a/include/commands_parser.h
+++ b/include/commands_parser.h
@@ -13,7 +13,7 @@
#include
-/*
+/**
* Holds an intermediate represenation 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.
diff --git a/include/con.h b/include/con.h
index 58123a87..2c991b0c 100644
--- a/include/con.h
+++ b/include/con.h
@@ -20,7 +20,8 @@
*/
Con *con_new_skeleton(Con *parent, i3Window *window);
-/* A wrapper for con_new_skeleton, to retain the old con_new behaviour
+/**
+ * A wrapper for con_new_skeleton, to retain the old con_new behaviour
*
*/
Con *con_new(Con *parent, i3Window *window);
@@ -120,6 +121,14 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation);
*/
Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode);
+/**
+ * Returns the fullscreen node that covers the given workspace if it exists.
+ * This is either a CF_GLOBAL fullscreen container anywhere or a CF_OUTPUT
+ * fullscreen container in the workspace.
+ *
+ */
+Con *con_get_fullscreen_covering_ws(Con *ws);
+
/**
* Returns true if the container is internal, such as __i3_scratch
*
@@ -212,7 +221,7 @@ void con_mark_toggle(Con *con, const char *mark, mark_mode_t mode);
*/
void con_mark(Con *con, const char *mark, mark_mode_t mode);
-/*
+/**
* Removes marks from containers.
* If con is NULL, all containers are considered.
* If name is NULL, this removes all existing marks.
@@ -394,7 +403,7 @@ Con *con_descend_focused(Con *con);
*/
Con *con_descend_tiling_focused(Con *con);
-/*
+/**
* Returns the leftmost, rightmost, etc. container in sub-tree. For example, if
* direction is D_LEFT, then we return the rightmost container and if direction
* is D_RIGHT, we return the leftmost container. This is because if we are
diff --git a/include/config_directives.h b/include/config_directives.h
index 187b550c..72b59ea2 100644
--- a/include/config_directives.h
+++ b/include/config_directives.h
@@ -56,12 +56,14 @@ CFGFUN(disable_randr15, const char *value);
CFGFUN(fake_outputs, const char *outputs);
CFGFUN(force_display_urgency_hint, const long duration_ms);
CFGFUN(focus_on_window_activation, const char *mode);
+CFGFUN(title_align, const char *alignment);
CFGFUN(show_marks, const char *value);
CFGFUN(hide_edge_borders, const char *borders);
CFGFUN(assign_output, const char *output);
CFGFUN(assign, const char *workspace, bool is_number);
CFGFUN(no_focus);
CFGFUN(ipc_socket, const char *path);
+CFGFUN(ipc_kill_timeout, const long timeout_ms);
CFGFUN(restart_state, const char *path);
CFGFUN(popup_during_fullscreen, const char *value);
CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border);
@@ -81,7 +83,7 @@ CFGFUN(bar_hidden_state, const char *hidden_state);
CFGFUN(bar_id, const char *bar_id);
CFGFUN(bar_output, const char *output);
CFGFUN(bar_verbose, const char *verbose);
-CFGFUN(bar_modifier, const char *modifier);
+CFGFUN(bar_modifier, const char *modifiers);
CFGFUN(bar_wheel_up_cmd, const char *command);
CFGFUN(bar_wheel_down_cmd, const char *command);
CFGFUN(bar_bindsym, const char *button, const char *release, const char *command);
@@ -96,5 +98,6 @@ CFGFUN(bar_status_command, const char *command);
CFGFUN(bar_binding_mode_indicator, const char *value);
CFGFUN(bar_workspace_buttons, const char *value);
CFGFUN(bar_strip_workspace_numbers, const char *value);
+CFGFUN(bar_strip_workspace_name, const char *value);
CFGFUN(bar_start);
CFGFUN(bar_finish);
diff --git a/include/config_parser.h b/include/config_parser.h
index ace4041d..009538f2 100644
--- a/include/config_parser.h
+++ b/include/config_parser.h
@@ -16,7 +16,7 @@
SLIST_HEAD(variables_head, Variable);
extern pid_t config_error_nagbar_pid;
-/*
+/**
* An intermediate reprsentation 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.
diff --git a/include/configuration.h b/include/configuration.h
index ac800159..6f55ac2a 100644
--- a/include/configuration.h
+++ b/include/configuration.h
@@ -201,6 +201,13 @@ struct Config {
* decoration. Marks starting with a "_" will be ignored either way. */
bool show_marks;
+ /** Title alignment options. */
+ enum {
+ ALIGN_LEFT,
+ ALIGN_CENTER,
+ ALIGN_RIGHT
+ } title_align;
+
/** The default border style for new windows. */
border_style_t default_border;
@@ -289,16 +296,7 @@ struct Barconfig {
S_SHOW = 1 } hidden_state;
/** Bar modifier (to show bar when in hide mode). */
- enum {
- M_NONE = 0,
- M_CONTROL = 1,
- M_SHIFT = 2,
- M_MOD1 = 3,
- M_MOD2 = 4,
- M_MOD3 = 5,
- M_MOD4 = 6,
- M_MOD5 = 7
- } modifier;
+ uint32_t modifier;
TAILQ_HEAD(bar_bindings_head, Barbinding)
bar_bindings;
@@ -331,6 +329,10 @@ struct Barconfig {
* 'strip_workspace_numbers yes'. */
bool strip_workspace_numbers;
+ /** Strip workspace name? Configuration option is
+ * 'strip_workspace_name yes'. */
+ bool strip_workspace_name;
+
/** Hide mode button? Configuration option is 'binding_mode_indicator no'
* but we invert the bool for the same reason as hide_workspace_buttons.*/
bool hide_binding_mode_indicator;
@@ -432,7 +434,7 @@ void ungrab_all_keys(xcb_connection_t *conn);
* Sends the current bar configuration as an event to all barconfig_update listeners.
*
*/
-void update_barconfig();
+void update_barconfig(void);
/**
* Kills the configerror i3-nagbar process, if any.
diff --git a/include/data.h b/include/data.h
index 69a46e46..f55e003d 100644
--- a/include/data.h
+++ b/include/data.h
@@ -477,6 +477,10 @@ struct Window {
int min_width;
int min_height;
+ /* Maximum size specified for the window. */
+ int max_width;
+ int max_height;
+
/* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */
double aspect_ratio;
};
@@ -573,7 +577,7 @@ struct Assignment {
/** the criteria to check if a window matches */
Match match;
- /** destination workspace/command, depending on the type */
+ /** destination workspace/command/output, depending on the type */
union {
char *command;
char *workspace;
diff --git a/include/ewmh.h b/include/ewmh.h
index 5844faa6..01ae67f9 100644
--- a/include/ewmh.h
+++ b/include/ewmh.h
@@ -83,6 +83,12 @@ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows);
*/
void ewmh_update_sticky(xcb_window_t window, bool sticky);
+/**
+ * Set or remove _NEW_WM_STATE_FOCUSED on the window.
+ *
+ */
+void ewmh_update_focused(xcb_window_t window, bool is_focused);
+
/**
* Set up the EWMH hints on the root window.
*
diff --git a/include/floating.h b/include/floating.h
index babfafc9..4382437b 100644
--- a/include/floating.h
+++ b/include/floating.h
@@ -143,7 +143,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
* outputs.
*
*/
-void floating_reposition(Con *con, Rect newrect);
+bool floating_reposition(Con *con, Rect newrect);
/**
* Sets size of the CT_FLOATING_CON to specified dimensions. Might limit the
diff --git a/include/i3/ipc.h b/include/i3/ipc.h
index 9e0280c9..884a0cf6 100644
--- a/include/i3/ipc.h
+++ b/include/i3/ipc.h
@@ -63,6 +63,9 @@ typedef struct i3_ipc_header {
/** Send a tick event to all subscribers. */
#define I3_IPC_MESSAGE_TYPE_SEND_TICK 10
+/** Trigger an i3 sync protocol message via IPC. */
+#define I3_IPC_MESSAGE_TYPE_SYNC 11
+
/*
* Messages from i3 to clients
*
@@ -78,12 +81,13 @@ typedef struct i3_ipc_header {
#define I3_IPC_REPLY_TYPE_BINDING_MODES 8
#define I3_IPC_REPLY_TYPE_CONFIG 9
#define I3_IPC_REPLY_TYPE_TICK 10
+#define I3_IPC_REPLY_TYPE_SYNC 11
/*
* Events from i3 to clients. Events have the first bit set high.
*
*/
-#define I3_IPC_EVENT_MASK (1 << 31)
+#define I3_IPC_EVENT_MASK (1UL << 31)
/* The workspace event will be triggered upon changes in the workspace list */
#define I3_IPC_EVENT_WORKSPACE (I3_IPC_EVENT_MASK | 0)
diff --git a/include/ipc.h b/include/ipc.h
index c6ad35c7..a1caea82 100644
--- a/include/ipc.h
+++ b/include/ipc.h
@@ -35,6 +35,11 @@ typedef struct ipc_client {
* event has been sent by i3. */
bool first_tick_sent;
+ struct ev_io *callback;
+ struct ev_timer *timeout;
+ uint8_t *buffer;
+ size_t buffer_size;
+
TAILQ_ENTRY(ipc_client)
clients;
} ipc_client;
@@ -124,3 +129,9 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig);
* For the binding events, we send the serialized binding struct.
*/
void ipc_send_binding_event(const char *event_type, Binding *bind);
+
+/**
+ * Set the maximum duration that we allow for a connection with an unwriteable
+ * socket.
+ */
+void ipc_set_kill_timeout(ev_tstamp new);
diff --git a/include/libi3.h b/include/libi3.h
index b7a1e2aa..790baba9 100644
--- a/include/libi3.h
+++ b/include/libi3.h
@@ -166,6 +166,14 @@ int sasprintf(char **strp, const char *fmt, ...);
*/
ssize_t writeall(int fd, const void *buf, size_t count);
+/**
+ * Like writeall, but instead of retrying upon EAGAIN (returned when a write
+ * would block), the function stops and returns the total number of bytes
+ * written so far.
+ *
+ */
+ssize_t writeall_nonblock(int fd, const void *buf, size_t count);
+
/**
* Safe-wrapper around writeall which exits if it returns -1 (meaning that
* write failed)
@@ -188,11 +196,11 @@ i3String *i3string_from_markup(const char *from_markup);
/**
* Build an i3String from an UTF-8 encoded string with fixed length.
- * To be used when no proper NUL-terminaison is available.
+ * To be used when no proper NULL-termination is available.
* Returns the newly-allocated i3String.
*
*/
-i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes);
+i3String *i3string_from_utf8_with_length(const char *from_utf8, ssize_t num_bytes);
/**
* Build an i3String from an UTF-8 encoded string in Pango markup with fixed
@@ -312,6 +320,11 @@ int ipc_recv_message(int sockfd, uint32_t *message_type,
*/
void fake_configure_notify(xcb_connection_t *conn, xcb_rectangle_t r, xcb_window_t window, int border_width);
+#define HAS_G_UTF8_MAKE_VALID GLIB_CHECK_VERSION(2, 52, 0)
+#if !HAS_G_UTF8_MAKE_VALID
+gchar *g_utf8_make_valid(const gchar *str, gssize len);
+#endif
+
/**
* Returns the colorpixel to use for the given hex color (think of HTML). Only
* works for true-color (vast majority of cases) at the moment, avoiding a
@@ -330,7 +343,7 @@ uint32_t get_colorpixel(const char *hex) __attribute__((const));
#if defined(__APPLE__)
-/*
+/**
* Taken from FreeBSD
* Returns a pointer to a new string which is a duplicate of the
* string, but only copies at most n characters.
@@ -459,7 +472,7 @@ xcb_visualtype_t *get_visualtype(xcb_screen_t *screen);
* release version), based on the git version number.
*
*/
-bool is_debug_build() __attribute__((const));
+bool is_debug_build(void) __attribute__((const));
/**
* Returns the name of a temporary file with the specified prefix.
@@ -506,11 +519,11 @@ int logical_px(const int logical);
char *resolve_tilde(const char *path);
/**
- * Get the path of the first configuration file found. If override_configpath
- * is specified, that path is returned and saved for further calls. Otherwise,
- * checks the home directory first, then the system directory first, always
- * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
- * $XDG_CONFIG_DIRS)
+ * Get the path of the first configuration file found. If override_configpath is
+ * specified, that path is returned and saved for further calls. Otherwise,
+ * checks the home directory first, then the system directory, always taking
+ * into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
+ * $XDG_CONFIG_DIRS).
*
*/
char *get_config_path(const char *override_configpath, bool use_system_paths);
diff --git a/include/match.h b/include/match.h
index 4ff8c485..043c3a8f 100644
--- a/include/match.h
+++ b/include/match.h
@@ -15,7 +15,7 @@
#include
-/*
+/**
* Initializes the Match data structure. This function is necessary because the
* members representing boolean values (like dock) need to be initialized with
* -1 instead of 0.
diff --git a/include/move.h b/include/move.h
index 64b12b80..df644a6b 100644
--- a/include/move.h
+++ b/include/move.h
@@ -17,3 +17,13 @@
*
*/
void tree_move(Con *con, int direction);
+
+typedef enum { BEFORE,
+ AFTER } position_t;
+
+/**
+ * This function detaches 'con' from its parent and inserts it either before or
+ * after 'target'.
+ *
+ */
+void insert_con_into(Con *con, Con *target, position_t position);
diff --git a/include/output.h b/include/output.h
index 31084da1..a2ad97b0 100644
--- a/include/output.h
+++ b/include/output.h
@@ -40,5 +40,12 @@ Output *get_output_for_con(Con *con);
* Iterates over all outputs and pushes sticky windows to the currently visible
* workspace on that output.
*
+ * old_focus is used to determine if a sticky window is going to be focused.
+ * old_focus might be different than the currently focused container because the
+ * caller might need to temporarily change the focus and then call
+ * output_push_sticky_windows. For example, workspace_show needs to set focus to
+ * one of its descendants first, then call output_push_sticky_windows that
+ * should focus a sticky window if it was the focused in the previous workspace.
+ *
*/
-void output_push_sticky_windows(Con *to_focus);
+void output_push_sticky_windows(Con *old_focus);
diff --git a/include/randr.h b/include/randr.h
index bfbfd5a9..ec533a28 100644
--- a/include/randr.h
+++ b/include/randr.h
@@ -88,6 +88,14 @@ Output *get_output_by_name(const char *name, const bool require_active);
*/
Output *get_output_containing(unsigned int x, unsigned int y);
+/**
+ * Returns the active output which contains the midpoint of the given rect. If
+ * such an output doesn't exist, returns the output which contains most of the
+ * rectangle or NULL if there is no output which intersects with it.
+ *
+ */
+Output *get_output_from_rect(Rect rect);
+
/**
* Returns the active output which spans exactly the area specified by
* rect or NULL if there is no output like this.
@@ -95,15 +103,14 @@ Output *get_output_containing(unsigned int x, unsigned int y);
*/
Output *get_output_with_dimensions(Rect rect);
-/*
- * In contained_by_output, we check if any active output contains part of the container.
+/**
+ * In output_containing_rect, we check if any active output contains part of the container.
* We do this by checking if the output rect is intersected by the Rect.
* This is the 2-dimensional counterpart of get_output_containing.
- * Since we don't actually need the outputs intersected by the given Rect (There could
- * be many), we just return true or false for convenience.
+ * Returns the output with the maximum intersecting area.
*
*/
-bool contained_by_output(Rect rect);
+Output *output_containing_rect(Rect rect);
/**
* Gets the output which is the next one in the given direction.
@@ -130,7 +137,7 @@ Output *get_output_next(direction_t direction, Output *current, output_close_far
*/
Output *get_output_next_wrap(direction_t direction, Output *current);
-/*
+/**
* Creates an output covering the root window.
*
*/
diff --git a/include/render.h b/include/render.h
index 750b7d31..2b2c8dad 100644
--- a/include/render.h
+++ b/include/render.h
@@ -12,7 +12,10 @@
#include
-/* This is used to keep a state to pass around when rendering a con in render_con(). */
+/**
+ * This is used to keep a state to pass around when rendering a con in render_con().
+ *
+ */
typedef struct render_params {
/* A copy of the coordinates of the container which is being rendered. */
int x;
@@ -39,7 +42,8 @@ typedef struct render_params {
*/
void render_con(Con *con, bool render_fullscreen);
-/*
+/**
* Returns the height for the decorations
+ *
*/
int render_deco_height(void);
diff --git a/include/resize.h b/include/resize.h
index 38634156..72dffc0f 100644
--- a/include/resize.h
+++ b/include/resize.h
@@ -13,4 +13,27 @@
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides);
-int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
+void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
+
+/**
+ * Resize the two given containers using the given amount of pixels or
+ * percentage points. One of the two needs to be 0. A positive amount means
+ * growing the first container while a negative means shrinking it.
+ * Returns false when the resize would result in one of the two containers
+ * having less than 1 pixel of size.
+ *
+ */
+bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt);
+
+/**
+ * Calculate the minimum percent needed for the given container to be at least 1
+ * pixel.
+ *
+ */
+double percent_for_1px(Con *con);
+
+/**
+ * Calculate the given container's new percent given a change in pixels.
+ *
+ */
+double px_resize_to_percent(Con *con, int px_diff);
diff --git a/include/scratchpad.h b/include/scratchpad.h
index 241653c0..b24ffc08 100644
--- a/include/scratchpad.h
+++ b/include/scratchpad.h
@@ -29,7 +29,7 @@ void scratchpad_move(Con *con);
* can press the same key to quickly look something up).
*
*/
-void scratchpad_show(Con *con);
+bool scratchpad_show(Con *con);
/**
* When starting i3 initially (and after each change to the connected outputs),
diff --git a/include/shmlog.h b/include/shmlog.h
index b90211ef..dc8081f1 100644
--- a/include/shmlog.h
+++ b/include/shmlog.h
@@ -20,7 +20,7 @@
/* Default shmlog size if not set by user. */
extern const int default_shmlog_size;
-/*
+/**
* Header of the shmlog file. Used by i3/src/log.c and i3/i3-dump-log/main.c.
*
*/
diff --git a/include/startup.h b/include/startup.h
index 5da221e1..feece575 100644
--- a/include/startup.h
+++ b/include/startup.h
@@ -21,7 +21,7 @@
* Starts the given application by passing it through a shell. We use double
* fork to avoid zombie processes. As the started application’s parent exits
* (immediately), the application is reparented to init (process-id 1), which
- * correctly handles childs, so we don’t have to do it :-).
+ * correctly handles children, so we don’t have to do it :-).
*
* The shell used to start applications is the system's bourne shell (i.e.,
* /bin/sh).
diff --git a/include/sync.h b/include/sync.h
new file mode 100644
index 00000000..e726f99e
--- /dev/null
+++ b/include/sync.h
@@ -0,0 +1,14 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * sync.c: i3 sync protocol: https://i3wm.org/docs/testsuite.html#i3_sync
+ *
+ */
+#pragma once
+
+#include
+
+void sync_respond(xcb_window_t window, uint32_t rnd);
diff --git a/include/tree.h b/include/tree.h
index 62461def..41a63036 100644
--- a/include/tree.h
+++ b/include/tree.h
@@ -78,7 +78,7 @@ void tree_next(char way, orientation_t orientation);
* container) and focus should be set there.
*
*/
-bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus);
+bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent);
/**
* Loads tree from ~/.i3/_restart.json (used for in-place restarts).
diff --git a/include/util.h b/include/util.h
index 3547d8d7..d08ac69d 100644
--- a/include/util.h
+++ b/include/util.h
@@ -25,9 +25,6 @@
#define STARTS_WITH(string, needle) (strncasecmp((string), (needle), strlen((needle))) == 0)
#define CIRCLEQ_NEXT_OR_NULL(head, elm, field) (CIRCLEQ_NEXT(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_NEXT(elm, field) : NULL)
#define CIRCLEQ_PREV_OR_NULL(head, elm, field) (CIRCLEQ_PREV(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_PREV(elm, field) : NULL)
-#define FOR_TABLE(workspace) \
- for (int cols = 0; cols < (workspace)->cols; cols++) \
- for (int rows = 0; rows < (workspace)->rows; rows++)
#define NODES_FOREACH(head) \
for (Con *child = (Con *)-1; (child == (Con *)-1) && ((child = 0), true);) \
@@ -128,7 +125,7 @@ void i3_restart(bool forget_layout);
#if defined(__OpenBSD__) || defined(__APPLE__)
-/*
+/**
* Taken from FreeBSD
* Find the first occurrence of the byte string s in byte string l.
*
@@ -177,3 +174,9 @@ bool parse_long(const char *str, long *out, int base);
*
*/
ssize_t slurp(const char *path, char **buf);
+
+/**
+ * Convert a direction to its corresponding orientation.
+ *
+ */
+orientation_t orientation_from_direction(direction_t direction);
diff --git a/include/workspace.h b/include/workspace.h
index 8d109e9e..28d9eb66 100644
--- a/include/workspace.h
+++ b/include/workspace.h
@@ -24,6 +24,27 @@
#define NET_WM_DESKTOP_NONE 0xFFFFFFF0
#define NET_WM_DESKTOP_ALL 0xFFFFFFFF
+/**
+ * Returns the workspace with the given name or NULL if such a workspace does
+ * not exist.
+ *
+ */
+Con *get_existing_workspace_by_name(const char *name);
+
+/**
+ * Returns the workspace with the given number or NULL if such a workspace does
+ * not exist.
+ *
+ */
+Con *get_existing_workspace_by_num(int num);
+
+/**
+ * Returns true if the first output assigned to a workspace with the given
+ * workspace assignment is the same as the given output.
+ *
+ */
+bool output_triggers_assignment(Output *output, struct Workspace_Assignment *assignment);
+
/**
* Returns a pointer to the workspace with the given number (starting at 0),
* creating the workspace if necessary (by allocating the necessary amount of
@@ -193,4 +214,4 @@ Con *workspace_encapsulate(Con *ws);
* This returns true if and only if moving the workspace was successful.
*
*/
-bool workspace_move_to_output(Con *ws, const char *output);
+bool workspace_move_to_output(Con *ws, Output *output);
diff --git a/include/x.h b/include/x.h
index 3e81bc36..8b7664f2 100644
--- a/include/x.h
+++ b/include/x.h
@@ -49,6 +49,12 @@ void x_reinit(Con *con);
*/
void x_con_kill(Con *con);
+/*
+ * Completely reinitializes the container's frame, without destroying the old window.
+ *
+ */
+void x_con_reframe(Con *con);
+
/**
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
*
diff --git a/include/xcb.h b/include/xcb.h
index 92be7b89..53c932bf 100644
--- a/include/xcb.h
+++ b/include/xcb.h
@@ -53,6 +53,7 @@
ConfigureNotify */ \
XCB_EVENT_MASK_POINTER_MOTION | \
XCB_EVENT_MASK_PROPERTY_CHANGE | \
+ XCB_EVENT_MASK_FOCUS_CHANGE | \
XCB_EVENT_MASK_ENTER_WINDOW)
#define xmacro(atom) xcb_atom_t A_##atom;
diff --git a/libi3/dpi.c b/libi3/dpi.c
index a2c40319..d15e35be 100644
--- a/libi3/dpi.c
+++ b/libi3/dpi.c
@@ -49,14 +49,12 @@ void init_dpi(void) {
dpi = 0;
goto init_dpi_end;
}
- dpi = (long)round(in_dpi);
+ dpi = lround(in_dpi);
DLOG("Found Xft.dpi = %ld.\n", dpi);
init_dpi_end:
- if (resource != NULL) {
- free(resource);
- }
+ free(resource);
if (database != NULL) {
xcb_xrm_database_free(database);
diff --git a/libi3/draw_util.c b/libi3/draw_util.c
index 6a2e93dc..f88360dc 100644
--- a/libi3/draw_util.c
+++ b/libi3/draw_util.c
@@ -121,7 +121,7 @@ static void draw_util_set_source_color(surface_t *surface, color_t color) {
cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha);
}
-/**
+/*
* Draw the given text using libi3.
* This function also marks the surface dirty which is needed if other means of
* drawing are used. This will be the case when using XCB to draw text.
@@ -140,7 +140,7 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_
cairo_surface_mark_dirty(surface->surface);
}
-/**
+/*
* Draws a filled rectangle.
* This function is a convenience wrapper and takes care of flushing the
* surface as well as restoring the cairo state.
@@ -167,7 +167,7 @@ void draw_util_rectangle(surface_t *surface, color_t color, double x, double y,
cairo_restore(surface->cr);
}
-/**
+/*
* Clears a surface with the given color.
*
*/
@@ -191,7 +191,7 @@ void draw_util_clear_surface(surface_t *surface, color_t color) {
cairo_restore(surface->cr);
}
-/**
+/*
* Copies a surface onto another surface.
*
*/
diff --git a/libi3/font.c b/libi3/font.c
index 81091ea7..c06bae00 100644
--- a/libi3/font.c
+++ b/libi3/font.c
@@ -109,9 +109,8 @@ static void draw_text_pango(const char *text, size_t text_len,
cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue);
pango_cairo_update_layout(cr, layout);
pango_layout_get_pixel_size(layout, NULL, &height);
- /* Center the piece of text vertically if its height is smaller than the
- * cached font height, and just let "high" symbols fall out otherwise. */
- int yoffset = (height < savedFont->height ? 0.5 : 1) * (height - savedFont->height);
+ /* Center the piece of text vertically. */
+ int yoffset = (height - savedFont->height) / 2;
cairo_move_to(cr, x, y - yoffset);
pango_cairo_show_layout(cr, layout);
@@ -224,9 +223,7 @@ i3Font load_font(const char *pattern, const bool fallback) {
error->error_code);
}
}
- if (error != NULL) {
- free(error);
- }
+ free(error);
font.pattern = sstrdup(pattern);
LOG("Using X font %s\n", pattern);
@@ -275,17 +272,13 @@ void free_font(void) {
case FONT_TYPE_XCB: {
/* Close the font and free the info */
xcb_close_font(conn, savedFont->specific.xcb.id);
- if (savedFont->specific.xcb.info)
- free(savedFont->specific.xcb.info);
+ free(savedFont->specific.xcb.info);
break;
}
case FONT_TYPE_PANGO:
/* Free the font description */
pango_font_description_free(savedFont->specific.pango_desc);
break;
- default:
- assert(false);
- break;
}
savedFont = NULL;
@@ -315,9 +308,6 @@ void set_font_colors(xcb_gcontext_t gc, color_t foreground, color_t background)
pango_font_green = foreground.green;
pango_font_blue = foreground.blue;
break;
- default:
- assert(false);
- break;
}
}
@@ -388,8 +378,6 @@ void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc,
draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text),
drawable, visual, x, y, max_width, i3string_is_markup(text));
return;
- default:
- assert(false);
}
}
@@ -425,8 +413,6 @@ void draw_text_ascii(const char *text, xcb_drawable_t drawable,
draw_text_pango(text, strlen(text),
drawable, root_visual_type, x, y, max_width, false);
return;
- default:
- assert(false);
}
}
@@ -519,8 +505,6 @@ int predict_text_width(i3String *text) {
/* Calculate extents using Pango */
return predict_text_width_pango(i3string_as_utf8(text), i3string_get_num_bytes(text),
i3string_is_markup(text));
- default:
- assert(false);
- return 0;
}
+ assert(false);
}
diff --git a/libi3/format_placeholders.c b/libi3/format_placeholders.c
index 59e94781..770e383d 100644
--- a/libi3/format_placeholders.c
+++ b/libi3/format_placeholders.c
@@ -11,8 +11,8 @@
#include
#include
-#ifndef STARTS_WITH
-#define STARTS_WITH(string, needle) (strncasecmp((string), (needle), strlen((needle))) == 0)
+#ifndef CS_STARTS_WITH
+#define CS_STARTS_WITH(string, needle) (strncmp((string), (needle), strlen((needle))) == 0)
#endif
/*
@@ -28,7 +28,7 @@ char *format_placeholders(char *format, placeholder_t *placeholders, int num) {
int buffer_len = strlen(format) + 1;
for (char *walk = format; *walk != '\0'; walk++) {
for (int i = 0; i < num; i++) {
- if (!STARTS_WITH(walk, placeholders[i].name))
+ if (!CS_STARTS_WITH(walk, placeholders[i].name))
continue;
buffer_len = buffer_len - strlen(placeholders[i].name) + strlen(placeholders[i].value);
@@ -48,7 +48,7 @@ char *format_placeholders(char *format, placeholder_t *placeholders, int num) {
bool matched = false;
for (int i = 0; i < num; i++) {
- if (!STARTS_WITH(walk, placeholders[i].name)) {
+ if (!CS_STARTS_WITH(walk, placeholders[i].name)) {
continue;
}
diff --git a/libi3/g_utf8_make_valid.c b/libi3/g_utf8_make_valid.c
new file mode 100644
index 00000000..b15873b3
--- /dev/null
+++ b/libi3/g_utf8_make_valid.c
@@ -0,0 +1,93 @@
+/* g_utf8_make_valid.c - Coerce string into UTF-8
+ *
+ * Copyright (C) 1999 Tom Tromey
+ * Copyright (C) 2000 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+
+#include "libi3.h"
+
+#include
+#include
+
+/* Copied from:
+ * https://gitlab.gnome.org/GNOME/glib/blob/f928dfdf57bf92c883b53b16d7a9d49add504f52/glib/gutf8.c#L1752-1815 */
+/* clang-format off */
+#if !HAS_G_UTF8_MAKE_VALID
+/**
+ * g_utf8_make_valid:
+ * @str: string to coerce into UTF-8
+ * @len: the maximum length of @str to use, in bytes. If @len < 0,
+ * then the string is nul-terminated.
+ *
+ * If the provided string is valid UTF-8, return a copy of it. If not,
+ * return a copy in which bytes that could not be interpreted as valid Unicode
+ * are replaced with the Unicode replacement character (U+FFFD).
+ *
+ * For example, this is an appropriate function to use if you have received
+ * a string that was incorrectly declared to be UTF-8, and you need a valid
+ * UTF-8 version of it that can be logged or displayed to the user, with the
+ * assumption that it is close enough to ASCII or UTF-8 to be mostly
+ * readable as-is.
+ *
+ * Returns: (transfer full): a valid UTF-8 string whose content resembles @str
+ *
+ * Since: 2.52
+ */
+gchar *
+g_utf8_make_valid (const gchar *str,
+ gssize len)
+{
+ GString *string;
+ const gchar *remainder, *invalid;
+ gsize remaining_bytes, valid_bytes;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ if (len < 0)
+ len = strlen (str);
+
+ string = NULL;
+ remainder = str;
+ remaining_bytes = len;
+
+ while (remaining_bytes != 0)
+ {
+ if (g_utf8_validate (remainder, remaining_bytes, &invalid))
+ break;
+ valid_bytes = invalid - remainder;
+
+ if (string == NULL)
+ string = g_string_sized_new (remaining_bytes);
+
+ g_string_append_len (string, remainder, valid_bytes);
+ /* append U+FFFD REPLACEMENT CHARACTER */
+ g_string_append (string, "\357\277\275");
+
+ remaining_bytes -= valid_bytes + 1;
+ remainder = invalid + 1;
+ }
+
+ if (string == NULL)
+ return g_strndup (str, len);
+
+ g_string_append_len (string, remainder, remaining_bytes);
+ g_string_append_c (string, '\0');
+
+ g_assert (g_utf8_validate (string->str, -1, NULL));
+
+ return g_string_free (string, FALSE);
+}
+#endif
diff --git a/libi3/get_colorpixel.c b/libi3/get_colorpixel.c
index 9bdd0a71..49a9e3b4 100644
--- a/libi3/get_colorpixel.c
+++ b/libi3/get_colorpixel.c
@@ -43,7 +43,7 @@ uint32_t get_colorpixel(const char *hex) {
/* Shortcut: if our screen is true color, no need to do a roundtrip to X11 */
if (root_screen == NULL || root_screen->root_depth == 24 || root_screen->root_depth == 32) {
- return (0xFF << 24) | (r << 16 | g << 8 | b);
+ return (0xFFUL << 24) | (r << 16 | g << 8 | b);
}
/* Lookup this colorpixel in the cache */
@@ -60,8 +60,7 @@ uint32_t get_colorpixel(const char *hex) {
xcb_alloc_color_reply_t *reply;
- reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, root_screen->default_colormap,
- r16, g16, b16),
+ reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, root_screen->default_colormap, r16, g16, b16),
NULL);
if (!reply) {
diff --git a/libi3/get_config_path.c b/libi3/get_config_path.c
index efece5cd..4909e116 100644
--- a/libi3/get_config_path.c
+++ b/libi3/get_config_path.c
@@ -21,11 +21,11 @@ static bool path_exists(const char *path) {
}
/*
- * Get the path of the first configuration file found. If override_configpath
- * is specified, that path is returned and saved for further calls. Otherwise,
- * checks the home directory first, then the system directory first, always
- * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
- * $XDG_CONFIG_DIRS)
+ * Get the path of the first configuration file found. If override_configpath is
+ * specified, that path is returned and saved for further calls. Otherwise,
+ * checks the home directory first, then the system directory, always taking
+ * into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
+ * $XDG_CONFIG_DIRS).
*
*/
char *get_config_path(const char *override_configpath, bool use_system_paths) {
@@ -38,40 +38,41 @@ char *get_config_path(const char *override_configpath, bool use_system_paths) {
return sstrdup(saved_configpath);
}
- if (saved_configpath != NULL)
+ if (saved_configpath != NULL) {
return sstrdup(saved_configpath);
+ }
- /* 1: check the traditional path under the home directory */
- config_path = resolve_tilde("~/.i3/config");
- if (path_exists(config_path))
- return config_path;
- free(config_path);
-
- /* 2: check for $XDG_CONFIG_HOME/i3/config */
- if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
+ /* 1: check for $XDG_CONFIG_HOME/i3/config */
+ if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) {
xdg_config_home = "~/.config";
+ }
xdg_config_home = resolve_tilde(xdg_config_home);
sasprintf(&config_path, "%s/i3/config", xdg_config_home);
free(xdg_config_home);
- if (path_exists(config_path))
+ if (path_exists(config_path)) {
return config_path;
+ }
+ free(config_path);
+
+ /* 2: check the traditional path under the home directory */
+ config_path = resolve_tilde("~/.i3/config");
+ if (path_exists(config_path)) {
+ return config_path;
+ }
free(config_path);
/* The below paths are considered system-level, and can be skipped if the
* caller only wants user-level configs. */
- if (!use_system_paths)
+ if (!use_system_paths) {
return NULL;
+ }
- /* 3: check the traditional path under /etc */
- config_path = SYSCONFDIR "/i3/config";
- if (path_exists(config_path))
- return sstrdup(config_path);
-
- /* 4: check for $XDG_CONFIG_DIRS/i3/config */
- if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
+ /* 3: check for $XDG_CONFIG_DIRS/i3/config */
+ if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL) {
xdg_config_dirs = SYSCONFDIR "/xdg";
+ }
char *buf = sstrdup(xdg_config_dirs);
char *tok = strtok(buf, ":");
@@ -88,5 +89,11 @@ char *get_config_path(const char *override_configpath, bool use_system_paths) {
}
free(buf);
+ /* 4: check the traditional path under /etc */
+ config_path = SYSCONFDIR "/i3/config";
+ if (path_exists(config_path)) {
+ return sstrdup(config_path);
+ }
+
return NULL;
}
diff --git a/libi3/is_debug_build.c b/libi3/is_debug_build.c
index 4e583622..52187bda 100644
--- a/libi3/is_debug_build.c
+++ b/libi3/is_debug_build.c
@@ -15,7 +15,7 @@
* release version), based on the git version number.
*
*/
-bool is_debug_build() {
+bool is_debug_build(void) {
/* i3_version contains either something like this:
* "4.0.2 (2011-11-11, branch "release")".
* or: "4.0.2-123-gCOFFEEBABE (2011-11-11, branch "next")".
diff --git a/libi3/mkdirp.c b/libi3/mkdirp.c
index 1fc8c346..f5281bd7 100644
--- a/libi3/mkdirp.c
+++ b/libi3/mkdirp.c
@@ -44,10 +44,7 @@ int mkdirp(const char *path, mode_t mode) {
char *sep = strrchr(copy, '/');
if (sep == NULL) {
- if (copy != NULL) {
- free(copy);
- copy = NULL;
- }
+ free(copy);
return -1;
}
*sep = '\0';
diff --git a/libi3/resolve_tilde.c b/libi3/resolve_tilde.c
index 51d642db..6dbf132f 100644
--- a/libi3/resolve_tilde.c
+++ b/libi3/resolve_tilde.c
@@ -35,9 +35,10 @@ char *resolve_tilde(const char *path) {
} else {
head = globbuf.gl_pathv[0];
result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1, 1);
- strncpy(result, head, strlen(head));
- if (tail)
- strncat(result, tail, strlen(tail));
+ strcpy(result, head);
+ if (tail) {
+ strcat(result, tail);
+ }
}
globfree(&globbuf);
diff --git a/libi3/safewrappers.c b/libi3/safewrappers.c
index 94ad4ee6..1802b327 100644
--- a/libi3/safewrappers.c
+++ b/libi3/safewrappers.c
@@ -68,10 +68,9 @@ int sasprintf(char **strp, const char *fmt, ...) {
ssize_t writeall(int fd, const void *buf, size_t count) {
size_t written = 0;
- ssize_t n = 0;
while (written < count) {
- n = write(fd, buf + written, count - written);
+ const ssize_t n = write(fd, ((char *)buf) + written, count - written);
if (n == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
@@ -83,6 +82,25 @@ ssize_t writeall(int fd, const void *buf, size_t count) {
return written;
}
+ssize_t writeall_nonblock(int fd, const void *buf, size_t count) {
+ size_t written = 0;
+
+ while (written < count) {
+ const ssize_t n = write(fd, ((char *)buf) + written, count - written);
+ if (n == -1) {
+ if (errno == EAGAIN) {
+ return written;
+ } else if (errno == EINTR) {
+ continue;
+ } else {
+ return n;
+ }
+ }
+ written += (size_t)n;
+ }
+ return written;
+}
+
ssize_t swrite(int fd, const void *buf, size_t count) {
ssize_t n;
diff --git a/libi3/string.c b/libi3/string.c
index edd588da..9efa3690 100644
--- a/libi3/string.c
+++ b/libi3/string.c
@@ -30,15 +30,7 @@ struct _i3String {
*
*/
i3String *i3string_from_utf8(const char *from_utf8) {
- i3String *str = scalloc(1, sizeof(i3String));
-
- /* Get the text */
- str->utf8 = sstrdup(from_utf8);
-
- /* Compute and store the length */
- str->num_bytes = strlen(str->utf8);
-
- return str;
+ return i3string_from_utf8_with_length(from_utf8, -1);
}
/*
@@ -56,20 +48,18 @@ i3String *i3string_from_markup(const char *from_markup) {
/*
* Build an i3String from an UTF-8 encoded string with fixed length.
- * To be used when no proper NUL-terminaison is available.
+ * To be used when no proper NULL-termination is available.
* Returns the newly-allocated i3String.
*
*/
-i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes) {
+i3String *i3string_from_utf8_with_length(const char *from_utf8, ssize_t num_bytes) {
i3String *str = scalloc(1, sizeof(i3String));
- /* Copy the actual text to our i3String */
- str->utf8 = scalloc(num_bytes + 1, 1);
- strncpy(str->utf8, from_utf8, num_bytes);
- str->utf8[num_bytes] = '\0';
+ /* g_utf8_make_valid NULL-terminates the string. */
+ str->utf8 = g_utf8_make_valid(from_utf8, num_bytes);
- /* Store the length */
- str->num_bytes = num_bytes;
+ /* num_bytes < 0 means NULL-terminated string, need to calculate length */
+ str->num_bytes = num_bytes < 0 ? strlen(str->utf8) : (size_t)num_bytes;
return str;
}
@@ -109,7 +99,7 @@ i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs) {
return str;
}
-/**
+/*
* Copies the given i3string.
* Note that this will not free the source string.
*/
diff --git a/libi3/ucs2_conversion.c b/libi3/ucs2_conversion.c
index 75cff78a..398c1ae5 100644
--- a/libi3/ucs2_conversion.c
+++ b/libi3/ucs2_conversion.c
@@ -83,8 +83,7 @@ xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen) {
}
/* Do the conversion */
- size_t rc = iconv(ucs2_conversion_descriptor, (char **)&input,
- &input_size, (char **)&output, &output_size);
+ size_t rc = iconv(ucs2_conversion_descriptor, &input, &input_size, (char **)&output, &output_size);
if (rc == (size_t)-1) {
perror("Converting to UCS-2 failed");
free(buffer);
diff --git a/man/i3-config-wizard.man b/man/i3-config-wizard.man
index 5a9ca39e..e8cce007 100644
--- a/man/i3-config-wizard.man
+++ b/man/i3-config-wizard.man
@@ -9,7 +9,21 @@ i3-config-wizard - creates a keysym based config based on your layout
== SYNOPSIS
-i3-config-wizard
+i3-config-wizard [*-s* 'socket'] [*-m* 'modifier'] [*-v*] [*-h*]
+
+== OPTIONS
+
+*-s, --socket* 'socket'::
+Overwrites the path to the i3 IPC socket.
+
+*-m, --modifier* 'modifier'::
+Generates the configuration file headlessly. Accepts win or alt.
+
+*-v, --version*::
+Display version number and exit.
+
+*-h, --help*::
+Display a short help message and exit.
== FILES
diff --git a/man/i3-input.man b/man/i3-input.man
index 07a91783..dc145914 100644
--- a/man/i3-input.man
+++ b/man/i3-input.man
@@ -1,5 +1,5 @@
i3-input(1)
-=========
+===========
Michael Stapelberg
v4.1.2, April 2012
diff --git a/man/i3-msg.man b/man/i3-msg.man
index 7f050f59..625131de 100644
--- a/man/i3-msg.man
+++ b/man/i3-msg.man
@@ -31,6 +31,11 @@ with an error.
*-t* 'type'::
Send ipc message, see below. This option defaults to "command".
+*-m*, *--monitor*::
+Instead of exiting right after receiving the first subscribed event,
+wait indefinitely for all of them. Can only be used with "-t subscribe".
+See the "subscribe" IPC message type below for details.
+
*message*::
Send ipc message, see below.
@@ -69,6 +74,17 @@ get_version::
Gets the version of i3. The reply will be a JSON-encoded dictionary with the
major, minor, patch and human-readable version.
+get_config::
+Gets the currently loaded i3 configuration.
+
+send_tick::
+Sends a tick to all IPC connections which subscribe to tick events.
+
+subscribe::
+The payload of the message describes the events to subscribe to.
+Upon reception, each event will be dumped as a JSON-encoded object.
+See the -m option for continuous monitoring.
+
== DESCRIPTION
i3-msg is a sample implementation for a client using the unix socket IPC
@@ -85,6 +101,9 @@ i3-msg border normal
# Dump the layout tree
i3-msg -t get_tree
+
+# Monitor window changes
+i3-msg -t subscribe -m '[ "window" ]'
------------------------------------------------
== ENVIRONMENT
diff --git a/man/i3-nagbar.man b/man/i3-nagbar.man
index 77fdd80b..ef3a3545 100644
--- a/man/i3-nagbar.man
+++ b/man/i3-nagbar.man
@@ -9,7 +9,7 @@ i3-nagbar - displays an error bar on top of your screen
== SYNOPSIS
-i3-nagbar [-m ] [-b ] [-t warning|error] [-f ] [-v]
+i3-nagbar [-m ] [-b ] [-B ] [-t warning|error] [-f ] [-v]
== OPTIONS
@@ -32,6 +32,12 @@ Select font that is being used.
*-b, --button* 'button' 'action'::
Create a button with text 'button'. The 'action' are the shell commands that
will be executed by this button. Multiple buttons can be defined.
+Will launch the shell commands inside a terminal emulator, using
+i3-sensible-terminal.
+
+*-B, --button-no-terminal* 'button' 'action'::
+Same as above, but will execute the shell commands directly, without launching a
+terminal emulator.
== DESCRIPTION
diff --git a/man/i3-sensible-editor.man b/man/i3-sensible-editor.man
index 4f16d6c1..31e82ca3 100644
--- a/man/i3-sensible-editor.man
+++ b/man/i3-sensible-editor.man
@@ -1,5 +1,5 @@
i3-sensible-editor(1)
-===================
+=====================
Michael Stapelberg
v4.1, November 2011
diff --git a/man/i3-sensible-pager.man b/man/i3-sensible-pager.man
index 22754c0b..2339bef0 100644
--- a/man/i3-sensible-pager.man
+++ b/man/i3-sensible-pager.man
@@ -1,5 +1,5 @@
i3-sensible-pager(1)
-===================
+====================
Michael Stapelberg
v4.1, November 2011
diff --git a/man/i3-sensible-terminal.man b/man/i3-sensible-terminal.man
index 894af912..bda5a723 100644
--- a/man/i3-sensible-terminal.man
+++ b/man/i3-sensible-terminal.man
@@ -47,6 +47,8 @@ It tries to start one of the following (in that order):
* kitty
* guake
* tilda
+* alacritty
+* hyper
Please don’t complain about the order: If the user has any preference, they will
have $TERMINAL set or modified their i3 configuration file.
diff --git a/man/i3.man b/man/i3.man
index 1f595ce8..640b5ac8 100644
--- a/man/i3.man
+++ b/man/i3.man
@@ -170,10 +170,10 @@ Exits i3.
When starting, i3 looks for configuration files in the following order:
-1. ~/.i3/config
-2. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
-3. /etc/i3/config
-4. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
+1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
+2. ~/.i3/config
+3. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
+4. /etc/i3/config
You can specify a custom path using the -c option.
diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec
index 0289fa1a..6b015188 100644
--- a/parser-specs/commands.spec
+++ b/parser-specs/commands.spec
@@ -86,16 +86,16 @@ state DEBUGLOG:
# border normal|pixel []
# border none|1pixel|toggle
state BORDER:
- border_style = 'normal', 'pixel'
+ border_style = 'normal', 'pixel', 'toggle'
-> BORDER_WIDTH
- border_style = 'none', 'toggle'
+ border_style = 'none'
-> call cmd_border($border_style, 0)
- border_style = '1pixel'
- -> call cmd_border($border_style, 1)
+ '1pixel'
+ -> call cmd_border("pixel", 1)
state BORDER_WIDTH:
end
- -> call cmd_border($border_style, 2)
+ -> call cmd_border($border_style, -1)
border_width = number
-> call cmd_border($border_style, &border_width)
@@ -243,7 +243,7 @@ state RESIZE_TILING:
'or'
-> RESIZE_TILING_OR
end
- -> call cmd_resize($way, $direction, &resize_px, 10)
+ -> call cmd_resize($way, $direction, &resize_px, 0)
state RESIZE_TILING_OR:
resize_ppt = number
@@ -254,12 +254,24 @@ state RESIZE_TILING_FINAL:
-> call cmd_resize($way, $direction, &resize_px, &resize_ppt)
state RESIZE_SET:
+ 'height'
+ -> RESIZE_HEIGHT_GET_NUMBER
+ 'width'
+ ->
width = number
-> RESIZE_WIDTH
state RESIZE_WIDTH:
mode_width = 'px', 'ppt'
->
+ end
+ -> call cmd_resize_set(&width, $mode_width, 0, 0)
+ 'height'
+ -> RESIZE_HEIGHT_GET_NUMBER
+ height = number
+ -> RESIZE_HEIGHT
+
+state RESIZE_HEIGHT_GET_NUMBER:
height = number
-> RESIZE_HEIGHT
@@ -396,7 +408,7 @@ state MOVE_TO_POSITION_X:
state MOVE_TO_POSITION_Y:
'px', end
- -> call cmd_move_window_to_position($method, &coord_x, &coord_y)
+ -> call cmd_move_window_to_position(&coord_x, &coord_y)
# mode
state MODE:
diff --git a/parser-specs/config.spec b/parser-specs/config.spec
index 60a1fc67..43181c59 100644
--- a/parser-specs/config.spec
+++ b/parser-specs/config.spec
@@ -45,9 +45,11 @@ state INITIAL:
'fake_outputs', 'fake-outputs' -> FAKE_OUTPUTS
'force_display_urgency_hint' -> FORCE_DISPLAY_URGENCY_HINT
'focus_on_window_activation' -> FOCUS_ON_WINDOW_ACTIVATION
+ 'title_align' -> TITLE_ALIGN
'show_marks' -> SHOW_MARKS
'workspace' -> WORKSPACE
'ipc_socket', 'ipc-socket' -> IPC_SOCKET
+ 'ipc_kill_timeout' -> IPC_KILL_TIMEOUT
'restart_state' -> RESTART_STATE
'popup_during_fullscreen' -> POPUP_DURING_FULLSCREEN
exectype = 'exec_always', 'exec' -> EXEC
@@ -247,6 +249,11 @@ state FORCE_DISPLAY_URGENCY_HINT:
duration_ms = number
-> FORCE_DISPLAY_URGENCY_HINT_MS
+# title_align [left|center|right]
+state TITLE_ALIGN:
+ alignment = 'left', 'center', 'right'
+ -> call cfg_title_align($alignment)
+
# show_marks
state SHOW_MARKS:
value = word
@@ -273,7 +280,7 @@ state WORKSPACE_OUTPUT:
-> WORKSPACE_OUTPUT_STR
state WORKSPACE_OUTPUT_STR:
- output = word
+ output = string
-> call cfg_workspace($workspace, $output)
# ipc-socket
@@ -281,6 +288,11 @@ state IPC_SOCKET:
path = string
-> call cfg_ipc_socket($path)
+# ipc_kill_timeout
+state IPC_KILL_TIMEOUT:
+ timeout = number
+ -> call cfg_ipc_kill_timeout(&timeout)
+
# restart_state (for testcases)
state RESTART_STATE:
path = string
@@ -455,6 +467,7 @@ state BAR:
'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR
'workspace_buttons' -> BAR_WORKSPACE_BUTTONS
'strip_workspace_numbers' -> BAR_STRIP_WORKSPACE_NUMBERS
+ 'strip_workspace_name' -> BAR_STRIP_WORKSPACE_NAME
'verbose' -> BAR_VERBOSE
'colors' -> BAR_COLORS_BRACE
'}'
@@ -490,8 +503,14 @@ state BAR_ID:
-> call cfg_bar_id($bar_id); BAR
state BAR_MODIFIER:
- modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift', 'none', 'off'
- -> call cfg_bar_modifier($modifier); BAR
+ 'off', 'none'
+ -> call cfg_bar_modifier(NULL); BAR
+ modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl'
+ ->
+ '+'
+ ->
+ end
+ -> call cfg_bar_modifier($modifiers); BAR
state BAR_WHEEL_UP_CMD:
command = string
@@ -555,6 +574,10 @@ state BAR_STRIP_WORKSPACE_NUMBERS:
value = word
-> call cfg_bar_strip_workspace_numbers($value); BAR
+state BAR_STRIP_WORKSPACE_NAME:
+ value = word
+ -> call cfg_bar_strip_workspace_name($value); BAR
+
state BAR_VERBOSE:
value = word
-> call cfg_bar_verbose($value); BAR
diff --git a/release.sh b/release.sh
index cfb4beae..0190fcfb 100755
--- a/release.sh
+++ b/release.sh
@@ -1,9 +1,9 @@
#!/bin/zsh
# This script is used to prepare a new release of i3.
-export RELEASE_VERSION="4.14.1"
+export RELEASE_VERSION="4.15"
export PREVIOUS_VERSION="4.14"
-export RELEASE_BRANCH="master"
+export RELEASE_BRANCH="next"
if [ ! -e "../i3.github.io" ]
then
@@ -85,12 +85,12 @@ if [ "${RELEASE_BRANCH}" = "master" ]; then
git checkout master
git merge --no-ff release-${RELEASE_VERSION} -m "Merge branch 'release-${RELEASE_VERSION}'"
git checkout next
- git merge --no-ff -X ours master -m "Merge branch 'master' into next"
+ git merge --no-ff -s recursive -X ours -X no-renames master -m "Merge branch 'master' into next"
else
git checkout next
git merge --no-ff release-${RELEASE_VERSION} -m "Merge branch 'release-${RELEASE_VERSION}'"
git checkout master
- git merge --no-ff -X theirs next -m "Merge branch 'next' into master"
+ git merge --no-ff -s recursive -X theirs -X no-renames next -m "Merge branch 'next' into master"
fi
git remote remove origin
@@ -126,6 +126,7 @@ WORKDIR /usr/src
RUN mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' i3-${RELEASE_VERSION}/debian/control
WORKDIR /usr/src/i3-${RELEASE_VERSION}
RUN dpkg-buildpackage -sa -j8
+RUN dpkg-buildpackage -S -sa -j8
EOT
CONTAINER_NAME=$(echo "i3-${TMPDIR}" | sed 's,/,,g')
@@ -139,7 +140,7 @@ echo "Content of resulting package’s .changes file:"
cat ${TMPDIR}/debian/*.changes
# debsign is in devscripts, which is available in fedora and debian
-debsign -k4AC8EE1D ${TMPDIR}/debian/*.changes
+debsign --no-re-sign -k4AC8EE1D ${TMPDIR}/debian/*.changes
# TODO: docker cleanup
@@ -227,7 +228,7 @@ echo " cd ${TMPDIR}/i3.github.io"
echo " git push"
echo ""
echo " cd ${TMPDIR}/debian"
-echo " dput *.changes"
+echo " dput"
echo ""
echo " cd ${TMPDIR}"
echo " sendmail -t < email.txt"
diff --git a/src/assignments.c b/src/assignments.c
index a0f5d5a7..abacc0a3 100644
--- a/src/assignments.c
+++ b/src/assignments.c
@@ -22,7 +22,7 @@ void run_assignments(i3Window *window) {
/* Check if any assignments match */
Assignment *current;
TAILQ_FOREACH(current, &assignments, assignments) {
- if (!match_matches_window(&(current->match), window))
+ if (current->type != A_COMMAND || !match_matches_window(&(current->match), window))
continue;
bool skip = false;
@@ -45,19 +45,16 @@ void run_assignments(i3Window *window) {
window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment *) * window->nr_assignments);
window->ran_assignments[window->nr_assignments - 1] = current;
- DLOG("matching assignment, would do:\n");
- if (current->type == A_COMMAND) {
- DLOG("execute command %s\n", current->dest.command);
- char *full_command;
- sasprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
- CommandResult *result = parse_command(full_command, NULL);
- free(full_command);
+ DLOG("matching assignment, execute command %s\n", current->dest.command);
+ char *full_command;
+ sasprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
+ CommandResult *result = parse_command(full_command, NULL);
+ free(full_command);
- if (result->needs_tree_render)
- needs_tree_render = true;
+ if (result->needs_tree_render)
+ needs_tree_render = true;
- command_result_free(result);
- }
+ command_result_free(result);
}
/* If any of the commands required re-rendering, we will do that now. */
diff --git a/src/bindings.c b/src/bindings.c
index c145b956..6704c816 100644
--- a/src/bindings.c
+++ b/src/bindings.c
@@ -198,6 +198,7 @@ void regrab_all_buttons(xcb_connection_t *conn) {
*/
static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_release, uint16_t input_code, input_type_t input_type) {
Binding *bind;
+ Binding *result = NULL;
if (!is_release) {
/* On a press event, we first reset all B_UPON_KEYRELEASE_IGNORE_MODS
@@ -227,30 +228,27 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas
/* For keyboard bindings where a symbol was specified by the user, we
* need to look in the array of translated keycodes for the event’s
* keycode */
+ bool found_keycode = false;
if (input_type == B_KEYBOARD && bind->symbol != NULL) {
xcb_keycode_t input_keycode = (xcb_keycode_t)input_code;
- bool found_keycode = false;
struct Binding_Keycode *binding_keycode;
TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) {
const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF);
const bool mods_match = (modifiers_mask == modifiers_state);
DLOG("binding_keycode->modifiers = %d, modifiers_mask = %d, modifiers_state = %d, mods_match = %s\n",
binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no"));
- if (binding_keycode->keycode == input_keycode && mods_match) {
+ if (binding_keycode->keycode == input_keycode &&
+ (mods_match || (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS && is_release))) {
found_keycode = true;
break;
}
}
- if (!found_keycode) {
- continue;
- }
} else {
/* This case is easier: The user specified a keycode */
if (bind->keycode != input_code) {
continue;
}
- bool found_keycode = false;
struct Binding_Keycode *binding_keycode;
TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) {
const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF);
@@ -262,9 +260,9 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas
break;
}
}
- if (!found_keycode) {
- continue;
- }
+ }
+ if (!found_keycode) {
+ continue;
}
/* If this binding is a release binding, it matches the key which the
@@ -274,23 +272,26 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas
if (bind->release == B_UPON_KEYRELEASE && !is_release) {
bind->release = B_UPON_KEYRELEASE_IGNORE_MODS;
DLOG("marked bind %p as B_UPON_KEYRELEASE_IGNORE_MODS\n", bind);
- /* The correct binding has been found, so abort the search, but
- * also don’t return this binding, since it should not be executed
- * yet (only when the keys are released). */
- bind = TAILQ_END(bindings);
- break;
- }
-
- /* Check if the binding is for a press or a release event */
- if ((bind->release == B_UPON_KEYPRESS && is_release) ||
- (bind->release >= B_UPON_KEYRELEASE && !is_release)) {
+ if (result) {
+ break;
+ }
continue;
}
- break;
+ /* Check if the binding is for a press or a release event */
+ if ((bind->release == B_UPON_KEYPRESS && is_release)) {
+ continue;
+ }
+
+ if (is_release) {
+ return bind;
+ } else if (!result) {
+ /* Continue looping to mark needed B_UPON_KEYRELEASE_IGNORE_MODS. */
+ result = bind;
+ }
}
- return (bind == TAILQ_END(bindings) ? NULL : bind);
+ return result;
}
/*
@@ -361,6 +362,14 @@ struct resolve {
struct xkb_state *xkb_state_numlock_no_shift;
};
+#define ADD_TRANSLATED_KEY(code, mods) \
+ do { \
+ struct Binding_Keycode *binding_keycode = smalloc(sizeof(struct Binding_Keycode)); \
+ binding_keycode->modifiers = (mods); \
+ binding_keycode->keycode = (code); \
+ TAILQ_INSERT_TAIL(&(bind->keycodes_head), binding_keycode, keycodes); \
+ } while (0)
+
/*
* add_keycode_if_matches is called for each keycode in the keymap and will add
* the keycode to |data->bind| if the keycode can result in the keysym
@@ -390,18 +399,10 @@ static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key,
}
Binding *bind = resolving->bind;
-#define ADD_TRANSLATED_KEY(mods) \
- do { \
- struct Binding_Keycode *binding_keycode = smalloc(sizeof(struct Binding_Keycode)); \
- binding_keycode->modifiers = (mods); \
- binding_keycode->keycode = key; \
- TAILQ_INSERT_TAIL(&(bind->keycodes_head), binding_keycode, keycodes); \
- } while (0)
-
- ADD_TRANSLATED_KEY(bind->event_state_mask);
+ ADD_TRANSLATED_KEY(key, bind->event_state_mask);
/* Also bind the key with active CapsLock */
- ADD_TRANSLATED_KEY(bind->event_state_mask | XCB_MOD_MASK_LOCK);
+ ADD_TRANSLATED_KEY(key, bind->event_state_mask | XCB_MOD_MASK_LOCK);
/* If this binding is not explicitly for NumLock, check whether we need to
* add a fallback. */
@@ -413,17 +414,15 @@ static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key,
xkb_keysym_t sym_numlock = xkb_state_key_get_one_sym(numlock_state, key);
if (sym_numlock == resolving->keysym) {
/* Also bind the key with active NumLock */
- ADD_TRANSLATED_KEY(bind->event_state_mask | xcb_numlock_mask);
+ ADD_TRANSLATED_KEY(key, bind->event_state_mask | xcb_numlock_mask);
/* Also bind the key with active NumLock+CapsLock */
- ADD_TRANSLATED_KEY(bind->event_state_mask | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
+ ADD_TRANSLATED_KEY(key, bind->event_state_mask | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
} else {
DLOG("Skipping automatic numlock fallback, key %d resolves to 0x%x with numlock\n",
key, sym_numlock);
}
}
-
-#undef ADD_TRANSLATED_KEY
}
/*
@@ -431,41 +430,22 @@ static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key,
*
*/
void translate_keysyms(void) {
- struct xkb_state *dummy_state = xkb_state_new(xkb_keymap);
- if (dummy_state == NULL) {
- ELOG("Could not create XKB state, cannot translate keysyms.\n");
- return;
- }
-
- struct xkb_state *dummy_state_no_shift = xkb_state_new(xkb_keymap);
- if (dummy_state_no_shift == NULL) {
- ELOG("Could not create XKB state, cannot translate keysyms.\n");
- return;
- }
-
- struct xkb_state *dummy_state_numlock = xkb_state_new(xkb_keymap);
- if (dummy_state_numlock == NULL) {
- ELOG("Could not create XKB state, cannot translate keysyms.\n");
- return;
- }
-
- struct xkb_state *dummy_state_numlock_no_shift = xkb_state_new(xkb_keymap);
- if (dummy_state_numlock_no_shift == NULL) {
- ELOG("Could not create XKB state, cannot translate keysyms.\n");
- return;
- }
-
+ struct xkb_state *dummy_state = NULL;
+ struct xkb_state *dummy_state_no_shift = NULL;
+ struct xkb_state *dummy_state_numlock = NULL;
+ struct xkb_state *dummy_state_numlock_no_shift = NULL;
bool has_errors = false;
+
+ if ((dummy_state = xkb_state_new(xkb_keymap)) == NULL ||
+ (dummy_state_no_shift = xkb_state_new(xkb_keymap)) == NULL ||
+ (dummy_state_numlock = xkb_state_new(xkb_keymap)) == NULL ||
+ (dummy_state_numlock_no_shift = xkb_state_new(xkb_keymap)) == NULL) {
+ ELOG("Could not create XKB state, cannot translate keysyms.\n");
+ goto out;
+ }
+
Binding *bind;
TAILQ_FOREACH(bind, bindings, bindings) {
-#define ADD_TRANSLATED_KEY(code, mods) \
- do { \
- struct Binding_Keycode *binding_keycode = smalloc(sizeof(struct Binding_Keycode)); \
- binding_keycode->modifiers = (mods); \
- binding_keycode->keycode = (code); \
- TAILQ_INSERT_TAIL(&(bind->keycodes_head), binding_keycode, keycodes); \
- } while (0)
-
if (bind->input_type == B_MOUSE) {
long button;
if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) {
@@ -616,10 +596,9 @@ void translate_keysyms(void) {
DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n",
bind->event_state_mask, bind->symbol, keysym, keycodes, num_keycodes);
free(keycodes);
-
-#undef ADD_TRANSLATED_KEY
}
+out:
xkb_state_unref(dummy_state);
xkb_state_unref(dummy_state_no_shift);
xkb_state_unref(dummy_state_numlock);
@@ -630,6 +609,8 @@ void translate_keysyms(void) {
}
}
+#undef ADD_TRANSLATED_KEY
+
/*
* Switches the key bindings to the given mode, if the mode exists
*
@@ -648,6 +629,14 @@ void switch_mode(const char *new_mode) {
translate_keysyms();
grab_all_keys(conn);
+ /* Reset all B_UPON_KEYRELEASE_IGNORE_MODS bindings to avoid possibly
+ * activating one of them. */
+ Binding *bind;
+ TAILQ_FOREACH(bind, bindings, bindings) {
+ if (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS)
+ bind->release = B_UPON_KEYRELEASE;
+ }
+
char *event_msg;
sasprintf(&event_msg, "{\"change\":\"%s\", \"pango_markup\":%s}",
mode->name, (mode->pango_markup ? "true" : "false"));
@@ -658,7 +647,7 @@ void switch_mode(const char *new_mode) {
return;
}
- ELOG("ERROR: Mode not found\n");
+ ELOG("Mode not found\n");
}
static int reorder_binding_cmp(const void *a, const void *b) {
@@ -870,8 +859,6 @@ static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) {
xcb_intern_atom_reply_t *atom_reply;
size_t content_max_words = 256;
- xcb_window_t root = root_screen->root;
-
atom_reply = xcb_intern_atom_reply(
conn, xcb_intern_atom(conn, 0, strlen("_XKB_RULES_NAMES"), "_XKB_RULES_NAMES"), NULL);
if (atom_reply == NULL)
@@ -968,10 +955,7 @@ bool load_keymap(void) {
.options = NULL};
if (fill_rmlvo_from_root(&names) == -1) {
ELOG("Could not get _XKB_RULES_NAMES atom from root window, falling back to defaults.\n");
- if ((new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0)) == NULL) {
- ELOG("xkb_keymap_new_from_names(NULL) failed\n");
- return false;
- }
+ /* Using NULL for the fields of xkb_rule_names. */
}
new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0);
free((char *)names.rules);
@@ -980,7 +964,7 @@ bool load_keymap(void) {
free((char *)names.variant);
free((char *)names.options);
if (new_keymap == NULL) {
- ELOG("xkb_keymap_new_from_names(RMLVO) failed\n");
+ ELOG("xkb_keymap_new_from_names failed\n");
return false;
}
}
diff --git a/src/click.c b/src/click.c
index b036c5f8..1218a4c2 100644
--- a/src/click.c
+++ b/src/click.c
@@ -44,9 +44,6 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
case BORDER_BOTTOM:
search_direction = D_DOWN;
break;
- default:
- assert(false);
- break;
}
bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
@@ -233,15 +230,12 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
event->detail == XCB_BUTTON_SCROLL_LEFT ||
event->detail == XCB_BUTTON_SCROLL_RIGHT)) {
DLOG("Scrolling on a window decoration\n");
- orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ);
- /* Focus the currently focused container on the same level that the
- * user scrolled on. e.g. the tabbed decoration contains
- * "urxvt | i3: V[xterm geeqie] | firefox",
- * focus is on the xterm, but the user scrolled on urxvt.
- * The splitv container will be focused. */
+ orientation_t orientation = con_orientation(con->parent);
+ /* Use the focused child of the tabbed / stacked container, not the
+ * container the user scrolled on. */
Con *focused = con->parent;
focused = TAILQ_FIRST(&(focused->focus_head));
- con_activate(focused);
+ con_activate(con_descend_focused(focused));
/* To prevent scrolling from going outside the container (see ticket
* #557), we first check if scrolling is possible at all. */
bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
@@ -260,7 +254,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
/* 3: For floating containers, we also want to raise them on click.
* We will skip handling events on floating cons in fullscreen mode */
- Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
+ Con *fs = con_get_fullscreen_covering_ws(ws);
if (floatingcon != NULL && fs != con) {
/* 4: floating_modifier plus left mouse button drags */
if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) {
diff --git a/src/commands.c b/src/commands.c
index 899bbb90..eecd59fc 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -269,6 +269,33 @@ static void move_matches_to_workspace(Con *ws) {
}
}
+#define CHECK_MOVE_CON_TO_WORKSPACE \
+ do { \
+ HANDLE_EMPTY_MATCH; \
+ if (TAILQ_EMPTY(&owindows)) { \
+ yerror("Nothing to move: specified criteria don't match any window"); \
+ return; \
+ } else { \
+ bool found = false; \
+ owindow *current = TAILQ_FIRST(&owindows); \
+ while (current) { \
+ owindow *next = TAILQ_NEXT(current, owindows); \
+ \
+ if (current->con->type == CT_WORKSPACE && !con_has_children(current->con)) { \
+ TAILQ_REMOVE(&owindows, current, owindows); \
+ } else { \
+ found = true; \
+ } \
+ \
+ current = next; \
+ } \
+ if (!found) { \
+ yerror("Nothing to move: workspace empty"); \
+ return; \
+ } \
+ } \
+ } while (0)
+
/*
* Implementation of 'move [window|container] [to] workspace
* next|prev|next_on_output|prev_on_output|current'.
@@ -277,17 +304,7 @@ static void move_matches_to_workspace(Con *ws) {
void cmd_move_con_to_workspace(I3_CMD, const char *which) {
DLOG("which=%s\n", which);
- /* We have nothing to move:
- * when criteria was specified but didn't match any window or
- * when criteria wasn't specified and we don't have any window focused. */
- if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
- (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
- !con_has_children(focused))) {
- ysuccess(false);
- return;
- }
-
- HANDLE_EMPTY_MATCH;
+ CHECK_MOVE_CON_TO_WORKSPACE;
/* get the workspace */
Con *ws;
@@ -302,8 +319,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) {
else if (strcmp(which, "current") == 0)
ws = con_get_workspace(focused);
else {
- ELOG("BUG: called with which=%s\n", which);
- ysuccess(false);
+ yerror("BUG: called with which=%s", which);
return;
}
@@ -314,7 +330,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) {
ysuccess(true);
}
-/**
+/*
* Implementation of 'move [window|container] [to] workspace back_and_forth'.
*
*/
@@ -338,36 +354,21 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
* Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace '.
*
*/
-void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_forth) {
+void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *no_auto_back_and_forth) {
if (strncasecmp(name, "__", strlen("__")) == 0) {
- LOG("You cannot move containers to i3-internal workspaces (\"%s\").\n", name);
- ysuccess(false);
+ yerror("You cannot move containers to i3-internal workspaces (\"%s\").", name);
return;
}
- const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
-
- /* We have nothing to move:
- * when criteria was specified but didn't match any window or
- * when criteria wasn't specified and we don't have any window focused. */
- if (!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) {
- ELOG("No windows match your criteria, cannot move.\n");
- ysuccess(false);
- return;
- } else if (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
- !con_has_children(focused)) {
- ysuccess(false);
- return;
- }
+ CHECK_MOVE_CON_TO_WORKSPACE;
LOG("should move window to workspace %s\n", name);
/* get the workspace */
Con *ws = workspace_get(name, NULL);
- if (!no_auto_back_and_forth)
+ if (no_auto_back_and_forth == NULL) {
ws = maybe_auto_back_and_forth_workspace(ws);
-
- HANDLE_EMPTY_MATCH;
+ }
move_matches_to_workspace(ws);
@@ -380,43 +381,26 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_au
* Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace number '.
*
*/
-void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) {
- const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
-
- /* We have nothing to move:
- * when criteria was specified but didn't match any window or
- * when criteria wasn't specified and we don't have any window focused. */
- if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
- (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
- !con_has_children(focused))) {
- ysuccess(false);
- return;
- }
+void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth) {
+ CHECK_MOVE_CON_TO_WORKSPACE;
LOG("should move window to workspace %s\n", which);
- /* get the workspace */
- Con *output, *ws = NULL;
long parsed_num = ws_name_to_number(which);
-
if (parsed_num == -1) {
LOG("Could not parse initial part of \"%s\" as a number.\n", which);
yerror("Could not parse number \"%s\"", which);
return;
}
- TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
- GREP_FIRST(ws, output_get_content(output),
- child->num == parsed_num);
-
+ Con *ws = get_existing_workspace_by_num(parsed_num);
if (!ws) {
ws = workspace_get(which, NULL);
}
- if (!no_auto_back_and_forth)
+ if (no_auto_back_and_forth == NULL) {
ws = maybe_auto_back_and_forth_workspace(ws);
-
- HANDLE_EMPTY_MATCH;
+ }
move_matches_to_workspace(ws);
@@ -425,110 +409,104 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no
ysuccess(true);
}
-static void cmd_resize_floating(I3_CMD, const char *way, const char *direction, Con *floating_con, int px) {
- LOG("floating resize\n");
+/*
+ * Convert a string direction ("left", "right", etc.) to a direction_t. Assumes
+ * valid direction string.
+ */
+static direction_t parse_direction(const char *str) {
+ if (strcmp(str, "left") == 0) {
+ return D_LEFT;
+ } else if (strcmp(str, "right") == 0) {
+ return D_RIGHT;
+ } else if (strcmp(str, "up") == 0) {
+ return D_UP;
+ } else if (strcmp(str, "down") == 0) {
+ return D_DOWN;
+ } else {
+ ELOG("Invalid direction. This is a parser bug.\n");
+ assert(false);
+ }
+}
+
+static void cmd_resize_floating(I3_CMD, const char *way, const char *direction_str, Con *floating_con, int px) {
Rect old_rect = floating_con->rect;
Con *focused_con = con_descend_focused(floating_con);
+ direction_t direction;
+ if (strcmp(direction_str, "height") == 0) {
+ direction = D_DOWN;
+ } else if (strcmp(direction_str, "width") == 0) {
+ direction = D_RIGHT;
+ } else {
+ direction = parse_direction(direction_str);
+ }
+ orientation_t orientation = orientation_from_direction(direction);
+
/* ensure that resize will take place even if pixel increment is smaller than
* height increment or width increment.
* fixes #1011 */
const i3Window *window = focused_con->window;
if (window != NULL) {
- if (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0 ||
- strcmp(direction, "height") == 0) {
- if (px < 0)
+ if (orientation == VERT) {
+ if (px < 0) {
px = (-px < window->height_increment) ? -window->height_increment : px;
- else
+ } else {
px = (px < window->height_increment) ? window->height_increment : px;
- } else if (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0) {
- if (px < 0)
+ }
+ } else {
+ if (px < 0) {
px = (-px < window->width_increment) ? -window->width_increment : px;
- else
+ } else {
px = (px < window->width_increment) ? window->width_increment : px;
+ }
}
}
- if (strcmp(direction, "up") == 0) {
+ if (orientation == VERT) {
floating_con->rect.height += px;
- } else if (strcmp(direction, "down") == 0 || strcmp(direction, "height") == 0) {
- floating_con->rect.height += px;
- } else if (strcmp(direction, "left") == 0) {
- floating_con->rect.width += px;
} else {
floating_con->rect.width += px;
}
-
floating_check_size(floating_con);
/* Did we actually resize anything or did the size constraints prevent us?
* If we could not resize, exit now to not move the window. */
- if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0)
+ if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0) {
return;
+ }
- if (strcmp(direction, "up") == 0) {
+ if (direction == D_UP) {
floating_con->rect.y -= (floating_con->rect.height - old_rect.height);
- } else if (strcmp(direction, "left") == 0) {
+ } else if (direction == D_LEFT) {
floating_con->rect.x -= (floating_con->rect.width - old_rect.width);
}
/* If this is a scratchpad window, don't auto center it from now on. */
- if (floating_con->scratchpad_state == SCRATCHPAD_FRESH)
+ if (floating_con->scratchpad_state == SCRATCHPAD_FRESH) {
floating_con->scratchpad_state = SCRATCHPAD_CHANGED;
+ }
}
-static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
- LOG("tiling resize\n");
+static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *direction, int px, int ppt) {
Con *second = NULL;
Con *first = current;
- direction_t search_direction;
- if (!strcmp(direction, "left"))
- search_direction = D_LEFT;
- else if (!strcmp(direction, "right"))
- search_direction = D_RIGHT;
- else if (!strcmp(direction, "up"))
- search_direction = D_UP;
- else
- search_direction = D_DOWN;
+ direction_t search_direction = parse_direction(direction);
bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
if (!res) {
- LOG("No second container in this direction found.\n");
- ysuccess(false);
+ yerror("No second container found in this direction.");
return false;
}
- /* get the default percentage */
- int children = con_num_children(first->parent);
- LOG("ins. %d children\n", children);
- double percentage = 1.0 / children;
- LOG("default percentage = %f\n", percentage);
-
- /* resize */
- LOG("first->percent before = %f\n", first->percent);
- LOG("second->percent before = %f\n", second->percent);
- if (first->percent == 0.0)
- first->percent = percentage;
- if (second->percent == 0.0)
- second->percent = percentage;
- double new_first_percent = first->percent + ((double)ppt / 100.0);
- double new_second_percent = second->percent - ((double)ppt / 100.0);
- LOG("new_first_percent = %f\n", new_first_percent);
- LOG("new_second_percent = %f\n", new_second_percent);
- /* Ensure that the new percentages are positive. */
- if (new_first_percent > 0.0 && new_second_percent > 0.0) {
- first->percent = new_first_percent;
- second->percent = new_second_percent;
- LOG("first->percent after = %f\n", first->percent);
- LOG("second->percent after = %f\n", second->percent);
- } else {
- LOG("Not resizing, already at minimum size\n");
+ if (ppt) {
+ /* For backwards compatibility, 'X px or Y ppt' means that ppt is
+ * preferred. */
+ px = 0;
}
-
- return true;
+ return resize_neighboring_cons(first, second, px, ppt);
}
-static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
+static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *direction, int px, double ppt) {
LOG("width/height resize\n");
/* get the appropriate current container (skip stacked/tabbed cons) */
@@ -536,7 +514,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way
direction_t search_direction = (strcmp(direction, "width") == 0 ? D_LEFT : D_DOWN);
bool search_result = resize_find_tiling_participants(¤t, &dummy, search_direction, true);
if (search_result == false) {
- ysuccess(false);
+ yerror("Failed to find appropriate tiling containers for resize operation");
return false;
}
@@ -554,25 +532,34 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way
child->percent = percentage;
}
- double new_current_percent = current->percent + ((double)ppt / 100.0);
- double subtract_percent = ((double)ppt / 100.0) / (children - 1);
+ double new_current_percent;
+ double subtract_percent;
+ if (ppt != 0.0) {
+ new_current_percent = current->percent + ppt;
+ } else {
+ new_current_percent = px_resize_to_percent(current, px);
+ ppt = new_current_percent - current->percent;
+ }
+ subtract_percent = ppt / (children - 1);
+ if (ppt < 0.0 && new_current_percent < percent_for_1px(current)) {
+ yerror("Not resizing, container would end with less than 1px");
+ return false;
+ }
+
LOG("new_current_percent = %f\n", new_current_percent);
LOG("subtract_percent = %f\n", subtract_percent);
/* Ensure that the new percentages are positive. */
- TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
- if (child == current)
- continue;
- if (child->percent - subtract_percent <= 0.0) {
- LOG("Not resizing, already at minimum size (child %p would end up with a size of %.f\n", child, child->percent - subtract_percent);
- ysuccess(false);
- return false;
+ if (subtract_percent >= 0.0) {
+ TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
+ if (child == current) {
+ continue;
+ }
+ if (child->percent - subtract_percent < percent_for_1px(child)) {
+ yerror("Not resizing, already at minimum size (child %p would end up with a size of %.f", child, child->percent - subtract_percent);
+ return false;
+ }
}
}
- if (new_current_percent <= 0.0) {
- LOG("Not resizing, already at minimum size\n");
- ysuccess(false);
- return false;
- }
current->percent = new_current_percent;
LOG("current->percent after = %f\n", current->percent);
@@ -614,12 +601,15 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
} else {
if (strcmp(direction, "width") == 0 ||
strcmp(direction, "height") == 0) {
+ const double ppt = (double)resize_ppt / 100.0;
if (!cmd_resize_tiling_width_height(current_match, cmd_output,
- current->con, way, direction, resize_ppt))
+ current->con, direction,
+ resize_px, ppt))
return;
} else {
if (!cmd_resize_tiling_direction(current_match, cmd_output,
- current->con, way, direction, resize_ppt))
+ current->con, direction,
+ resize_px, resize_ppt))
return;
}
}
@@ -630,6 +620,35 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
ysuccess(true);
}
+static bool resize_set_tiling(I3_CMD, Con *target, orientation_t resize_orientation, bool is_ppt, long target_size) {
+ direction_t search_direction;
+ char *mode;
+ if (resize_orientation == HORIZ) {
+ search_direction = D_LEFT;
+ mode = "width";
+ } else {
+ search_direction = D_DOWN;
+ mode = "height";
+ }
+
+ /* Get the appropriate current container (skip stacked/tabbed cons) */
+ Con *dummy;
+ resize_find_tiling_participants(&target, &dummy, search_direction, true);
+
+ /* Calculate new size for the target container */
+ double ppt = 0.0;
+ int px = 0;
+ if (is_ppt) {
+ ppt = (double)target_size / 100.0 - target->percent;
+ } else {
+ px = target_size - (resize_orientation == HORIZ ? target->rect.width : target->rect.height);
+ }
+
+ /* Perform resizing and report failure if not possible */
+ return cmd_resize_tiling_width_height(current_match, cmd_output,
+ target, mode, px, ppt);
+}
+
/*
* Implementation of 'resize set [px | ppt] [px | ppt]'.
*
@@ -650,12 +669,12 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
if ((floating_con = con_inside_floating(current->con))) {
Con *output = con_get_output(floating_con);
if (cwidth == 0) {
- cwidth = output->rect.width;
+ cwidth = floating_con->rect.width;
} else if (mode_width && strcmp(mode_width, "ppt") == 0) {
cwidth = output->rect.width * ((double)cwidth / 100.0);
}
if (cheight == 0) {
- cheight = output->rect.height;
+ cheight = floating_con->rect.height;
} else if (mode_height && strcmp(mode_height, "ppt") == 0) {
cheight = output->rect.height * ((double)cheight / 100.0);
}
@@ -666,56 +685,15 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
continue;
}
- if (cwidth > 0 && mode_width && strcmp(mode_width, "ppt") == 0) {
- /* get the appropriate current container (skip stacked/tabbed cons) */
- Con *target = current->con;
- Con *dummy;
- resize_find_tiling_participants(&target, &dummy, D_LEFT, true);
-
- /* Calculate new size for the target container */
- double current_percent = target->percent;
- char *action_string;
- long adjustment;
-
- if (current_percent > cwidth) {
- action_string = "shrink";
- adjustment = (int)(current_percent * 100) - cwidth;
- } else {
- action_string = "grow";
- adjustment = cwidth - (int)(current_percent * 100);
- }
-
- /* perform resizing and report failure if not possible */
- if (!cmd_resize_tiling_width_height(current_match, cmd_output,
- target, action_string, "width", adjustment)) {
- success = false;
- }
+ if (cwidth > 0) {
+ bool is_ppt = mode_width && strcmp(mode_width, "ppt") == 0;
+ success &= resize_set_tiling(current_match, cmd_output, current->con,
+ HORIZ, is_ppt, cwidth);
}
-
- if (cheight > 0 && mode_width && strcmp(mode_width, "ppt") == 0) {
- /* get the appropriate current container (skip stacked/tabbed cons) */
- Con *target = current->con;
- Con *dummy;
- resize_find_tiling_participants(&target, &dummy, D_DOWN, true);
-
- /* Calculate new size for the target container */
- double current_percent = target->percent;
- char *action_string;
- long adjustment;
-
- if (current_percent > cheight) {
- action_string = "shrink";
- adjustment = (int)(current_percent * 100) - cheight;
- } else {
- action_string = "grow";
- adjustment = cheight - (int)(current_percent * 100);
- }
-
- /* perform resizing and report failure if not possible */
- if (!cmd_resize_tiling_width_height(current_match, cmd_output,
- target, action_string, "height", adjustment)) {
- success = false;
- }
+ if (cheight > 0) {
+ bool is_ppt = mode_height && strcmp(mode_height, "ppt") == 0;
+ success &= resize_set_tiling(current_match, cmd_output, current->con,
+ VERT, is_ppt, cheight);
}
}
}
@@ -724,6 +702,26 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
ysuccess(success);
}
+static int border_width_from_style(border_style_t border_style, long border_width, Con *con) {
+ if (border_style == BS_NONE) {
+ return 0;
+ }
+ if (border_width >= 0) {
+ return logical_px(border_width);
+ }
+
+ const bool is_floating = con_inside_floating(con) != NULL;
+ /* Load the configured defaults. */
+ if (is_floating && border_style == config.default_floating_border) {
+ return config.default_floating_border_width;
+ } else if (!is_floating && border_style == config.default_border) {
+ return config.default_border_width;
+ } else {
+ /* Use some hardcoded values. */
+ return logical_px(border_style == BS_NORMAL ? 2 : 1);
+ }
+}
+
/*
* Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'.
*
@@ -736,40 +734,26 @@ void cmd_border(I3_CMD, const char *border_style_str, long border_width) {
TAILQ_FOREACH(current, &owindows, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name);
- int border_style = current->con->border_style;
- int con_border_width = border_width;
+ border_style_t border_style;
if (strcmp(border_style_str, "toggle") == 0) {
- border_style++;
- border_style %= 3;
- if (border_style == BS_NORMAL)
- con_border_width = 2;
- else if (border_style == BS_NONE)
- con_border_width = 0;
- else if (border_style == BS_PIXEL)
- con_border_width = 1;
+ border_style = (current->con->border_style + 1) % 3;
+ } else if (strcmp(border_style_str, "normal") == 0) {
+ border_style = BS_NORMAL;
+ } else if (strcmp(border_style_str, "pixel") == 0) {
+ border_style = BS_PIXEL;
+ } else if (strcmp(border_style_str, "none") == 0) {
+ border_style = BS_NONE;
} else {
- if (strcmp(border_style_str, "normal") == 0) {
- border_style = BS_NORMAL;
- } else if (strcmp(border_style_str, "pixel") == 0) {
- border_style = BS_PIXEL;
- } else if (strcmp(border_style_str, "1pixel") == 0) {
- border_style = BS_PIXEL;
- con_border_width = 1;
- } else if (strcmp(border_style_str, "none") == 0) {
- border_style = BS_NONE;
- } else {
- ELOG("BUG: called with border_style=%s\n", border_style_str);
- ysuccess(false);
- return;
- }
+ yerror("BUG: called with border_style=%s", border_style_str);
+ return;
}
- con_set_border_style(current->con, border_style, logical_px(con_border_width));
+ const int con_border_width = border_width_from_style(border_style, border_width, current->con);
+ con_set_border_style(current->con, border_style, con_border_width);
}
cmd_output->needs_tree_render = true;
- // XXX: default reply for now, make this a better reply
ysuccess(true);
}
@@ -789,11 +773,10 @@ void cmd_nop(I3_CMD, const char *comment) {
*
*/
void cmd_append_layout(I3_CMD, const char *cpath) {
- char *path = sstrdup(cpath);
- LOG("Appending layout \"%s\"\n", path);
+ LOG("Appending layout \"%s\"\n", cpath);
/* Make sure we allow paths like '~/.i3/layout.json' */
- path = resolve_tilde(path);
+ char *path = resolve_tilde(cpath);
char *buf = NULL;
ssize_t len;
@@ -870,8 +853,7 @@ void cmd_workspace(I3_CMD, const char *which) {
DLOG("which=%s\n", which);
if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
- LOG("Cannot switch workspace while in global fullscreen\n");
- ysuccess(false);
+ yerror("Cannot switch workspace while in global fullscreen");
return;
}
@@ -884,8 +866,7 @@ void cmd_workspace(I3_CMD, const char *which) {
else if (strcmp(which, "prev_on_output") == 0)
ws = workspace_prev_on_output();
else {
- ELOG("BUG: called with which=%s\n", which);
- ysuccess(false);
+ yerror("BUG: called with which=%s", which);
return;
}
@@ -902,26 +883,19 @@ void cmd_workspace(I3_CMD, const char *which) {
*/
void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) {
const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
- Con *output, *workspace = NULL;
if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
- LOG("Cannot switch workspace while in global fullscreen\n");
- ysuccess(false);
+ yerror("Cannot switch workspace while in global fullscreen");
return;
}
long parsed_num = ws_name_to_number(which);
-
if (parsed_num == -1) {
- LOG("Could not parse initial part of \"%s\" as a number.\n", which);
- yerror("Could not parse number \"%s\"", which);
+ yerror("Could not parse initial part of \"%s\" as a number.", which);
return;
}
- TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
- GREP_FIRST(workspace, output_get_content(output),
- child->num == parsed_num);
-
+ Con *workspace = get_existing_workspace_by_num(parsed_num);
if (!workspace) {
LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num);
ysuccess(true);
@@ -946,8 +920,7 @@ void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_a
*/
void cmd_workspace_back_and_forth(I3_CMD) {
if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
- LOG("Cannot switch workspace while in global fullscreen\n");
- ysuccess(false);
+ yerror("Cannot switch workspace while in global fullscreen");
return;
}
@@ -966,14 +939,12 @@ void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_
const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
if (strncasecmp(name, "__", strlen("__")) == 0) {
- LOG("You cannot switch to the i3-internal workspaces (\"%s\").\n", name);
- ysuccess(false);
+ yerror("You cannot switch to the i3-internal workspaces (\"%s\").", name);
return;
}
if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
- LOG("Cannot switch workspace while in global fullscreen\n");
- ysuccess(false);
+ yerror("Cannot switch workspace while in global fullscreen");
return;
}
@@ -998,7 +969,7 @@ void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle) {
owindow *current = TAILQ_FIRST(&owindows);
if (current == NULL) {
- ysuccess(false);
+ yerror("Given criteria don't match a window");
return;
}
@@ -1140,16 +1111,26 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) {
continue;
}
- bool success = workspace_move_to_output(ws, name);
+ Output *current_output = get_output_for_con(ws);
+ if (current_output == NULL) {
+ yerror("Cannot get current output. This is a bug in i3.");
+ return;
+ }
+
+ Output *target_output = get_output_from_string(current_output, name);
+ if (!target_output) {
+ yerror("Could not get output from string \"%s\"", name);
+ return;
+ }
+
+ bool success = workspace_move_to_output(ws, target_output);
if (!success) {
- ELOG("Failed to move workspace to output.\n");
- ysuccess(false);
+ yerror("Failed to move workspace to output.");
return;
}
}
cmd_output->needs_tree_render = true;
- // XXX: default reply for now, make this a better reply
ysuccess(true);
}
@@ -1208,8 +1189,7 @@ void cmd_kill(I3_CMD, const char *kill_mode_str) {
else if (strcmp(kill_mode_str, "client") == 0)
kill_mode = KILL_CLIENT;
else {
- ELOG("BUG: called with kill_mode=%s\n", kill_mode_str);
- ysuccess(false);
+ yerror("BUG: called with kill_mode=%s", kill_mode_str);
return;
}
@@ -1235,7 +1215,6 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) {
DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
start_application(command, no_startup_id);
- // XXX: default reply for now, make this a better reply
ysuccess(true);
}
@@ -1244,20 +1223,19 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) {
*
*/
void cmd_focus_direction(I3_CMD, const char *direction) {
- DLOG("direction = *%s*\n", direction);
-
- if (strcmp(direction, "left") == 0)
- tree_next('p', HORIZ);
- else if (strcmp(direction, "right") == 0)
- tree_next('n', HORIZ);
- else if (strcmp(direction, "up") == 0)
- tree_next('p', VERT);
- else if (strcmp(direction, "down") == 0)
- tree_next('n', VERT);
- else {
- ELOG("Invalid focus direction (%s)\n", direction);
- ysuccess(false);
- return;
+ switch (parse_direction(direction)) {
+ case D_LEFT:
+ tree_next('p', HORIZ);
+ break;
+ case D_RIGHT:
+ tree_next('n', HORIZ);
+ break;
+ case D_UP:
+ tree_next('p', VERT);
+ break;
+ case D_DOWN:
+ tree_next('n', VERT);
+ break;
}
cmd_output->needs_tree_render = true;
@@ -1272,7 +1250,7 @@ void cmd_focus_direction(I3_CMD, const char *direction) {
static void cmd_focus_force_focus(Con *con) {
/* Disable fullscreen container in workspace with container to be focused. */
Con *ws = con_get_workspace(con);
- Con *fullscreen_on_ws = (focused && focused->fullscreen_mode == CF_GLOBAL) ? focused : con_get_fullscreen_con(ws, CF_OUTPUT);
+ Con *fullscreen_on_ws = con_get_fullscreen_covering_ws(ws);
if (fullscreen_on_ws && fullscreen_on_ws != con && !con_has_parent(con, fullscreen_on_ws)) {
con_disable_fullscreen(fullscreen_on_ws);
}
@@ -1482,29 +1460,37 @@ void cmd_sticky(I3_CMD, const char *action) {
* Implementation of 'move [ [px]]'.
*
*/
-void cmd_move_direction(I3_CMD, const char *direction, long move_px) {
+void cmd_move_direction(I3_CMD, const char *direction_str, long move_px) {
owindow *current;
HANDLE_EMPTY_MATCH;
Con *initially_focused = focused;
+ direction_t direction = parse_direction(direction_str);
TAILQ_FOREACH(current, &owindows, owindows) {
- DLOG("moving in direction %s, px %ld\n", direction, move_px);
+ DLOG("moving in direction %s, px %ld\n", direction_str, move_px);
if (con_is_floating(current->con)) {
DLOG("floating move with %ld pixels\n", move_px);
Rect newrect = current->con->parent->rect;
- if (strcmp(direction, "left") == 0) {
- newrect.x -= move_px;
- } else if (strcmp(direction, "right") == 0) {
- newrect.x += move_px;
- } else if (strcmp(direction, "up") == 0) {
- newrect.y -= move_px;
- } else if (strcmp(direction, "down") == 0) {
- newrect.y += move_px;
+
+ switch (direction) {
+ case D_LEFT:
+ newrect.x -= move_px;
+ break;
+ case D_RIGHT:
+ newrect.x += move_px;
+ break;
+ case D_UP:
+ newrect.y -= move_px;
+ break;
+ case D_DOWN:
+ newrect.y += move_px;
+ break;
}
+
floating_reposition(current->con->parent, newrect);
} else {
- tree_move(current->con, (strcmp(direction, "right") == 0 ? D_RIGHT : (strcmp(direction, "left") == 0 ? D_LEFT : (strcmp(direction, "up") == 0 ? D_UP : D_DOWN))));
+ tree_move(current->con, direction);
cmd_output->needs_tree_render = true;
}
}
@@ -1670,8 +1656,7 @@ void cmd_focus_output(I3_CMD, const char *name) {
output = get_output_from_string(current_output, name);
if (!output) {
- LOG("No such output found.\n");
- ysuccess(false);
+ yerror("No such output found.");
return;
}
@@ -1679,7 +1664,7 @@ void cmd_focus_output(I3_CMD, const char *name) {
Con *ws = NULL;
GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
if (!ws) {
- ysuccess(false);
+ yerror("BUG: No workspace found on output.");
return;
}
@@ -1694,7 +1679,7 @@ void cmd_focus_output(I3_CMD, const char *name) {
* Implementation of 'move [window|container] [to] [absolute] position [px] [px]
*
*/
-void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) {
+void cmd_move_window_to_position(I3_CMD, long x, long y) {
bool has_error = false;
owindow *current;
@@ -1712,27 +1697,18 @@ void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) {
continue;
}
- if (strcmp(method, "absolute") == 0) {
- current->con->parent->rect.x = x;
- current->con->parent->rect.y = y;
+ Rect newrect = current->con->parent->rect;
- DLOG("moving to absolute position %ld %ld\n", x, y);
- floating_maybe_reassign_ws(current->con->parent);
- cmd_output->needs_tree_render = true;
- }
+ DLOG("moving to position %ld %ld\n", x, y);
+ newrect.x = x;
+ newrect.y = y;
- if (strcmp(method, "position") == 0) {
- Rect newrect = current->con->parent->rect;
-
- DLOG("moving to position %ld %ld\n", x, y);
- newrect.x = x;
- newrect.y = y;
-
- floating_reposition(current->con->parent, newrect);
+ if (!floating_reposition(current->con->parent, newrect)) {
+ yerror("Cannot move window/container out of bounds.");
+ has_error = true;
}
}
- // XXX: default reply for now, make this a better reply
if (!has_error)
ysuccess(true);
}
@@ -1832,19 +1808,20 @@ void cmd_move_scratchpad(I3_CMD) {
void cmd_scratchpad_show(I3_CMD) {
DLOG("should show scratchpad window\n");
owindow *current;
+ bool result = false;
if (match_is_empty(current_match)) {
- scratchpad_show(NULL);
+ result = scratchpad_show(NULL);
} else {
TAILQ_FOREACH(current, &owindows, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name);
- scratchpad_show(current->con);
+ result |= scratchpad_show(current->con);
}
}
cmd_output->needs_tree_render = true;
- // XXX: default reply for now, make this a better reply
- ysuccess(true);
+
+ ysuccess(result);
}
/*
@@ -1856,7 +1833,11 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
owindow *match = TAILQ_FIRST(&owindows);
if (match == NULL) {
- DLOG("No match found for swapping.\n");
+ yerror("No match found for swapping.");
+ return;
+ }
+ if (match->con == NULL) {
+ yerror("Match %p has no container.", match);
return;
}
@@ -1864,7 +1845,7 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
if (strcmp(mode, "id") == 0) {
long target;
if (!parse_long(arg, &target, 0)) {
- yerror("Failed to parse %s into a window id.\n", arg);
+ yerror("Failed to parse %s into a window id.", arg);
return;
}
@@ -1872,7 +1853,7 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
} else if (strcmp(mode, "con_id") == 0) {
long target;
if (!parse_long(arg, &target, 0)) {
- yerror("Failed to parse %s into a container id.\n", arg);
+ yerror("Failed to parse %s into a container id.", arg);
return;
}
@@ -1880,29 +1861,24 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
} else if (strcmp(mode, "mark") == 0) {
con = con_by_mark(arg);
} else {
- yerror("Unhandled swap mode \"%s\". This is a bug.\n", mode);
+ yerror("Unhandled swap mode \"%s\". This is a bug.", mode);
return;
}
if (con == NULL) {
- yerror("Could not find container for %s = %s\n", mode, arg);
+ yerror("Could not find container for %s = %s", mode, arg);
return;
}
if (match != TAILQ_LAST(&owindows, owindows_head)) {
- DLOG("More than one container matched the swap command, only using the first one.");
- }
-
- if (match->con == NULL) {
- DLOG("Match %p has no container.\n", match);
- ysuccess(false);
- return;
+ LOG("More than one container matched the swap command, only using the first one.");
}
DLOG("Swapping %p with %p.\n", match->con, con);
bool result = con_swap(match->con, con);
cmd_output->needs_tree_render = true;
+ // XXX: default reply for now, make this a better reply
ysuccess(result);
}
@@ -1955,8 +1931,7 @@ void cmd_title_format(I3_CMD, const char *format) {
*/
void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
if (strncasecmp(new_name, "__", strlen("__")) == 0) {
- LOG("Cannot rename workspace to \"%s\": names starting with __ are i3-internal.\n", new_name);
- ysuccess(false);
+ yerror("Cannot rename workspace to \"%s\": names starting with __ are i3-internal.", new_name);
return;
}
if (old_name) {
@@ -1965,11 +1940,9 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
LOG("Renaming current workspace to \"%s\"\n", new_name);
}
- Con *output, *workspace = NULL;
+ Con *workspace;
if (old_name) {
- TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
- GREP_FIRST(workspace, output_get_content(output),
- !strcasecmp(child->name, old_name));
+ workspace = get_existing_workspace_by_name(old_name);
} else {
workspace = con_get_workspace(focused);
old_name = workspace->name;
@@ -1980,10 +1953,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
return;
}
- Con *check_dest = NULL;
- TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
- GREP_FIRST(check_dest, output_get_content(output),
- !strcasecmp(child->name, new_name));
+ Con *check_dest = get_existing_workspace_by_name(new_name);
/* If check_dest == workspace, the user might be changing the case of the
* workspace, or it might just be a no-op. */
@@ -2003,9 +1973,11 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
/* By re-attaching, the sort order will be correct afterwards. */
Con *previously_focused = focused;
+ Con *previously_focused_content = focused->type == CT_WORKSPACE ? focused->parent : NULL;
Con *parent = workspace->parent;
con_detach(workspace);
con_attach(workspace, parent, false);
+ ipc_send_workspace_event("rename", workspace, NULL);
/* Move the workspace to the correct output if it has an assignment */
struct Workspace_Assignment *assignment = NULL;
@@ -2016,21 +1988,40 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
continue;
}
- workspace_move_to_output(workspace, assignment->output);
-
- if (previously_focused)
- workspace_show(con_get_workspace(previously_focused));
+ Output *target_output = get_output_by_name(assignment->output, true);
+ if (!target_output) {
+ LOG("Could not get output named \"%s\"\n", assignment->output);
+ continue;
+ }
+ if (!output_triggers_assignment(target_output, assignment)) {
+ continue;
+ }
+ workspace_move_to_output(workspace, target_output);
break;
}
- /* Restore the previous focus since con_attach messes with the focus. */
- con_activate(previously_focused);
+ bool can_restore_focus = previously_focused != NULL;
+ /* NB: If previously_focused is a workspace we can't work directly with it
+ * since it might have been cleaned up by workspace_show() already,
+ * depending on the focus order/number of other workspaces on the output.
+ * Instead, we loop through the available workspaces and only focus
+ * previously_focused if we still find it. */
+ if (previously_focused_content) {
+ Con *workspace = NULL;
+ GREP_FIRST(workspace, previously_focused_content, child == previously_focused);
+ can_restore_focus &= (workspace != NULL);
+ }
+
+ if (can_restore_focus) {
+ /* Restore the previous focus since con_attach messes with the focus. */
+ workspace_show(con_get_workspace(previously_focused));
+ con_focus(previously_focused);
+ }
cmd_output->needs_tree_render = true;
ysuccess(true);
- ipc_send_workspace_event("rename", workspace, NULL);
ewmh_update_desktop_names();
ewmh_update_desktop_viewport();
ewmh_update_current_desktop();
@@ -2043,7 +2034,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
* Implementation of 'bar mode dock|hide|invisible|toggle []'
*
*/
-bool cmd_bar_mode(const char *bar_mode, const char *bar_id) {
+static bool cmd_bar_mode(const char *bar_mode, const char *bar_id) {
int mode = M_DOCK;
bool toggle = false;
if (strcmp(bar_mode, "dock") == 0)
@@ -2088,7 +2079,7 @@ bool cmd_bar_mode(const char *bar_mode, const char *bar_id) {
* Implementation of 'bar hidden_state hide|show|toggle []'
*
*/
-bool cmd_bar_hidden_state(const char *bar_hidden_state, const char *bar_id) {
+static bool cmd_bar_hidden_state(const char *bar_hidden_state, const char *bar_id) {
int hidden_state = S_SHOW;
bool toggle = false;
if (strcmp(bar_hidden_state, "hide") == 0)
@@ -2162,21 +2153,22 @@ void cmd_shmlog(I3_CMD, const char *argument) {
else if (!strcmp(argument, "off"))
shmlog_size = 0;
else {
+ long new_size = 0;
+ if (!parse_long(argument, &new_size, 0)) {
+ yerror("Failed to parse %s into a shmlog size.", argument);
+ return;
+ }
/* If shm logging now, restart logging with the new size. */
if (shmlog_size > 0) {
shmlog_size = 0;
LOG("Restarting shm logging...\n");
init_logging();
}
- shmlog_size = atoi(argument);
- /* Make a weakly attempt at ensuring the argument is valid. */
- if (shmlog_size <= 0)
- shmlog_size = default_shmlog_size;
+ shmlog_size = (int)new_size;
}
LOG("%s shm logging\n", shmlog_size > 0 ? "Enabling" : "Disabling");
init_logging();
update_shmlog_atom();
- // XXX: default reply for now, make this a better reply
ysuccess(true);
}
diff --git a/src/commands_parser.c b/src/commands_parser.c
index 98f06659..4299c008 100644
--- a/src/commands_parser.c
+++ b/src/commands_parser.c
@@ -157,7 +157,7 @@ static long get_long(const char *identifier) {
// TODO move to a common util
static void clear_stack(void) {
for (int c = 0; c < 10; c++) {
- if (stack[c].type == STACK_STR && stack[c].val.str != NULL)
+ if (stack[c].type == STACK_STR)
free(stack[c].val.str);
stack[c].identifier = NULL;
stack[c].val.str = NULL;
diff --git a/src/con.c b/src/con.c
index 985d07da..21d2f097 100644
--- a/src/con.c
+++ b/src/con.c
@@ -286,14 +286,14 @@ void con_close(Con *con, kill_window_t kill_window) {
for (child = TAILQ_FIRST(&(con->focus_head)); child;) {
nextchild = TAILQ_NEXT(child, focused);
DLOG("killing child = %p.\n", child);
- tree_close_internal(child, kill_window, false, false);
+ tree_close_internal(child, kill_window, false);
child = nextchild;
}
return;
}
- tree_close_internal(con, kill_window, false, false);
+ tree_close_internal(con, kill_window, false);
}
/*
@@ -312,7 +312,7 @@ bool con_has_managed_window(Con *con) {
return (con != NULL && con->window != NULL && con->window->id != XCB_WINDOW_NONE && con_get_workspace(con) != NULL);
}
-/**
+/*
* Returns true if this node has regular or floating children.
*
*/
@@ -509,7 +509,24 @@ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) {
return NULL;
}
-/**
+/*
+ * Returns the fullscreen node that covers the given workspace if it exists.
+ * This is either a CF_GLOBAL fullscreen container anywhere or a CF_OUTPUT
+ * fullscreen container in the workspace.
+ *
+ */
+Con *con_get_fullscreen_covering_ws(Con *ws) {
+ if (!ws) {
+ return NULL;
+ }
+ Con *fs = con_get_fullscreen_con(croot, CF_GLOBAL);
+ if (!fs) {
+ return con_get_fullscreen_con(ws, CF_OUTPUT);
+ }
+ return fs;
+}
+
+/*
* Returns true if the container is internal, such as __i3_scratch
*
*/
@@ -879,7 +896,7 @@ int con_num_children(Con *con) {
return children;
}
-/**
+/*
* Returns the number of visible non-floating children of this container.
* For example, if the container contains a hsplit which has two children,
* this will return 2 instead of 1.
@@ -919,6 +936,10 @@ int con_num_windows(Con *con) {
num += con_num_windows(current);
}
+ TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
+ num += con_num_windows(current);
+ }
+
return num;
}
@@ -1097,7 +1118,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
/* Prevent moving if this would violate the fullscreen focus restrictions. */
Con *target_ws = con_get_workspace(target);
- if (!con_fullscreen_permits_focusing(target_ws)) {
+ if (!ignore_focus && !con_fullscreen_permits_focusing(target_ws)) {
LOG("Cannot move out of a fullscreen container.\n");
return false;
}
@@ -1140,7 +1161,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
/* 1: save the container which is going to be focused after the current
* container is moved away */
- Con *focus_next = con_next_focused(con);
+ Con *focus_next = NULL;
+ if (!ignore_focus && source_ws == current_ws) {
+ focus_next = con_descend_focused(source_ws);
+ if (focus_next == con || con_has_parent(focus_next, con)) {
+ focus_next = con_next_focused(con);
+ }
+ }
/* 2: we go up one level, but only when target is a normal container */
if (target->type != CT_WORKSPACE) {
@@ -1148,13 +1175,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
target = target->parent;
}
- /* 3: if the target container is floating, we get the workspace instead.
- * Only tiling windows need to get inserted next to the current container.
- * */
- Con *floatingcon = con_inside_floating(target);
- if (floatingcon != NULL) {
+ /* 3: if the original target is the direct child of a floating container, we
+ * can't move con next to it - floating containers have only one child - so
+ * we get the workspace instead. */
+ if (target->type == CT_FLOATING_CON) {
DLOG("floatingcon, going up even further\n");
- target = floatingcon->parent;
+ orig_target = target;
+ target = target->parent;
}
if (con->type == CT_FLOATING_CON) {
@@ -1170,20 +1197,6 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
floating_fix_coordinates(con, &(source_output->rect), &(dest_output->rect));
} else
DLOG("Not fixing coordinates, fix_coordinates flag = %d\n", fix_coordinates);
-
- /* If moving to a visible workspace, call show so it can be considered
- * focused. Must do before attaching because workspace_show checks to see
- * if focused container is in its area. */
- if (!ignore_focus && workspace_is_visible(target_ws)) {
- workspace_show(target_ws);
-
- /* Don’t warp if told so (when dragging floating windows with the
- * mouse for example) */
- if (dont_warp)
- x_set_warp_to(NULL);
- else
- x_set_warp_to(&(con->rect));
- }
}
/* If moving a fullscreen container and the destination already has a
@@ -1217,20 +1230,21 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
/* We need to save the focused workspace on the output in case the
* new workspace is hidden and it's necessary to immediately switch
* back to the originally-focused workspace. */
- Con *old_focus = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head));
+ Con *old_focus_ws = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head));
+ Con *old_focus = focused;
con_activate(con_descend_focused(con));
- /* Restore focus if the output's focused workspace has changed. */
- if (con_get_workspace(focused) != old_focus)
+ if (old_focus_ws == current_ws && old_focus->type != CT_WORKSPACE) {
+ /* Restore focus to the currently focused container. */
con_activate(old_focus);
+ } else if (con_get_workspace(focused) != old_focus_ws) {
+ /* Restore focus if the output's focused workspace has changed. */
+ con_focus(con_descend_focused(old_focus_ws));
+ }
}
/* 7: when moving to another workspace, we leave the focus on the current
* workspace. (see also #809) */
-
- /* Descend focus stack in case focus_next is a workspace which can
- * occur if we move to the same workspace. Also show current workspace
- * to ensure it is focused. */
if (!ignore_focus) {
workspace_show(current_ws);
if (dont_warp) {
@@ -1241,7 +1255,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
/* Set focus only if con was on current workspace before moving.
* Otherwise we would give focus to some window on different workspace. */
- if (!ignore_focus && source_ws == current_ws)
+ if (focus_next)
con_activate(con_descend_focused(focus_next));
/* 8. If anything within the container is associated with a startup sequence,
@@ -1310,7 +1324,7 @@ bool con_move_to_mark(Con *con, const char *mark) {
return true;
}
- if (con->type == CT_WORKSPACE) {
+ if (target->type == CT_WORKSPACE) {
DLOG("target container is a workspace, simply moving the container there.\n");
con_move_to_workspace(con, target, true, false, false);
return true;
@@ -1418,20 +1432,16 @@ orientation_t con_orientation(Con *con) {
return HORIZ;
case L_DEFAULT:
- DLOG("Someone called con_orientation() on a con with L_DEFAULT, this is a bug in the code.\n");
+ ELOG("Someone called con_orientation() on a con with L_DEFAULT, this is a bug in the code.\n");
assert(false);
- return HORIZ;
case L_DOCKAREA:
case L_OUTPUT:
- DLOG("con_orientation() called on dockarea/output (%d) container %p\n", con->layout, con);
- assert(false);
- return HORIZ;
-
- default:
- DLOG("con_orientation() ran into default\n");
+ ELOG("con_orientation() called on dockarea/output (%d) container %p\n", con->layout, con);
assert(false);
}
+ /* should not be reached */
+ assert(false);
}
/*
@@ -1441,52 +1451,20 @@ orientation_t con_orientation(Con *con) {
*
*/
Con *con_next_focused(Con *con) {
- Con *next;
- /* floating containers are attached to a workspace, so we focus either the
- * next floating container (if any) or the workspace itself. */
- if (con->type == CT_FLOATING_CON) {
- DLOG("selecting next for CT_FLOATING_CON\n");
- next = TAILQ_NEXT(con, floating_windows);
- DLOG("next = %p\n", next);
- if (!next) {
- next = TAILQ_PREV(con, floating_head, floating_windows);
- DLOG("using prev, next = %p\n", next);
- }
- if (!next) {
- Con *ws = con_get_workspace(con);
- next = ws;
- DLOG("no more floating containers for next = %p, restoring workspace focus\n", next);
- while (next != TAILQ_END(&(ws->focus_head)) && !TAILQ_EMPTY(&(next->focus_head))) {
- next = TAILQ_FIRST(&(next->focus_head));
- if (next == con) {
- DLOG("skipping container itself, we want the next client\n");
- next = TAILQ_NEXT(next, focused);
- }
- }
- if (next == TAILQ_END(&(ws->focus_head))) {
- DLOG("Focus list empty, returning ws\n");
- next = ws;
- }
- } else {
- /* Instead of returning the next CT_FLOATING_CON, we descend it to
- * get an actual window to focus. */
- next = con_descend_focused(next);
- }
- return next;
- }
-
/* dock clients cannot be focused, so we focus the workspace instead */
if (con->parent->type == CT_DOCKAREA) {
DLOG("selecting workspace for dock client\n");
return con_descend_focused(output_get_content(con->parent->parent));
}
+ if (con_is_floating(con)) {
+ con = con->parent;
+ }
/* if 'con' is not the first entry in the focus stack, use the first one as
* it’s currently focused already */
- Con *first = TAILQ_FIRST(&(con->parent->focus_head));
- if (first != con) {
- DLOG("Using first entry %p\n", first);
- next = first;
+ Con *next = TAILQ_FIRST(&(con->parent->focus_head));
+ if (next != con) {
+ DLOG("Using first entry %p\n", next);
} else {
/* try to focus the next container on the same level as this one or fall
* back to its parent */
@@ -1501,6 +1479,10 @@ Con *con_next_focused(Con *con) {
next = TAILQ_FIRST(&(next->focus_head));
}
+ if (con->type == CT_FLOATING_CON && next != con->parent) {
+ next = con_descend_focused(next);
+ }
+
return next;
}
@@ -1734,8 +1716,7 @@ adjacent_t con_adjacent_borders(Con *con) {
*
*/
int con_border_style(Con *con) {
- Con *fs = con_get_fullscreen_con(con->parent, CF_OUTPUT);
- if (fs == con) {
+ if (con->fullscreen_mode == CF_OUTPUT || con->fullscreen_mode == CF_GLOBAL) {
DLOG("this one is fullscreen! overriding BS_NONE\n");
return BS_NONE;
}
@@ -1935,7 +1916,6 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
* now let's activate the current layout (next in list) */
if (current_layout_found) {
new_layout = layout;
- free(tm_dup);
break;
}
@@ -1943,6 +1923,7 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
current_layout_found = true;
}
}
+ free(tm_dup);
if (new_layout != L_DEFAULT) {
con_set_layout(con, new_layout);
@@ -1995,7 +1976,7 @@ static void con_on_remove_child(Con *con) {
if (TAILQ_EMPTY(&(con->focus_head)) && !workspace_is_visible(con)) {
LOG("Closing old workspace (%p / %s), it is empty\n", con, con->name);
yajl_gen gen = ipc_marshal_workspace_event("empty", con, NULL);
- tree_close_internal(con, DONT_KILL_WINDOW, false, false);
+ tree_close_internal(con, DONT_KILL_WINDOW, false);
const unsigned char *payload;
ylength length;
@@ -2016,7 +1997,7 @@ static void con_on_remove_child(Con *con) {
int children = con_num_children(con);
if (children == 0) {
DLOG("Container empty, closing\n");
- tree_close_internal(con, DONT_KILL_WINDOW, false, false);
+ tree_close_internal(con, DONT_KILL_WINDOW, false);
return;
}
}
@@ -2413,6 +2394,10 @@ bool con_swap(Con *first, Con *second) {
/* Move first to second. */
result &= _con_move_to_con(first, second, false, false, false, true, false);
+ /* If swapping the containers didn't work we don't need to mess with the focus. */
+ if (!result) {
+ goto swap_end;
+ }
/* If we moved the container holding the focused window to another
* workspace we need to ensure the visible workspace has the focused
@@ -2425,8 +2410,6 @@ bool con_swap(Con *first, Con *second) {
/* Move second to where first has been originally. */
result &= _con_move_to_con(second, fake, false, false, false, true, false);
-
- /* If swapping the containers didn't work we don't need to mess with the focus. */
if (!result) {
goto swap_end;
}
diff --git a/src/config.c b/src/config.c
index 24c7b541..9631b216 100644
--- a/src/config.c
+++ b/src/config.c
@@ -18,7 +18,7 @@ Config config;
struct modes_head modes;
struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);
-/**
+/*
* Ungrabs all keys, to be called before re-grabbing the keys because of a
* mapping_notify event or a configuration file reload
*
@@ -32,7 +32,7 @@ void ungrab_all_keys(xcb_connection_t *conn) {
* Sends the current bar configuration as an event to all barconfig_update listeners.
*
*/
-void update_barconfig() {
+void update_barconfig(void) {
Barconfig *current;
TAILQ_FOREACH(current, &barconfigs, configs) {
ipc_send_barconfig_update_event(current);
@@ -49,7 +49,8 @@ bool parse_configuration(const char *override_configpath, bool use_nagbar) {
char *path = get_config_path(override_configpath, true);
if (path == NULL) {
die("Unable to find the configuration file (looked at "
- "~/.i3/config, $XDG_CONFIG_HOME/i3/config, " SYSCONFDIR "/i3/config and $XDG_CONFIG_DIRS/i3/config)");
+ "$XDG_CONFIG_HOME/i3/config, ~/.i3/config, $XDG_CONFIG_DIRS/i3/config "
+ "and " SYSCONFDIR "/i3/config)");
}
LOG("Parsing configfile %s\n", path);
@@ -96,18 +97,27 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
FREE(mode);
}
- struct Assignment *assign;
while (!TAILQ_EMPTY(&assignments)) {
- assign = TAILQ_FIRST(&assignments);
- if (assign->type == A_TO_WORKSPACE)
+ struct Assignment *assign = TAILQ_FIRST(&assignments);
+ if (assign->type == A_TO_WORKSPACE || assign->type == A_TO_WORKSPACE_NUMBER)
FREE(assign->dest.workspace);
else if (assign->type == A_COMMAND)
FREE(assign->dest.command);
+ else if (assign->type == A_TO_OUTPUT)
+ FREE(assign->dest.output);
match_free(&(assign->match));
TAILQ_REMOVE(&assignments, assign, assignments);
FREE(assign);
}
+ while (!TAILQ_EMPTY(&ws_assignments)) {
+ struct Workspace_Assignment *assign = TAILQ_FIRST(&ws_assignments);
+ FREE(assign->name);
+ FREE(assign->output);
+ TAILQ_REMOVE(&ws_assignments, assign, ws_assignments);
+ FREE(assign);
+ }
+
/* Clear bar configs */
Barconfig *barconfig;
while (!TAILQ_EMPTY(&barconfigs)) {
@@ -160,10 +170,16 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
FREE(barconfig);
}
- /* Invalidate pixmap caches in case font or colors changed */
Con *con;
- TAILQ_FOREACH(con, &all_cons, all_cons)
- FREE(con->deco_render_params);
+ TAILQ_FOREACH(con, &all_cons, all_cons) {
+ /* Assignments changed, previously ran assignments are invalid. */
+ if (con->window) {
+ con->window->nr_assignments = 0;
+ FREE(con->window->ran_assignments);
+ }
+ /* Invalidate pixmap caches in case font or colors changed. */
+ FREE(con->deco_render_params);
+ }
/* Get rid of the current font */
free_font();
@@ -183,10 +199,6 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
bindings = default_mode->bindings;
-#define REQUIRED_OPTION(name) \
- if (config.name == NULL) \
- die("You did not specify required configuration option " #name "\n");
-
/* Clear the old config or initialize the data structure */
memset(&config, 0, sizeof(config));
diff --git a/src/config_directives.c b/src/config_directives.c
index ad6d65b5..0b01d54a 100644
--- a/src/config_directives.c
+++ b/src/config_directives.c
@@ -318,31 +318,46 @@ CFGFUN(focus_on_window_activation, const char *mode) {
DLOG("Set new focus_on_window_activation mode = %i.\n", config.focus_on_window_activation);
}
+CFGFUN(title_align, const char *alignment) {
+ if (strcmp(alignment, "left") == 0) {
+ config.title_align = ALIGN_LEFT;
+ } else if (strcmp(alignment, "center") == 0) {
+ config.title_align = ALIGN_CENTER;
+ } else if (strcmp(alignment, "right") == 0) {
+ config.title_align = ALIGN_RIGHT;
+ } else {
+ assert(false);
+ }
+}
+
CFGFUN(show_marks, const char *value) {
config.show_marks = eval_boolstr(value);
}
-CFGFUN(workspace, const char *workspace, const char *output) {
- DLOG("Assigning workspace \"%s\" to output \"%s\"\n", workspace, output);
+CFGFUN(workspace, const char *workspace, const char *outputs) {
+ DLOG("Assigning workspace \"%s\" to outputs \"%s\"\n", workspace, outputs);
/* Check for earlier assignments of the same workspace so that we
* don’t have assignments of a single workspace to different
* outputs */
struct Workspace_Assignment *assignment;
- bool duplicate = false;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (strcasecmp(assignment->name, workspace) == 0) {
ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
workspace);
- assignment->output = sstrdup(output);
- duplicate = true;
+ return;
}
}
- if (!duplicate) {
+
+ char *buf = sstrdup(outputs);
+ char *output = strtok(buf, " ");
+ while (output != NULL) {
assignment = scalloc(1, sizeof(struct Workspace_Assignment));
assignment->name = sstrdup(workspace);
assignment->output = sstrdup(output);
TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
+ output = strtok(NULL, " ");
}
+ free(buf);
}
CFGFUN(ipc_socket, const char *path) {
@@ -403,6 +418,11 @@ CFGFUN(assign_output, const char *output) {
return;
}
+ if (current_match->window_mode != WM_ANY) {
+ ELOG("Assignments using window mode (floating/tiling) is not supported\n");
+ return;
+ }
+
DLOG("New assignment, using above criteria, to output \"%s\".\n", output);
Assignment *assignment = scalloc(1, sizeof(Assignment));
match_copy(&(assignment->match), current_match);
@@ -417,6 +437,11 @@ CFGFUN(assign, const char *workspace, bool is_number) {
return;
}
+ if (current_match->window_mode != WM_ANY) {
+ ELOG("Assignments using window mode (floating/tiling) is not supported\n");
+ return;
+ }
+
if (is_number && ws_name_to_number(workspace) == -1) {
ELOG("Could not parse initial part of \"%s\" as a number.\n", workspace);
return;
@@ -443,6 +468,10 @@ CFGFUN(no_focus) {
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
}
+CFGFUN(ipc_kill_timeout, const long timeout_ms) {
+ ipc_set_kill_timeout(timeout_ms / 1000.0);
+}
+
/*******************************************************************************
* Bar configuration (i3bar)
******************************************************************************/
@@ -482,25 +511,8 @@ CFGFUN(bar_verbose, const char *verbose) {
current_bar->verbose = eval_boolstr(verbose);
}
-CFGFUN(bar_modifier, const char *modifier) {
- if (strcmp(modifier, "Mod1") == 0)
- current_bar->modifier = M_MOD1;
- else if (strcmp(modifier, "Mod2") == 0)
- current_bar->modifier = M_MOD2;
- else if (strcmp(modifier, "Mod3") == 0)
- current_bar->modifier = M_MOD3;
- else if (strcmp(modifier, "Mod4") == 0)
- current_bar->modifier = M_MOD4;
- else if (strcmp(modifier, "Mod5") == 0)
- current_bar->modifier = M_MOD5;
- else if (strcmp(modifier, "Control") == 0 ||
- strcmp(modifier, "Ctrl") == 0)
- current_bar->modifier = M_CONTROL;
- else if (strcmp(modifier, "Shift") == 0)
- current_bar->modifier = M_SHIFT;
- else if (strcmp(modifier, "none") == 0 ||
- strcmp(modifier, "off") == 0)
- current_bar->modifier = M_NONE;
+CFGFUN(bar_modifier, const char *modifiers) {
+ current_bar->modifier = modifiers ? event_state_from_str(modifiers) : XCB_NONE;
}
static void bar_configure_binding(const char *button, const char *release, const char *command) {
@@ -627,12 +639,16 @@ CFGFUN(bar_strip_workspace_numbers, const char *value) {
current_bar->strip_workspace_numbers = eval_boolstr(value);
}
+CFGFUN(bar_strip_workspace_name, const char *value) {
+ current_bar->strip_workspace_name = eval_boolstr(value);
+}
+
CFGFUN(bar_start) {
current_bar = scalloc(1, sizeof(struct Barconfig));
TAILQ_INIT(&(current_bar->bar_bindings));
TAILQ_INIT(&(current_bar->tray_outputs));
current_bar->tray_padding = 2;
- current_bar->modifier = M_MOD4;
+ current_bar->modifier = XCB_KEY_BUT_MASK_MOD_4;
}
CFGFUN(bar_finish) {
diff --git a/src/config_parser.c b/src/config_parser.c
index 2d3f3bb9..9f972fed 100644
--- a/src/config_parser.c
+++ b/src/config_parser.c
@@ -171,7 +171,7 @@ static long get_long(const char *identifier) {
static void clear_stack(void) {
for (int c = 0; c < 10; c++) {
- if (stack[c].type == STACK_STR && stack[c].val.str != NULL)
+ if (stack[c].type == STACK_STR)
free(stack[c].val.str);
stack[c].identifier = NULL;
stack[c].val.str = NULL;
diff --git a/src/ewmh.c b/src/ewmh.c
index f8422bda..e5dcafcb 100644
--- a/src/ewmh.c
+++ b/src/ewmh.c
@@ -284,6 +284,20 @@ void ewmh_update_sticky(xcb_window_t window, bool sticky) {
}
}
+/*
+ * Set or remove _NEW_WM_STATE_FOCUSED on the window.
+ *
+ */
+void ewmh_update_focused(xcb_window_t window, bool is_focused) {
+ if (is_focused) {
+ DLOG("Setting _NET_WM_STATE_FOCUSED for window = %d.\n", window);
+ xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
+ } else {
+ DLOG("Removing _NET_WM_STATE_FOCUSED for window = %d.\n", window);
+ xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
+ }
+}
+
/*
* Set up the EWMH hints on the root window.
*
diff --git a/src/floating.c b/src/floating.c
index e958153d..a99d0970 100644
--- a/src/floating.c
+++ b/src/floating.c
@@ -59,7 +59,7 @@ static void floating_set_hint_atom(Con *con, bool floating) {
xcb_flush(conn);
}
-/**
+/*
* Called when a floating window is created or resized.
* This function resizes the window if its size is higher or lower than the
* configured maximum/minimum size, respectively.
@@ -96,6 +96,18 @@ void floating_check_size(Con *floating_con) {
floating_con->rect.height += border_rect.height;
}
+ if (focused_con->window->max_width) {
+ floating_con->rect.width -= border_rect.width;
+ floating_con->rect.width = min(floating_con->rect.width, focused_con->window->max_width);
+ floating_con->rect.width += border_rect.width;
+ }
+
+ if (focused_con->window->max_height) {
+ floating_con->rect.height -= border_rect.height;
+ floating_con->rect.height = min(floating_con->rect.height, focused_con->window->max_height);
+ floating_con->rect.height += border_rect.height;
+ }
+
if (focused_con->window->height_increment &&
floating_con->rect.height >= focused_con->window->base_height + border_rect.height) {
floating_con->rect.height -= focused_con->window->base_height + border_rect.height;
@@ -176,11 +188,36 @@ void floating_enable(Con *con, bool automatic) {
return;
}
+ Con *focus_head_placeholder = NULL;
+ bool focus_before_parent = true;
+ if (!set_focus) {
+ /* Find recursively the ancestor container which is a child of our workspace.
+ * We need to reuse its focus position later. */
+ Con *ancestor = con;
+ while (ancestor->parent->type != CT_WORKSPACE) {
+ focus_before_parent &= TAILQ_FIRST(&(ancestor->parent->focus_head)) == ancestor;
+ ancestor = ancestor->parent;
+ }
+ /* 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
+ * 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}.
+ * We should avoid using the S_{i} container for our operations since it might get
+ * killed if it has no other children. So, the two possible positions are after S_{i-1}
+ * or before S_{i+1}.
+ */
+ if (focus_before_parent) {
+ focus_head_placeholder = TAILQ_PREV(ancestor, focus_head, focused);
+ } else {
+ focus_head_placeholder = TAILQ_NEXT(ancestor, focused);
+ }
+ }
+
/* 1: detach the container from its parent */
/* TODO: refactor this with tree_close_internal() */
- TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
- TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
-
+ con_detach(con);
con_fix_percent(con->parent);
/* 2: create a new container to render the decoration on, add
@@ -196,12 +233,23 @@ void floating_enable(Con *con, bool automatic) {
/* We insert nc already, even though its rect is not yet calculated. This
* is necessary because otherwise the workspace might be empty (and get
* closed in tree_close_internal()) even though it’s not. */
- if (set_focus) {
- TAILQ_INSERT_TAIL(&(ws->floating_head), nc, floating_windows);
+ TAILQ_INSERT_HEAD(&(ws->floating_head), nc, floating_windows);
+
+ struct focus_head *fh = &(ws->focus_head);
+ if (focus_before_parent) {
+ if (focus_head_placeholder) {
+ TAILQ_INSERT_AFTER(fh, focus_head_placeholder, nc, focused);
+ } else {
+ TAILQ_INSERT_HEAD(fh, nc, focused);
+ }
} else {
- TAILQ_INSERT_HEAD(&(ws->floating_head), nc, floating_windows);
+ if (focus_head_placeholder) {
+ TAILQ_INSERT_BEFORE(focus_head_placeholder, nc, focused);
+ } else {
+ /* Also used for the set_focus case */
+ TAILQ_INSERT_TAIL(fh, nc, focused);
+ }
}
- TAILQ_INSERT_TAIL(&(ws->focus_head), nc, focused);
/* check if the parent container is empty and close it if so */
if ((con->parent->type == CT_CON || con->parent->type == CT_FLOATING_CON) &&
@@ -210,7 +258,7 @@ void floating_enable(Con *con, bool automatic) {
Con *parent = con->parent;
/* clear the pointer before calling tree_close_internal in which the memory is freed */
con->parent = NULL;
- tree_close_internal(parent, DONT_KILL_WINDOW, false, false);
+ tree_close_internal(parent, DONT_KILL_WINDOW, false);
}
char *name;
@@ -284,10 +332,7 @@ void floating_enable(Con *con, bool automatic) {
/* Sanity check: Are the coordinates on the appropriate output? If not, we
* need to change them */
- Output *current_output = get_output_containing(nc->rect.x +
- (nc->rect.width / 2),
- nc->rect.y + (nc->rect.height / 2));
-
+ Output *current_output = get_output_from_rect(nc->rect);
Con *correct_output = con_get_output(ws);
if (!current_output || current_output->con != correct_output) {
DLOG("This floating window is on the wrong output, fixing coordinates (currently (%d, %d))\n",
@@ -295,11 +340,13 @@ void floating_enable(Con *con, bool automatic) {
/* If moving from one output to another, keep the relative position
* consistent (e.g. a centered dialog will remain centered). */
- if (current_output)
+ if (current_output) {
floating_fix_coordinates(nc, ¤t_output->con->rect, &correct_output->rect);
- else {
- nc->rect.x = correct_output->rect.x;
- nc->rect.y = correct_output->rect.y;
+ /* Make sure that the result is in the correct output. */
+ current_output = get_output_from_rect(nc->rect);
+ }
+ if (!current_output || current_output->con != correct_output) {
+ floating_center(nc, ws->rect);
}
}
@@ -320,21 +367,6 @@ void floating_enable(Con *con, bool automatic) {
if (set_focus)
con_activate(con);
- /* Check if we need to re-assign it to a different workspace because of its
- * coordinates and exit if that was done successfully. */
- if (floating_maybe_reassign_ws(nc)) {
- goto done;
- }
-
- /* Sanitize coordinates: Check if they are on any output */
- if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) {
- goto done;
- }
-
- ELOG("No output found at destination coordinates, centering floating window on current ws\n");
- floating_center(nc, ws->rect);
-
-done:
floating_set_hint_atom(nc, true);
ipc_send_window_event("floating", con);
}
@@ -345,45 +377,26 @@ void floating_disable(Con *con, bool automatic) {
return;
}
- const bool set_focus = (con == focused);
-
Con *ws = con_get_workspace(con);
- Con *parent = con->parent;
+ if (con_is_internal(ws)) {
+ LOG("Can't disable floating for container in internal workspace.\n");
+ return;
+ }
+ Con *tiling_focused = con_descend_tiling_focused(ws);
- /* 1: detach from parent container */
- TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
- TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
-
- /* 2: kill parent container */
- TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows);
- TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused);
- /* clear the pointer before calling tree_close_internal in which the memory is freed */
- con->parent = NULL;
- tree_close_internal(parent, DONT_KILL_WINDOW, true, false);
-
- /* 3: re-attach to the parent of the currently focused con on the workspace
- * this floating con was on */
- Con *focused = con_descend_tiling_focused(ws);
-
- /* if there is no other container on this workspace, focused will be the
- * workspace itself */
- if (focused->type == CT_WORKSPACE)
- con->parent = focused;
- else
- con->parent = focused->parent;
-
- /* con_fix_percent will adjust the percent value */
- con->percent = 0.0;
+ if (tiling_focused->type == CT_WORKSPACE) {
+ Con *parent = con->parent;
+ con_detach(con);
+ con->parent = NULL;
+ tree_close_internal(parent, DONT_KILL_WINDOW, true);
+ con_attach(con, tiling_focused, false);
+ con->percent = 0.0;
+ con_fix_percent(con->parent);
+ } else {
+ insert_con_into(con, tiling_focused, AFTER);
+ }
con->floating = FLOATING_USER_OFF;
-
- con_attach(con, con->parent, false);
-
- con_fix_percent(con->parent);
-
- if (set_focus)
- con_activate(con);
-
floating_set_hint_atom(con, false);
ipc_send_window_event("floating", con);
}
@@ -429,9 +442,7 @@ void floating_raise_con(Con *con) {
*
*/
bool floating_maybe_reassign_ws(Con *con) {
- Output *output = get_output_containing(
- con->rect.x + (con->rect.width / 2),
- con->rect.y + (con->rect.height / 2));
+ Output *output = get_output_from_rect(con->rect);
if (!output) {
ELOG("No output found at destination coordinates?\n");
@@ -542,8 +553,10 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
}
/* If the user cancelled, undo the changes. */
- if (drag_result == DRAG_REVERT)
+ if (drag_result == DRAG_REVERT) {
floating_reposition(con, initial_rect);
+ return;
+ }
/* If this is a scratchpad window, don't auto center it from now on. */
if (con->scratchpad_state == SCRATCHPAD_FRESH)
@@ -687,8 +700,7 @@ struct drag_x11_cb {
const void *extra;
};
-static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
- struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
+static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
xcb_motion_notify_event_t *last_motion_notify = NULL;
xcb_generic_event_t *event;
@@ -748,13 +760,20 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
free(event);
if (dragloop->result != DRAGGING) {
- free(last_motion_notify);
- return;
+ ev_break(EV_A_ EVBREAK_ONE);
+ if (dragloop->result == DRAG_SUCCESS) {
+ /* Ensure motion notify events are handled. */
+ break;
+ } else {
+ free(last_motion_notify);
+ return true;
+ }
}
}
- if (last_motion_notify == NULL)
- return;
+ if (last_motion_notify == NULL) {
+ return true;
+ }
/* Ensure that we are either dragging the resize handle (con is NULL) or that the
* container still exists. The latter might not be true, e.g., if the window closed
@@ -767,9 +786,17 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
last_motion_notify->root_y,
dragloop->extra);
}
- free(last_motion_notify);
+ FREE(last_motion_notify);
xcb_flush(conn);
+ return dragloop->result != DRAGGING;
+}
+
+static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
+ struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
+ while (!drain_drag_events(EV_A, dragloop)) {
+ /* repeatedly drain events: draining might produce additional ones */
+ }
}
/*
@@ -780,8 +807,7 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
* rect of the client, the event and the new coordinates (x, y).
*
*/
-drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
- confine_to,
+drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to,
border_t border, int cursor, callback_t callback, const void *extra) {
xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE;
@@ -844,8 +870,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
main_set_x11_cb(false);
ev_prepare_start(main_loop, prepare);
- while (loop.result == DRAGGING)
- ev_run(main_loop, EVRUN_ONCE);
+ ev_loop(main_loop, 0);
ev_prepare_stop(main_loop, prepare);
main_set_x11_cb(true);
@@ -864,23 +889,28 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
* outputs.
*
*/
-void floating_reposition(Con *con, Rect newrect) {
+bool floating_reposition(Con *con, Rect newrect) {
/* Sanity check: Are the new coordinates on any output? If not, we
* ignore that request. */
- if (!contained_by_output(newrect)) {
+ if (!output_containing_rect(newrect)) {
ELOG("No output found at destination coordinates. Not repositioning.\n");
- return;
+ return false;
}
con->rect = newrect;
- floating_maybe_reassign_ws(con);
+ bool reassigned = floating_maybe_reassign_ws(con);
/* If this is a scratchpad window, don't auto center it from now on. */
if (con->scratchpad_state == SCRATCHPAD_FRESH)
con->scratchpad_state = SCRATCHPAD_CHANGED;
- tree_render();
+ /* Workspace change will already result in a tree_render. */
+ if (!reassigned) {
+ render_con(con, false);
+ x_push_node(con);
+ }
+ return true;
}
/*
diff --git a/src/handlers.c b/src/handlers.c
index e1671c3b..b9677917 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -184,8 +184,6 @@ static void handle_enter_notify(xcb_enter_notify_event_t *event) {
focused_id = XCB_NONE;
con_focus(con_descend_focused(con));
tree_render();
-
- return;
}
/*
@@ -249,8 +247,6 @@ static void handle_mapping_notify(xcb_mapping_notify_event_t *event) {
ungrab_all_keys(conn);
translate_keysyms();
grab_all_keys(conn);
-
- return;
}
/*
@@ -266,7 +262,6 @@ static void handle_map_request(xcb_map_request_event_t *event) {
add_ignore_event(event->sequence, -1);
manage_window(event->window, cookie, false);
- return;
}
/*
@@ -315,16 +310,12 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
DLOG("Configure request!\n");
- Con *workspace = con_get_workspace(con),
- *fullscreen = NULL;
-
- /* There might not be a corresponding workspace for dock cons, therefore we
- * have to be careful here. */
- if (workspace) {
- fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
- if (!fullscreen)
- fullscreen = con_get_fullscreen_con(workspace, CF_GLOBAL);
+ Con *workspace = con_get_workspace(con);
+ if (workspace && (strcmp(workspace->name, "__i3_scratch") == 0)) {
+ DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
+ goto out;
}
+ Con *fullscreen = con_get_fullscreen_covering_ws(workspace);
if (fullscreen != con && con_is_floating(con) && con_is_leaf(con)) {
/* find the height for the decorations */
@@ -337,12 +328,6 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
bsr.height -= deco_height;
}
Con *floatingcon = con->parent;
-
- if (strcmp(con_get_workspace(floatingcon)->name, "__i3_scratch") == 0) {
- DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
- return;
- }
-
Rect newrect = floatingcon->rect;
if (event->value_mask & XCB_CONFIG_WINDOW_X) {
@@ -400,15 +385,14 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
DLOG("Dock client will not be moved, we only support moving it to another output.\n");
}
}
- fake_absolute_configure_notify(con);
- return;
+ goto out;
}
if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) {
DLOG("window 0x%08x wants to be stacked %d\n", event->window, event->stack_mode);
/* Emacs and IntelliJ Idea “request focus” by stacking their window
- * above all others. */
+ * above all others. */
if (event->stack_mode != XCB_STACK_MODE_ABOVE) {
DLOG("stack_mode != XCB_STACK_MODE_ABOVE, ignoring ConfigureRequest\n");
goto out;
@@ -419,23 +403,17 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
goto out;
}
- Con *ws = con_get_workspace(con);
- if (ws == NULL) {
+ if (workspace == NULL) {
DLOG("Window is not being managed, ignoring ConfigureRequest\n");
goto out;
}
- if (strcmp(ws->name, "__i3_scratch") == 0) {
- DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
- goto out;
- }
-
- if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
+ if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(workspace))) {
DLOG("Focusing con = %p\n", con);
- workspace_show(ws);
+ workspace_show(workspace);
con_activate(con);
tree_render();
- } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
+ } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(workspace))) {
DLOG("Marking con = %p urgent\n", con);
con_set_urgency(con, true);
tree_render();
@@ -474,8 +452,6 @@ static void handle_screen_change(xcb_generic_event_t *e) {
scratchpad_fix_resolution();
ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
-
- return;
}
/*
@@ -518,7 +494,7 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) {
xcb_delete_property(conn, event->window, A__NET_WM_DESKTOP);
xcb_delete_property(conn, event->window, A__NET_WM_STATE);
- tree_close_internal(con, DONT_KILL_WINDOW, false, false);
+ tree_close_internal(con, DONT_KILL_WINDOW, false);
tree_render();
ignore_end:
@@ -659,7 +635,6 @@ static void handle_expose_event(xcb_expose_event_t *event) {
draw_util_copy_surface(&(parent->frame_buffer), &(parent->frame),
0, 0, 0, 0, parent->rect.width, parent->rect.height);
xcb_flush(conn);
- return;
}
#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0
@@ -800,21 +775,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
} else if (event->type == A_I3_SYNC) {
xcb_window_t window = event->data.data32[0];
uint32_t rnd = event->data.data32[1];
- DLOG("[i3 sync protocol] Sending random value %d back to X11 window 0x%08x\n", rnd, window);
-
- void *reply = scalloc(32, 1);
- xcb_client_message_event_t *ev = reply;
-
- ev->response_type = XCB_CLIENT_MESSAGE;
- ev->window = window;
- ev->type = A_I3_SYNC;
- ev->format = 32;
- ev->data.data32[0] = window;
- ev->data.data32[1] = rnd;
-
- xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)ev);
- xcb_flush(conn);
- free(reply);
+ sync_respond(window, rnd);
} else if (event->type == A__NET_REQUEST_FRAME_EXTENTS) {
/*
* A client can request an estimate for the frame size which the window
@@ -834,7 +795,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
Rect r = {
config.default_border_width, /* left */
config.default_border_width, /* right */
- config.font.height + 5, /* top */
+ render_deco_height(), /* top */
config.default_border_width /* bottom */
};
xcb_change_property(
@@ -845,6 +806,18 @@ static void handle_client_message(xcb_client_message_event_t *event) {
XCB_ATOM_CARDINAL, 32, 4,
&r);
xcb_flush(conn);
+ } 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;
+ * immediately revert to normal to avoid being stuck in a paused state. */
+ DLOG("Client has requested iconic state, rejecting. (window = %d)\n", event->window);
+ long data[] = {XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE};
+ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, event->window,
+ A_WM_STATE, A_WM_STATE, 32, 2, data);
+ } else {
+ DLOG("Not handling WM_CHANGE_STATE request. (window = %d, state = %d)\n", event->window, event->data.data32[0]);
+ }
} else if (event->type == A__NET_CURRENT_DESKTOP) {
/* This request is used by pagers and bars to change the current
* desktop likely as a result of some user action. We interpret this as
@@ -906,7 +879,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
if (event->data.data32[0])
last_timestamp = event->data.data32[0];
- tree_close_internal(con, KILL_WINDOW, false, false);
+ tree_close_internal(con, KILL_WINDOW, false);
tree_render();
} else {
DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window);
@@ -935,7 +908,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
case _NET_WM_MOVERESIZE_MOVE:
floating_drag_window(con->parent, &fake);
break;
- case _NET_WM_MOVERESIZE_SIZE_TOPLEFT... _NET_WM_MOVERESIZE_SIZE_LEFT:
+ case _NET_WM_MOVERESIZE_SIZE_TOPLEFT ... _NET_WM_MOVERESIZE_SIZE_LEFT:
floating_resize_window(con->parent, false, &fake);
break;
default:
@@ -976,8 +949,8 @@ static void handle_client_message(xcb_client_message_event_t *event) {
}
}
-bool handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
- xcb_atom_t atom, xcb_get_property_reply_t *reply) {
+static bool handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
+ xcb_atom_t atom, xcb_get_property_reply_t *reply) {
Con *con;
if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
return false;
@@ -1020,9 +993,18 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
con->window->min_height = size_hints.min_height;
}
+ if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) {
+ DLOG("Maximum size: %d (width) x %d (height)\n", size_hints.max_width, size_hints.max_height);
+
+ con->window->max_width = size_hints.max_width;
+ con->window->max_height = size_hints.max_height;
+ }
+
if (con_is_floating(con)) {
win_width = MAX(win_width, con->window->min_width);
win_height = MAX(win_height, con->window->min_height);
+ win_width = MIN(win_width, con->window->max_width);
+ win_height = MIN(win_height, con->window->max_height);
}
bool changed = false;
@@ -1090,7 +1072,7 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
/* Convert numerator/denominator to a double */
double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
- double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den;
+ double max_aspect = (double)size_hints.max_aspect_num / size_hints.max_aspect_den;
DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
DLOG("width = %f, height = %f\n", width, height);
@@ -1163,8 +1145,7 @@ static bool handle_transient_for(void *data, xcb_connection_t *conn, uint8_t sta
}
if (prop == NULL) {
- prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
- false, window, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 0, 32),
+ prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 0, 32),
NULL);
if (prop == NULL)
return false;
@@ -1187,8 +1168,7 @@ static bool handle_clientleader_change(void *data, xcb_connection_t *conn, uint8
return false;
if (prop == NULL) {
- prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
- false, window, A_WM_CLIENT_LEADER, XCB_ATOM_WINDOW, 0, 32),
+ prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, A_WM_CLIENT_LEADER, XCB_ATOM_WINDOW, 0, 32),
NULL);
if (prop == NULL)
return false;
@@ -1207,6 +1187,14 @@ static bool handle_clientleader_change(void *data, xcb_connection_t *conn, uint8
*/
static void handle_focus_in(xcb_focus_in_event_t *event) {
DLOG("focus change in, for window 0x%08x\n", event->event);
+
+ if (event->event == root) {
+ DLOG("Received focus in for root window, refocusing the focused window.\n");
+ con_focus(focused);
+ focused_id = XCB_NONE;
+ x_push_changes(croot);
+ }
+
Con *con;
if ((con = con_by_window_id(event->event)) == NULL || con->window == NULL)
return;
@@ -1249,7 +1237,6 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
/* We update focused_id because we don’t need to set focus again */
focused_id = event->event;
tree_render();
- return;
}
/*
@@ -1281,8 +1268,7 @@ static bool handle_class_change(void *data, xcb_connection_t *conn, uint8_t stat
return false;
if (prop == NULL) {
- prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
- false, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 32),
+ prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 32),
NULL);
if (prop == NULL)
@@ -1305,8 +1291,7 @@ static bool handle_motif_hints_change(void *data, xcb_connection_t *conn, uint8_
return false;
if (prop == NULL) {
- prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
- false, window, A__MOTIF_WM_HINTS, XCB_GET_PROPERTY_TYPE_ANY, 0, 5 * sizeof(uint64_t)),
+ prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, A__MOTIF_WM_HINTS, XCB_GET_PROPERTY_TYPE_ANY, 0, 5 * sizeof(uint64_t)),
NULL);
if (prop == NULL)
@@ -1456,7 +1441,7 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom)
struct property_handler_t *handler = NULL;
xcb_get_property_reply_t *propr = NULL;
- for (size_t c = 0; c < sizeof(property_handlers) / sizeof(struct property_handler_t); c++) {
+ for (size_t c = 0; c < NUM_HANDLERS; c++) {
if (property_handlers[c].atom != atom)
continue;
diff --git a/src/ipc.c b/src/ipc.c
index a1a72b1a..d0fb965c 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -38,6 +38,108 @@ static void set_nonblock(int sockfd) {
err(-1, "Could not set O_NONBLOCK");
}
+/*
+ * Given a message and a message type, create the corresponding header, merge it
+ * with the message and append it to the given client's output buffer.
+ *
+ */
+static void append_payload(ipc_client *client, uint32_t message_type, const char *payload) {
+ const size_t size = strlen(payload);
+ const i3_ipc_header_t header = {
+ .magic = {'i', '3', '-', 'i', 'p', 'c'},
+ .size = size,
+ .type = message_type};
+ const size_t header_size = sizeof(i3_ipc_header_t);
+ const size_t message_size = header_size + size;
+
+ client->buffer = srealloc(client->buffer, client->buffer_size + message_size);
+ memcpy(client->buffer + client->buffer_size, ((void *)&header), header_size);
+ memcpy(client->buffer + client->buffer_size + header_size, payload, size);
+ client->buffer_size += message_size;
+}
+
+static void free_ipc_client(ipc_client *client) {
+ close(client->fd);
+
+ ev_io_stop(main_loop, client->callback);
+ FREE(client->callback);
+ if (client->timeout) {
+ ev_timer_stop(main_loop, client->timeout);
+ FREE(client->timeout);
+ }
+
+ free(client->buffer);
+
+ for (int i = 0; i < client->num_events; i++) {
+ free(client->events[i]);
+ }
+ free(client->events);
+ TAILQ_REMOVE(&all_clients, client, clients);
+ free(client);
+}
+
+static void ipc_client_timeout(EV_P_ ev_timer *w, int revents);
+static void ipc_socket_writeable_cb(EV_P_ struct ev_io *w, int revents);
+
+static ev_tstamp kill_timeout = 10.0;
+
+void ipc_set_kill_timeout(ev_tstamp new) {
+ kill_timeout = new;
+}
+
+/*
+ * Try to write the contents of the pending buffer to the client's subscription
+ * socket. Will set, reset or clear the timeout and io callbacks depending on
+ * the result of the write operation.
+ *
+ */
+static void ipc_push_pending(ipc_client *client) {
+ const ssize_t result = writeall_nonblock(client->fd, client->buffer, client->buffer_size);
+ if (result < 0) {
+ return;
+ }
+
+ if ((size_t)result == client->buffer_size) {
+ /* Everything was written successfully: clear the timer and stop the io
+ * callback. */
+ FREE(client->buffer);
+ client->buffer_size = 0;
+ if (client->timeout) {
+ ev_timer_stop(main_loop, client->timeout);
+ FREE(client->timeout);
+ }
+ ev_io_stop(main_loop, client->callback);
+ return;
+ }
+
+ /* Otherwise, make sure that the io callback is enabled and create a new
+ * timer if needed. */
+ ev_io_start(main_loop, client->callback);
+
+ if (!client->timeout) {
+ struct ev_timer *timeout = scalloc(1, sizeof(struct ev_timer));
+ ev_timer_init(timeout, ipc_client_timeout, kill_timeout, 0.);
+ timeout->data = client;
+ client->timeout = timeout;
+ ev_set_priority(timeout, EV_MINPRI);
+ ev_timer_start(main_loop, client->timeout);
+ } else if (result > 0) {
+ /* Keep the old timeout when nothing is written. Otherwise, we would
+ * keep a dead connection by continuously renewing its timeouts. */
+ ev_timer_stop(main_loop, client->timeout);
+ ev_timer_set(client->timeout, kill_timeout, 0.0);
+ ev_timer_start(main_loop, client->timeout);
+ }
+ if (result == 0) {
+ return;
+ }
+
+ /* Shift the buffer to the left and reduce the allocated space. */
+ client->buffer_size -= (size_t)result;
+ memmove(client->buffer, client->buffer + result, client->buffer_size);
+ client->buffer = srealloc(client->buffer, client->buffer_size);
+}
+
/*
* Sends the specified event to all IPC clients which are currently connected
* and subscribed to this kind of event.
@@ -57,7 +159,11 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa
if (!interested)
continue;
- ipc_send_message(current->fd, strlen(payload), message_type, (const uint8_t *)payload);
+ const bool push_now = (current->buffer_size == 0);
+ append_payload(current, message_type, payload);
+ if (push_now) {
+ ipc_push_pending(current);
+ }
}
}
@@ -99,12 +205,7 @@ void ipc_shutdown(shutdown_reason_t reason) {
while (!TAILQ_EMPTY(&all_clients)) {
current = TAILQ_FIRST(&all_clients);
shutdown(current->fd, SHUT_RDWR);
- close(current->fd);
- for (int i = 0; i < current->num_events; i++)
- free(current->events[i]);
- free(current->events);
- TAILQ_REMOVE(&all_clients, current, clients);
- free(current);
+ free_ipc_client(current);
}
}
@@ -268,10 +369,6 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
case CT_DOCKAREA:
ystr("dockarea");
break;
- default:
- DLOG("About to dump unknown container type=%d. This is a bug.\n", con->type);
- assert(false);
- break;
}
/* provided for backwards compatibility only. */
@@ -660,32 +757,7 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
}
ystr("modifier");
- switch (config->modifier) {
- case M_NONE:
- ystr("none");
- break;
- case M_CONTROL:
- ystr("ctrl");
- break;
- case M_SHIFT:
- ystr("shift");
- break;
- case M_MOD1:
- ystr("Mod1");
- break;
- case M_MOD2:
- ystr("Mod2");
- break;
- case M_MOD3:
- ystr("Mod3");
- break;
- case M_MOD5:
- ystr("Mod5");
- break;
- default:
- ystr("Mod4");
- break;
- }
+ y(integer, config->modifier);
dump_bar_bindings(gen, config);
@@ -709,6 +781,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
ystr("strip_workspace_numbers");
y(bool, config->strip_workspace_numbers);
+ ystr("strip_workspace_name");
+ y(bool, config->strip_workspace_name);
+
ystr("binding_mode_indicator");
y(bool, !config->hide_binding_mode_indicator);
@@ -1153,6 +1228,9 @@ IPC_HANDLER(send_tick) {
y(map_open);
+ ystr("first");
+ y(bool, false);
+
ystr("payload");
yajl_gen_string(gen, (unsigned char *)message, message_size);
@@ -1170,9 +1248,68 @@ IPC_HANDLER(send_tick) {
DLOG("Sent tick event\n");
}
+struct sync_state {
+ char *last_key;
+ uint32_t rnd;
+ xcb_window_t window;
+};
+
+static int _sync_json_key(void *extra, const unsigned char *val, size_t len) {
+ struct sync_state *state = extra;
+ FREE(state->last_key);
+ state->last_key = scalloc(len + 1, 1);
+ memcpy(state->last_key, val, len);
+ return 1;
+}
+
+static int _sync_json_int(void *extra, long long val) {
+ struct sync_state *state = extra;
+ if (strcasecmp(state->last_key, "rnd") == 0) {
+ state->rnd = val;
+ } else if (strcasecmp(state->last_key, "window") == 0) {
+ state->window = (xcb_window_t)val;
+ }
+ return 1;
+}
+
+IPC_HANDLER(sync) {
+ yajl_handle p;
+ yajl_status stat;
+
+ /* Setup the JSON parser */
+ static yajl_callbacks callbacks = {
+ .yajl_map_key = _sync_json_key,
+ .yajl_integer = _sync_json_int,
+ };
+
+ struct sync_state state;
+ memset(&state, '\0', sizeof(struct sync_state));
+ p = yalloc(&callbacks, (void *)&state);
+ stat = yajl_parse(p, (const unsigned char *)message, message_size);
+ FREE(state.last_key);
+ if (stat != yajl_status_ok) {
+ unsigned char *err;
+ err = yajl_get_error(p, true, (const unsigned char *)message,
+ message_size);
+ ELOG("YAJL parse error: %s\n", err);
+ yajl_free_error(p, err);
+
+ const char *reply = "{\"success\":false}";
+ ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SYNC, (const uint8_t *)reply);
+ yajl_free(p);
+ return;
+ }
+ yajl_free(p);
+
+ DLOG("received IPC sync request (rnd = %d, window = 0x%08x)\n", state.rnd, state.window);
+ sync_respond(state.window, state.rnd);
+ const char *reply = "{\"success\":true}";
+ ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SYNC, (const uint8_t *)reply);
+}
+
/* The index of each callback function corresponds to the numeric
* value of the message type (see include/i3/ipc.h) */
-handler_t handlers[11] = {
+handler_t handlers[12] = {
handle_run_command,
handle_get_workspaces,
handle_subscribe,
@@ -1184,6 +1321,7 @@ handler_t handlers[11] = {
handle_get_binding_modes,
handle_get_config,
handle_send_tick,
+ handle_sync,
};
/*
@@ -1210,25 +1348,21 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
return;
}
- /* If not, there was some kind of error. We don’t bother
- * and close the connection */
- close(w->fd);
-
- /* Delete the client from the list of clients */
+ /* If not, there was some kind of error. We don’t bother and close the
+ * connection. Delete the client from the list of clients. */
+ bool closed = false;
ipc_client *current;
TAILQ_FOREACH(current, &all_clients, clients) {
if (current->fd != w->fd)
continue;
- for (int i = 0; i < current->num_events; i++)
- free(current->events[i]);
- free(current->events);
- /* We can call TAILQ_REMOVE because we break out of the
- * TAILQ_FOREACH afterwards */
- TAILQ_REMOVE(&all_clients, current, clients);
- free(current);
+ free_ipc_client(current);
+ closed = true;
break;
}
+ if (!closed) {
+ close(w->fd);
+ }
ev_io_stop(EV_A_ w);
free(w);
@@ -1248,6 +1382,62 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
FREE(message);
}
+static void ipc_client_timeout(EV_P_ ev_timer *w, int revents) {
+ /* No need to be polite and check for writeability, the other callback would
+ * have been called by now. */
+ ipc_client *client = (ipc_client *)w->data;
+
+ char *cmdline = NULL;
+#if defined(__linux__) && defined(SO_PEERCRED)
+ struct ucred peercred;
+ socklen_t so_len = sizeof(peercred);
+ if (getsockopt(client->fd, SOL_SOCKET, SO_PEERCRED, &peercred, &so_len) != 0) {
+ goto end;
+ }
+ char *exepath;
+ sasprintf(&exepath, "/proc/%d/cmdline", peercred.pid);
+
+ int fd = open(exepath, O_RDONLY);
+ free(exepath);
+ if (fd == -1) {
+ goto end;
+ }
+ char buf[512] = {'\0'}; /* cut off cmdline for the error message. */
+ const ssize_t n = read(fd, buf, sizeof(buf));
+ close(fd);
+ if (n < 0) {
+ goto end;
+ }
+ for (char *walk = buf; walk < buf + n - 1; walk++) {
+ if (*walk == '\0') {
+ *walk = ' ';
+ }
+ }
+ cmdline = buf;
+
+ if (cmdline) {
+ ELOG("client %p with pid %d and cmdline '%s' on fd %d timed out, killing\n", client, peercred.pid, cmdline, client->fd);
+ }
+
+end:
+#endif
+ if (!cmdline) {
+ ELOG("client %p on fd %d timed out, killing\n", client, client->fd);
+ }
+
+ free_ipc_client(client);
+}
+
+static void ipc_socket_writeable_cb(EV_P_ ev_io *w, int revents) {
+ DLOG("fd %d writeable\n", w->fd);
+ ipc_client *client = (ipc_client *)w->data;
+
+ /* If this callback is called then there should be a corresponding active
+ * timer. */
+ assert(client->timeout != NULL);
+ ipc_push_pending(client);
+}
+
/*
* Handler for activity on the listening socket, meaning that a new client
* has just connected and we should accept() him. Sets up the event handler
@@ -1276,10 +1466,16 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
ev_io_init(package, ipc_receive_message, client, EV_READ);
ev_io_start(EV_A_ package);
+ ipc_client *new = scalloc(1, sizeof(ipc_client));
+
+ package = scalloc(1, sizeof(struct ev_io));
+ package->data = new;
+ ev_io_init(package, ipc_socket_writeable_cb, client, EV_WRITE);
+
DLOG("IPC: new client connected on fd %d\n", w->fd);
- ipc_client *new = scalloc(1, sizeof(ipc_client));
new->fd = client;
+ new->callback = package;
TAILQ_INSERT_TAIL(&all_clients, new, clients);
}
@@ -1384,7 +1580,7 @@ void ipc_send_workspace_event(const char *change, Con *current, Con *old) {
y(free);
}
-/**
+/*
* For the window events we send, along the usual "change" field,
* also the window container, in "container".
*/
@@ -1414,7 +1610,7 @@ void ipc_send_window_event(const char *property, Con *con) {
setlocale(LC_NUMERIC, "");
}
-/**
+/*
* For the barconfig update events, we send the serialized barconfig.
*/
void ipc_send_barconfig_update_event(Barconfig *barconfig) {
diff --git a/src/load_layout.c b/src/load_layout.c
index aa7ac03c..5a340d2c 100644
--- a/src/load_layout.c
+++ b/src/load_layout.c
@@ -108,18 +108,11 @@ static int json_end_map(void *ctx) {
/* Prevent name clashes when appending a workspace, e.g. when the
* user tries to restore a workspace called “1” but already has a
* workspace called “1”. */
- Con *output;
- Con *workspace = NULL;
- TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
- GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name));
char *base = sstrdup(json_node->name);
int cnt = 1;
- while (workspace != NULL) {
+ while (get_existing_workspace_by_name(json_node->name) != NULL) {
FREE(json_node->name);
sasprintf(&(json_node->name), "%s_%d", base, cnt++);
- workspace = NULL;
- TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
- GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name));
}
free(base);
@@ -160,8 +153,7 @@ static int json_end_map(void *ctx) {
free(marks[i]);
}
- free(marks);
- marks = NULL;
+ FREE(marks);
num_marks = 0;
}
@@ -618,6 +610,17 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
yajl_config(hand, yajl_allow_comments, true);
/* Allow multiple values, i.e. multiple nodes to attach */
yajl_config(hand, yajl_allow_multiple_values, true);
+ /* We don't need to validate that the input is valid UTF8 here.
+ * tree_append_json is called in two cases:
+ * 1. With the append_layout command. json_validate is called first and will
+ * fail on invalid UTF8 characters so we don't need to recheck.
+ * 2. With an in-place restart. The rest of the codebase should be
+ * responsible for producing valid UTF8 JSON output. If not,
+ * tree_append_json will just preserve invalid UTF8 strings in the tree
+ * instead of failing to parse the layout file which could lead to
+ * problems like in #3156.
+ * Either way, disabling UTF8 validation slightly speeds up yajl. */
+ yajl_config(hand, yajl_dont_validate_strings, true);
json_node = con;
to_focus = NULL;
incomplete = 0;
@@ -639,6 +642,9 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
while (incomplete-- > 0) {
Con *parent = json_node->parent;
DLOG("freeing incomplete container %p\n", json_node);
+ if (json_node == to_focus) {
+ to_focus = NULL;
+ }
con_free(json_node);
json_node = parent;
}
diff --git a/src/main.c b/src/main.c
index 194ef05c..7eb47c82 100644
--- a/src/main.c
+++ b/src/main.c
@@ -32,15 +32,13 @@
* RLIM_INFINITY for i3 debugging versions. */
struct rlimit original_rlimit_core;
-/** The number of file descriptors passed via socket activation. */
+/* The number of file descriptors passed via socket activation. */
int listen_fds;
/* We keep the xcb_prepare watcher around to be able to enable and disable it
* temporarily for drag_pointer(). */
static struct ev_prepare *xcb_prepare;
-extern Con *focused;
-
char **start_argv;
xcb_connection_t *conn;
@@ -643,8 +641,16 @@ int main(int argc, char *argv[]) {
/* Setting both, XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE and
* XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED, will lead to the
* X server sending us the full XKB state in KeyPress and KeyRelease:
- * https://sources.debian.net/src/xorg-server/2:1.17.2-1.1/xkb/xkbEvents.c/?hl=927#L927
+ * https://cgit.freedesktop.org/xorg/xserver/tree/xkb/xkbEvents.c?h=xorg-server-1.20.0#n927
+ *
+ * XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT enable detectable autorepeat:
+ * https://www.x.org/releases/current/doc/kbproto/xkbproto.html#Detectable_Autorepeat
+ * This affects bindings using the --release flag: instead of getting multiple KeyRelease
+ * events we get only one event when the key is physically released by the user.
*/
+ const uint32_t mask = XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE |
+ XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED |
+ XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT;
xcb_xkb_per_client_flags_reply_t *pcf_reply;
/* The last three parameters are unset because they are only relevant
* when using a feature called “automatic reset of boolean controls”:
@@ -655,20 +661,24 @@ int main(int argc, char *argv[]) {
xcb_xkb_per_client_flags(
conn,
XCB_XKB_ID_USE_CORE_KBD,
- XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED,
- XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED,
+ mask,
+ mask,
0 /* uint32_t ctrlsToChange */,
0 /* uint32_t autoCtrls */,
0 /* uint32_t autoCtrlsValues */),
NULL);
- if (pcf_reply == NULL ||
- !(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE)) {
- ELOG("Could not set XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE\n");
- }
- if (pcf_reply == NULL ||
- !(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED)) {
- ELOG("Could not set XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED\n");
- }
+
+#define PCF_REPLY_ERROR(_value) \
+ do { \
+ if (pcf_reply == NULL || !(pcf_reply->value & (_value))) { \
+ ELOG("Could not set " #_value "\n"); \
+ } \
+ } while (0)
+
+ PCF_REPLY_ERROR(XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE);
+ PCF_REPLY_ERROR(XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED);
+ PCF_REPLY_ERROR(XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT);
+
free(pcf_reply);
xkb_base = extreply->first_event;
}
@@ -949,8 +959,9 @@ int main(int argc, char *argv[]) {
Barconfig *barconfig;
TAILQ_FOREACH(barconfig, &barconfigs, configs) {
char *command = NULL;
- sasprintf(&command, "%s --bar_id=%s --socket=\"%s\"",
+ sasprintf(&command, "%s %s --bar_id=%s --socket=\"%s\"",
barconfig->i3bar_command ? barconfig->i3bar_command : "i3bar",
+ barconfig->verbose ? "-V" : "",
barconfig->id, current_socketpath);
LOG("Starting bar process: %s\n", command);
start_application(command, true);
diff --git a/src/manage.c b/src/manage.c
index 8b306052..c4706b0d 100644
--- a/src/manage.c
+++ b/src/manage.c
@@ -246,17 +246,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
DLOG("Initial geometry: (%d, %d, %d, %d)\n", geom->x, geom->y, geom->width, geom->height);
- Con *nc = NULL;
- Match *match = NULL;
- Assignment *assignment;
-
- /* TODO: two matches for one container */
-
/* See if any container swallows this new window */
- nc = con_for_window(search_at, cwindow, &match);
+ Match *match = NULL;
+ Con *nc = con_for_window(search_at, cwindow, &match);
const bool match_from_restart_mode = (match && match->restart_mode);
if (nc == NULL) {
Con *wm_desktop_ws = NULL;
+ Assignment *assignment;
/* If not, check if it is assigned to a specific workspace */
if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE)) ||
@@ -265,13 +261,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
Con *assigned_ws = NULL;
if (assignment->type == A_TO_WORKSPACE_NUMBER) {
- Con *output = NULL;
long parsed_num = ws_name_to_number(assignment->dest.workspace);
- /* This will only work for workspaces that already exist. */
- TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
- GREP_FIRST(assigned_ws, output_get_content(output), child->num == parsed_num);
- }
+ assigned_ws = get_existing_workspace_by_num(parsed_num);
}
/* A_TO_WORKSPACE type assignment or fallback from A_TO_WORKSPACE_NUMBER
* when the target workspace number does not exist yet. */
@@ -359,8 +351,16 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
}
}
}
+ xcb_window_t old_frame = XCB_NONE;
if (nc->window != cwindow && nc->window != NULL) {
window_free(nc->window);
+ /* Match frame and window depth. This is needed because X will refuse to reparent a
+ * window whose background is ParentRelative under a window with a different depth. */
+ if (nc->depth != cwindow->depth) {
+ old_frame = nc->frame.id;
+ nc->depth = cwindow->depth;
+ x_con_reframe(nc);
+ }
}
nc->window = cwindow;
x_reinit(nc);
@@ -374,9 +374,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
/* handle fullscreen containers */
Con *ws = con_get_workspace(nc);
- Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
- if (fs == NULL)
- fs = con_get_fullscreen_con(croot, CF_GLOBAL);
+ Con *fs = con_get_fullscreen_covering_ws(ws);
if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_FULLSCREEN)) {
/* If this window is already fullscreen (after restarting!), skip
@@ -518,6 +516,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
nc->window->min_height = wm_size_hints.min_height;
}
+ if (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) {
+ DLOG("Window specifies maximum size %d x %d\n", wm_size_hints.max_width, wm_size_hints.max_height);
+ nc->window->max_width = wm_size_hints.max_width;
+ nc->window->max_height = wm_size_hints.max_height;
+ }
+
/* Store the requested geometry. The width/height gets raised to at least
* 75x50 when entering floating mode, which is the minimum size for a
* window to be useful (smaller windows are usually overlays/toolbars/…
@@ -651,6 +655,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
tree_render();
+ /* Destroy the old frame if we had to reframe the container. This needs to be done
+ * after rendering in order to prevent the background from flickering in its place. */
+ if (old_frame != XCB_NONE) {
+ xcb_destroy_window(conn, old_frame);
+ }
+
/* Windows might get managed with the urgency hint already set (Pidgin is
* known to do that), so check for that and handle the hint accordingly.
* This code needs to be in this part of manage_window() because the window
@@ -668,5 +678,4 @@ geom_out:
free(geom);
out:
free(attr);
- return;
}
diff --git a/src/match.c b/src/match.c
index b3136ab9..83e37327 100644
--- a/src/match.c
+++ b/src/match.c
@@ -87,31 +87,30 @@ void match_copy(Match *dest, Match *src) {
bool match_matches_window(Match *match, i3Window *window) {
LOG("Checking window 0x%08x (class %s)\n", window->id, window->class_class);
- if (match->class != NULL) {
- if (window->class_class == NULL)
- return false;
- if (strcmp(match->class->pattern, "__focused__") == 0 &&
- strcmp(window->class_class, focused->window->class_class) == 0) {
- LOG("window class matches focused window\n");
- } else if (regex_matches(match->class, window->class_class)) {
- LOG("window class matches (%s)\n", window->class_class);
- } else {
- return false;
- }
- }
+#define GET_FIELD_str(field) (field)
+#define GET_FIELD_i3string(field) (i3string_as_utf8(field))
+#define CHECK_WINDOW_FIELD(match_field, window_field, type) \
+ do { \
+ if (match->match_field != NULL) { \
+ if (window->window_field == NULL) { \
+ return false; \
+ } \
+ \
+ const char *window_field_str = GET_FIELD_##type(window->window_field); \
+ if (strcmp(match->match_field->pattern, "__focused__") == 0 && \
+ focused && focused->window && focused->window->window_field && \
+ strcmp(window_field_str, GET_FIELD_##type(focused->window->window_field)) == 0) { \
+ LOG("window " #match_field " matches focused window\n"); \
+ } else if (regex_matches(match->match_field, window_field_str)) { \
+ LOG("window " #match_field " matches (%s)\n", window_field_str); \
+ } else { \
+ return false; \
+ } \
+ } \
+ } while (0)
- if (match->instance != NULL) {
- if (window->class_instance == NULL)
- return false;
- if (strcmp(match->instance->pattern, "__focused__") == 0 &&
- strcmp(window->class_instance, focused->window->class_instance) == 0) {
- LOG("window instance matches focused window\n");
- } else if (regex_matches(match->instance, window->class_instance)) {
- LOG("window instance matches (%s)\n", window->class_instance);
- } else {
- return false;
- }
- }
+ CHECK_WINDOW_FIELD(class, class_class, str);
+ CHECK_WINDOW_FIELD(instance, class_instance, str);
if (match->id != XCB_NONE) {
if (window->id == match->id) {
@@ -122,33 +121,8 @@ bool match_matches_window(Match *match, i3Window *window) {
}
}
- if (match->title != NULL) {
- if (window->name == NULL)
- return false;
-
- const char *title = i3string_as_utf8(window->name);
- if (strcmp(match->title->pattern, "__focused__") == 0 &&
- strcmp(title, i3string_as_utf8(focused->window->name)) == 0) {
- LOG("window title matches focused window\n");
- } else if (regex_matches(match->title, title)) {
- LOG("title matches (%s)\n", title);
- } else {
- return false;
- }
- }
-
- if (match->window_role != NULL) {
- if (window->role == NULL)
- return false;
- if (strcmp(match->window_role->pattern, "__focused__") == 0 &&
- strcmp(window->role, focused->window->role) == 0) {
- LOG("window role matches focused window\n");
- } else if (regex_matches(match->window_role, window->role)) {
- LOG("window_role matches (%s)\n", window->role);
- } else {
- return false;
- }
- }
+ CHECK_WINDOW_FIELD(title, name, i3string);
+ CHECK_WINDOW_FIELD(window_role, role, str);
if (match->window_type != UINT32_MAX) {
if (window->window_type == match->window_type) {
diff --git a/src/move.c b/src/move.c
index 97ca6d40..545a910a 100644
--- a/src/move.c
+++ b/src/move.c
@@ -9,21 +9,112 @@
*/
#include "all.h"
-typedef enum { BEFORE,
- AFTER } position_t;
+/*
+ * Returns the lowest container in the tree that has both a and b as descendants.
+ *
+ */
+static Con *lowest_common_ancestor(Con *a, Con *b) {
+ Con *parent_a = a;
+ while (parent_a) {
+ Con *parent_b = b;
+ while (parent_b) {
+ if (parent_a == parent_b) {
+ return parent_a;
+ }
+ parent_b = parent_b->parent;
+ }
+ parent_a = parent_a->parent;
+ }
+ assert(false);
+}
+
+/*
+ * Returns the direct child of ancestor that contains con.
+ *
+ */
+static Con *child_containing_con_recursively(Con *ancestor, Con *con) {
+ Con *child = con;
+ while (child && child->parent != ancestor) {
+ child = child->parent;
+ assert(child->parent);
+ }
+ return child;
+}
+
+/*
+ * Returns true if the given container is the focused descendant of ancestor, recursively.
+ *
+ */
+static bool is_focused_descendant(Con *con, Con *ancestor) {
+ Con *current = con;
+ while (current != ancestor) {
+ if (TAILQ_FIRST(&(current->parent->focus_head)) != current) {
+ return false;
+ }
+ current = current->parent;
+ assert(current->parent);
+ }
+ return true;
+}
/*
* This function detaches 'con' from its parent and inserts it either before or
* after 'target'.
*
*/
-static void insert_con_into(Con *con, Con *target, position_t position) {
+void insert_con_into(Con *con, Con *target, position_t position) {
Con *parent = target->parent;
/* We need to preserve the old con->parent. While it might still be used to
* insert the entry before/after it, we call the on_remove_child callback
* afterwards which might then close the con if it is empty. */
Con *old_parent = con->parent;
+ /* We compare the focus order of the children of the lowest common ancestor. If con or
+ * its ancestor is before target's ancestor then con should be placed before the target
+ * in the focus stack. */
+ Con *lca = lowest_common_ancestor(con, parent);
+ if (lca == con) {
+ ELOG("Container is being inserted into one of its descendants.\n");
+ return;
+ }
+
+ Con *con_ancestor = child_containing_con_recursively(lca, con);
+ Con *target_ancestor = child_containing_con_recursively(lca, target);
+ bool moves_focus_from_ancestor = is_focused_descendant(con, con_ancestor);
+ bool focus_before;
+
+ /* Determine if con is going to be placed before or after target in the parent's focus stack. */
+ if (con_ancestor == target_ancestor) {
+ /* Happens when the target is con's old parent. Eg with layout V [ A H [ B C ] ],
+ * if we move C up. Target will be H. */
+ focus_before = moves_focus_from_ancestor;
+ } else {
+ /* Look at the focus stack order of the children of the lowest common ancestor. */
+ Con *current;
+ TAILQ_FOREACH(current, &(lca->focus_head), focused) {
+ if (current == con_ancestor || current == target_ancestor) {
+ break;
+ }
+ }
+ focus_before = (current == con_ancestor);
+ }
+
+ /* If con is the focused container in our old ancestor we place the new ancestor
+ * before the old ancestor in the focus stack. Example:
+ * Consider the layout [ H [ V1 [ A* B ] V2 [ C ] ] ] where A is focused. We move to
+ * a second workspace and from there we move A to the right and switch back to the
+ * original workspace. Without the change focus would move to B instead of staying
+ * with A. */
+ if (moves_focus_from_ancestor && focus_before) {
+ Con *place = TAILQ_PREV(con_ancestor, focus_head, focused);
+ TAILQ_REMOVE(&(lca->focus_head), target_ancestor, focused);
+ if (place) {
+ TAILQ_INSERT_AFTER(&(lca->focus_head), place, target_ancestor, focused);
+ } else {
+ TAILQ_INSERT_HEAD(&(lca->focus_head), target_ancestor, focused);
+ }
+ }
+
con_detach(con);
con_fix_percent(con->parent);
@@ -46,12 +137,28 @@ static void insert_con_into(Con *con, Con *target, position_t position) {
con->parent = parent;
+ if (parent == lca) {
+ if (focus_before) {
+ /* Example layout: H [ A B* ], we move A up/down. 'target' will be H. */
+ TAILQ_INSERT_BEFORE(target, con, focused);
+ } else {
+ /* Example layout: H [ A B* ], we move A up/down. 'target' will be H. */
+ TAILQ_INSERT_AFTER(&(parent->focus_head), target, con, focused);
+ }
+ } else {
+ if (focus_before) {
+ /* Example layout: V [ H [ A B ] C* ], we move C up. 'target' will be A. */
+ TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
+ } else {
+ /* Example layout: V [ H [ A* B ] C ], we move C up. 'target' will be A. */
+ TAILQ_INSERT_TAIL(&(parent->focus_head), con, focused);
+ }
+ }
+
if (position == BEFORE) {
TAILQ_INSERT_BEFORE(target, con, nodes);
- TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
} else if (position == AFTER) {
TAILQ_INSERT_AFTER(&(parent->nodes_head), target, con, nodes);
- TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
}
/* Pretend the con was just opened with regards to size percent values.
@@ -72,18 +179,16 @@ static void insert_con_into(Con *con, Con *target, position_t position) {
static void attach_to_workspace(Con *con, Con *ws, direction_t direction) {
con_detach(con);
con_fix_percent(con->parent);
-
CALL(con->parent, on_remove_child);
con->parent = ws;
if (direction == D_RIGHT || direction == D_DOWN) {
TAILQ_INSERT_HEAD(&(ws->nodes_head), con, nodes);
- TAILQ_INSERT_HEAD(&(ws->focus_head), con, focused);
} else {
TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes);
- TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
}
+ TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
/* Pretend the con was just opened with regards to size percent values.
* Since the con is moved to a completely different con, the old value
@@ -98,7 +203,6 @@ static void attach_to_workspace(Con *con, Con *ws, direction_t direction) {
*
*/
static void move_to_output_directed(Con *con, direction_t direction) {
- Con *old_ws = con_get_workspace(con);
Output *current_output = get_output_for_con(con);
Output *output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
@@ -115,17 +219,26 @@ static void move_to_output_directed(Con *con, direction_t direction) {
return;
}
+ Con *old_ws = con_get_workspace(con);
+ const bool moves_focus = (focused == con);
attach_to_workspace(con, ws, direction);
-
- /* fix the focus stack */
- con_activate(con);
+ if (moves_focus) {
+ /* workspace_show will not correctly update the active workspace because
+ * 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. */
+ focused = old_ws;
+ workspace_show(ws);
+ con_focus(con);
+ }
/* force re-painting the indicators */
FREE(con->deco_render_params);
tree_flatten(croot);
-
- ipc_send_workspace_event("focus", ws, old_ws);
+ ipc_send_window_event("move", con);
+ ewmh_update_wm_desktop();
}
/*
@@ -146,13 +259,19 @@ void tree_move(Con *con, int direction) {
return;
}
- if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) {
+ if (con->fullscreen_mode == CF_GLOBAL) {
+ DLOG("Not moving fullscreen global container\n");
+ return;
+ }
+
+ if ((con->fullscreen_mode == CF_OUTPUT) ||
+ (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1)) {
/* This is the only con on this workspace */
move_to_output_directed(con, direction);
return;
}
- orientation_t o = (direction == D_LEFT || direction == D_RIGHT ? HORIZ : VERT);
+ orientation_t o = orientation_from_direction(direction);
Con *same_orientation = con_parent_with_orientation(con, o);
/* The do {} while is used to 'restart' at this point with a different
@@ -179,9 +298,10 @@ void tree_move(Con *con, int direction) {
/* easy case: the move is within this container */
if (same_orientation == con->parent) {
- DLOG("We are in the same container\n");
- Con *swap;
- if ((swap = (direction == D_LEFT || direction == D_UP ? TAILQ_PREV(con, nodes_head, nodes) : TAILQ_NEXT(con, nodes)))) {
+ Con *swap = (direction == D_LEFT || direction == D_UP)
+ ? TAILQ_PREV(con, nodes_head, nodes)
+ : TAILQ_NEXT(con, nodes);
+ if (swap) {
if (!con_is_leaf(swap)) {
DLOG("Moving into our bordering branch\n");
target = con_descend_direction(swap, direction);
@@ -193,26 +313,22 @@ void tree_move(Con *con, int direction) {
insert_con_into(con, target, position);
goto end;
}
- if (direction == D_LEFT || direction == D_UP)
+
+ DLOG("Swapping with sibling.\n");
+ if (direction == D_LEFT || direction == D_UP) {
TAILQ_SWAP(swap, con, &(swap->parent->nodes_head), nodes);
- else
+ } else {
TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes);
+ }
- TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
- TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused);
-
- DLOG("Swapped.\n");
ipc_send_window_event("move", con);
- ewmh_update_wm_desktop();
return;
}
if (con->parent == con_get_workspace(con)) {
- /* If we couldn't find a place to move it on this workspace,
- * try to move it to a workspace on a different output */
+ /* If we couldn't find a place to move it on this workspace, try
+ * to move it to a workspace on a different output */
move_to_output_directed(con, direction);
- ipc_send_window_event("move", con);
- ewmh_update_wm_desktop();
return;
}
@@ -257,6 +373,7 @@ void tree_move(Con *con, int direction) {
* and move it to the next output. */
DLOG("Grandparent is workspace\n");
move_to_output_directed(con, direction);
+ return;
} else {
DLOG("Moving into container above\n");
position = (direction == D_UP || direction == D_LEFT ? BEFORE : AFTER);
@@ -264,11 +381,6 @@ void tree_move(Con *con, int direction) {
}
end:
- /* We need to call con_focus() to fix the focus stack "above" the container
- * we just inserted the focused container into (otherwise, the parent
- * container(s) would still point to the old container(s)). */
- con_focus(con);
-
/* force re-painting the indicators */
FREE(con->deco_render_params);
diff --git a/src/output.c b/src/output.c
index c76dfd03..ebba5c77 100644
--- a/src/output.c
+++ b/src/output.c
@@ -10,7 +10,7 @@
#include "all.h"
/*
- * Returns the output container below the given output container.
+ * Returns the content container below the given output container.
*
*/
Con *output_get_content(Con *output) {
@@ -72,8 +72,15 @@ Output *get_output_for_con(Con *con) {
* Iterates over all outputs and pushes sticky windows to the currently visible
* workspace on that output.
*
+ * old_focus is used to determine if a sticky window is going to be focused.
+ * old_focus might be different than the currently focused container because the
+ * caller might need to temporarily change the focus and then call
+ * output_push_sticky_windows. For example, workspace_show needs to set focus to
+ * one of its descendants first, then call output_push_sticky_windows that
+ * should focus a sticky window if it was the focused in the previous workspace.
+ *
*/
-void output_push_sticky_windows(Con *to_focus) {
+void output_push_sticky_windows(Con *old_focus) {
Con *output;
TAILQ_FOREACH(output, &(croot->focus_head), focused) {
Con *workspace, *visible_ws = NULL;
@@ -95,12 +102,17 @@ void output_push_sticky_windows(Con *to_focus) {
child != TAILQ_END(&(current_ws->focus_head));) {
Con *current = child;
child = TAILQ_NEXT(child, focused);
- if (current->type != CT_FLOATING_CON)
+ if (current->type != CT_FLOATING_CON || !con_is_sticky(current)) {
continue;
+ }
- if (con_is_sticky(current)) {
- bool ignore_focus = (to_focus == NULL) || (current != to_focus->parent);
- con_move_to_workspace(current, visible_ws, true, false, ignore_focus);
+ bool ignore_focus = (old_focus == NULL) || (current != old_focus->parent);
+ con_move_to_workspace(current, visible_ws, true, false, ignore_focus);
+ if (!ignore_focus) {
+ Con *current_ws = con_get_workspace(focused);
+ con_activate(con_descend_focused(current));
+ /* Pushing sticky windows shouldn't change the focused workspace. */
+ con_activate(con_descend_focused(current_ws));
}
}
}
diff --git a/src/randr.c b/src/randr.c
index 85add08f..6bb8d9e6 100644
--- a/src/randr.c
+++ b/src/randr.c
@@ -114,6 +114,20 @@ Output *get_output_containing(unsigned int x, unsigned int y) {
return NULL;
}
+/*
+ * Returns the active output which contains the midpoint of the given rect. If
+ * such an output doesn't exist, returns the output which contains most of the
+ * rectangle or NULL if there is no output which intersects with it.
+ *
+ */
+Output *get_output_from_rect(Rect rect) {
+ unsigned int mid_x = rect.x + rect.width / 2;
+ unsigned int mid_y = rect.y + rect.height / 2;
+ Output *output = get_output_containing(mid_x, mid_y);
+
+ return output ? output : output_containing_rect(rect);
+}
+
/*
* Returns the active output which spans exactly the area specified by
* rect or NULL if there is no output like this.
@@ -136,27 +150,37 @@ Output *get_output_with_dimensions(Rect rect) {
}
/*
- * In contained_by_output, we check if any active output contains part of the container.
+ * In output_containing_rect, we check if any active output contains part of the container.
* We do this by checking if the output rect is intersected by the Rect.
* This is the 2-dimensional counterpart of get_output_containing.
- * Since we don't actually need the outputs intersected by the given Rect (There could
- * be many), we just return true or false for convenience.
+ * Returns the output with the maximum intersecting area.
*
*/
-bool contained_by_output(Rect rect) {
+Output *output_containing_rect(Rect rect) {
Output *output;
int lx = rect.x, uy = rect.y;
int rx = rect.x + rect.width, by = rect.y + rect.height;
+ long max_area = 0;
+ Output *result = NULL;
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
+ int lx_o = (int)output->rect.x, uy_o = (int)output->rect.y;
+ int rx_o = (int)(output->rect.x + output->rect.width), by_o = (int)(output->rect.y + output->rect.height);
DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
rect.x, rect.y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
- if (rx >= (int)output->rect.x && lx <= (int)(output->rect.x + output->rect.width) &&
- by >= (int)output->rect.y && uy <= (int)(output->rect.y + output->rect.height))
- return true;
+ int left = max(lx, lx_o);
+ int right = min(rx, rx_o);
+ int bottom = min(by, by_o);
+ int top = max(uy, uy_o);
+ if (left < right && bottom > top) {
+ long area = (right - left) * (bottom - top);
+ if (area > max_area) {
+ result = output;
+ }
+ }
}
- return false;
+ return result;
}
/*
@@ -400,14 +424,10 @@ void init_ws_for_output(Output *output, Con *content) {
/* go through all assignments and move the existing workspaces to this output */
struct Workspace_Assignment *assignment;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
- if (strcmp(assignment->output, output_primary_name(output)) != 0)
+ if (!output_triggers_assignment(output, assignment)) {
continue;
-
- /* check if this workspace actually exists */
- Con *workspace = NULL, *out;
- TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
- GREP_FIRST(workspace, output_get_content(out),
- !strcasecmp(child->name, assignment->name));
+ }
+ Con *workspace = get_existing_workspace_by_name(assignment->name);
if (workspace == NULL)
continue;
@@ -481,8 +501,9 @@ void init_ws_for_output(Output *output, Con *content) {
/* otherwise, we create the first assigned ws for this output */
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
- if (strcmp(assignment->output, output_primary_name(output)) != 0)
+ if (!output_triggers_assignment(output, assignment)) {
continue;
+ }
LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
assignment->name, assignment->output);
@@ -496,7 +517,7 @@ void init_ws_for_output(Output *output, Con *content) {
Con *ws = create_workspace_on_output(output, content);
/* TODO: Set focus in main.c */
- con_activate(ws);
+ con_focus(ws);
}
/*
@@ -529,7 +550,7 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) {
}
/* If default_orientation is NO_ORIENTATION, we change the orientation of
- * the workspaces and their childs depending on output resolution. This is
+ * the workspaces and their children depending on output resolution. This is
* only done for workspaces with maximum one child. */
if (config.default_orientation == NO_ORIENTATION) {
TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) {
@@ -836,8 +857,9 @@ void randr_query_outputs(void) {
/* If there's no randr output, enable the output covering the root window. */
if (any_randr_output_active()) {
DLOG("Active RandR output found. Disabling root output.\n");
- if (root_output->active)
+ if (root_output && root_output->active) {
root_output->to_be_disabled = true;
+ }
} else {
DLOG("No active RandR output found. Enabling root output.\n");
root_output->active = true;
@@ -924,7 +946,9 @@ void randr_query_outputs(void) {
continue;
DLOG("Focusing primary output %s\n", output_primary_name(output));
- con_activate(con_descend_focused(output->con));
+ Con *content = output_get_content(output->con);
+ Con *ws = TAILQ_FIRST(&(content)->focus_head);
+ workspace_show(ws);
}
/* render_layout flushes */
@@ -969,7 +993,7 @@ void randr_disable_output(Output *output) {
if (current != next && TAILQ_EMPTY(&(current->focus_head))) {
/* the workspace is empty and not focused, get rid of it */
DLOG("Getting rid of current = %p / %s (empty, unfocused)\n", current, current->name);
- tree_close_internal(current, DONT_KILL_WINDOW, false, false);
+ tree_close_internal(current, DONT_KILL_WINDOW, false);
continue;
}
DLOG("Detaching current = %p / %s\n", current, current->name);
@@ -1015,7 +1039,7 @@ void randr_disable_output(Output *output) {
Con *con = output->con;
/* clear the pointer before calling tree_close_internal in which the memory is freed */
output->con = NULL;
- tree_close_internal(con, DONT_KILL_WINDOW, true, false);
+ tree_close_internal(con, DONT_KILL_WINDOW, true);
DLOG("Done. Should be fine now\n");
}
diff --git a/src/render.c b/src/render.c
index 0125b89d..d8bffc61 100644
--- a/src/render.c
+++ b/src/render.c
@@ -183,26 +183,28 @@ free_params:
}
static int *precalculate_sizes(Con *con, render_params *p) {
- int *sizes = smalloc(p->children * sizeof(int));
- if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && p->children > 0) {
- assert(!TAILQ_EMPTY(&con->nodes_head));
+ if ((con->layout != L_SPLITH && con->layout != L_SPLITV) || p->children <= 0) {
+ return NULL;
+ }
- Con *child;
- int i = 0, assigned = 0;
- int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height;
- TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
- double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children;
- assigned += sizes[i++] = percentage * total;
- }
- assert(assigned == total ||
- (assigned > total && assigned - total <= p->children * 2) ||
- (assigned < total && total - assigned <= p->children * 2));
- int signal = assigned < total ? 1 : -1;
- while (assigned != total) {
- for (i = 0; i < p->children && assigned != total; ++i) {
- sizes[i] += signal;
- assigned += signal;
- }
+ int *sizes = smalloc(p->children * sizeof(int));
+ assert(!TAILQ_EMPTY(&con->nodes_head));
+
+ Con *child;
+ int i = 0, assigned = 0;
+ int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height;
+ TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+ double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children;
+ assigned += sizes[i++] = lround(percentage * total);
+ }
+ assert(assigned == total ||
+ (assigned > total && assigned - total <= p->children * 2) ||
+ (assigned < total && total - assigned <= p->children * 2));
+ int signal = assigned < total ? 1 : -1;
+ while (assigned != total) {
+ for (i = 0; i < p->children && assigned != total; ++i) {
+ sizes[i] += signal;
+ assigned += signal;
}
}
@@ -232,25 +234,22 @@ static void render_root(Con *con, Con *fullscreen) {
continue;
}
Con *workspace = TAILQ_FIRST(&(content->focus_head));
- Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
+ Con *fullscreen = con_get_fullscreen_covering_ws(workspace);
Con *child;
TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) {
- /* Don’t render floating windows when there is a fullscreen window
- * on that workspace. Necessary to make floating fullscreen work
- * correctly (ticket #564). */
- /* If there is no fullscreen->window, this cannot be a
- * transient window, so we _know_ we need to skip it. This
- * happens during restarts where the container already exists,
- * but the window was not yet associated. */
- if (fullscreen != NULL && fullscreen->window == NULL)
- continue;
- if (fullscreen != NULL && fullscreen->window != NULL) {
+ if (fullscreen != NULL) {
+ /* Don’t render floating windows when there is a fullscreen
+ * window on that workspace. Necessary to make floating
+ * fullscreen work correctly (ticket #564). Exception to the
+ * above rule: smart popup_during_fullscreen handling (popups
+ * belonging to the fullscreen app will be rendered). */
+ if (config.popup_during_fullscreen != PDF_SMART) {
+ continue;
+ }
+
Con *floating_child = con_descend_focused(child);
Con *transient_con = floating_child;
bool is_transient_for = false;
- /* Exception to the above rule: smart
- * popup_during_fullscreen handling (popups belonging to
- * the fullscreen app will be rendered). */
while (transient_con != NULL &&
transient_con->window != NULL &&
transient_con->window->transient_for != XCB_NONE) {
diff --git a/src/resize.c b/src/resize.c
index ee50bfbc..d746ea22 100644
--- a/src/resize.c
+++ b/src/resize.c
@@ -57,7 +57,7 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
}
/* Go up in the tree and search for a container to resize */
- const orientation_t search_orientation = ((direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT);
+ const orientation_t search_orientation = orientation_from_direction(direction);
const bool dir_backwards = (direction == D_UP || direction == D_LEFT);
while (first->type != CT_WORKSPACE &&
first->type != CT_FLOATING_CON &&
@@ -101,10 +101,66 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
return true;
}
-int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
- DLOG("resize handler\n");
+/*
+ * Calculate the given container's new percent given a change in pixels.
+ *
+ */
+double px_resize_to_percent(Con *con, int px_diff) {
+ Con *parent = con->parent;
+ const orientation_t o = con_orientation(parent);
+ const int total = (o == HORIZ ? parent->rect.width : parent->rect.height);
+ /* deco_rect.height is subtracted from each child in render_con_split */
+ const int target = px_diff + (o == HORIZ ? con->rect.width : con->rect.height + con->deco_rect.height);
+ return ((double)target / (double)total);
+}
- /* TODO: previously, we were getting a rect containing all screens. why? */
+/*
+ * Calculate the minimum percent needed for the given container to be at least 1
+ * pixel.
+ *
+ */
+double percent_for_1px(Con *con) {
+ Con *parent = con->parent;
+ const orientation_t o = con_orientation(parent);
+ const int total = (o == HORIZ ? parent->rect.width : parent->rect.height);
+ const int target = (o == HORIZ ? 1 : 1 + con->deco_rect.height);
+ return ((double)target / (double)total);
+}
+
+/*
+ * Resize the two given containers using the given amount of pixels or
+ * percentage points. One of the two needs to be 0. A positive amount means
+ * growing the first container while a negative means shrinking it.
+ * Returns false when the resize would result in one of the two containers
+ * having less than 1 pixel of size.
+ *
+ */
+bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt) {
+ assert(px * ppt == 0);
+
+ Con *parent = first->parent;
+ double new_first_percent;
+ double new_second_percent;
+ if (ppt) {
+ new_first_percent = first->percent + ((double)ppt / 100.0);
+ new_second_percent = second->percent - ((double)ppt / 100.0);
+ } else {
+ new_first_percent = px_resize_to_percent(first, px);
+ new_second_percent = second->percent + first->percent - new_first_percent;
+ }
+ /* Ensure that no container will be less than 1 pixel in the resizing
+ * direction. */
+ if (new_first_percent < percent_for_1px(first) || new_second_percent < percent_for_1px(second)) {
+ return false;
+ }
+
+ first->percent = new_first_percent;
+ second->percent = new_second_percent;
+ con_fix_percent(parent);
+ return true;
+}
+
+void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
Con *output = con_get_output(first);
DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width);
@@ -117,29 +173,27 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
mask = XCB_CW_OVERRIDE_REDIRECT;
values[0] = 1;
- /* Open a new window, the resizebar. Grab the pointer and move the window around
- as the user moves the pointer. */
+ /* Open a new window, the resizebar. Grab the pointer and move the window
+ * around as the user moves the pointer. */
xcb_window_t grabwin = create_window(conn, output->rect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
XCB_WINDOW_CLASS_INPUT_ONLY, XCURSOR_CURSOR_POINTER, true, mask, values);
- /* Keep track of the coordinate orthogonal to motion so we can determine
- * the length of the resize afterward. */
+ /* Keep track of the coordinate orthogonal to motion so we can determine the
+ * length of the resize afterward. */
uint32_t initial_position, new_position;
/* Configure the resizebar and snap the pointer. The resizebar runs along
* the rect of the second con and follows the motion of the pointer. */
Rect helprect;
+ helprect.x = second->rect.x;
+ helprect.y = second->rect.y;
if (orientation == HORIZ) {
- helprect.x = second->rect.x;
- helprect.y = second->rect.y;
helprect.width = logical_px(2);
helprect.height = second->rect.height;
initial_position = second->rect.x;
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
second->rect.x, event->root_y);
} else {
- helprect.x = second->rect.x;
- helprect.y = second->rect.y;
helprect.width = second->rect.width;
helprect.height = logical_px(2);
initial_position = second->rect.y;
@@ -173,37 +227,17 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
xcb_flush(conn);
/* User cancelled the drag so no action should be taken. */
- if (drag_result == DRAG_REVERT)
- return 0;
+ if (drag_result == DRAG_REVERT) {
+ return;
+ }
int pixels = (new_position - initial_position);
-
DLOG("Done, pixels = %d\n", pixels);
- // if we got thus far, the containers must have
- // percentages associated with them
+ /* if we got thus far, the containers must have valid percentages. */
assert(first->percent > 0.0);
assert(second->percent > 0.0);
-
- // calculate the new percentage for the first container
- double new_percent, difference;
- double percent = first->percent;
- DLOG("percent = %f\n", percent);
- int original = (orientation == HORIZ ? first->rect.width : first->rect.height);
- DLOG("original = %d\n", original);
- new_percent = (original + pixels) * (percent / original);
- difference = percent - new_percent;
- DLOG("difference = %f\n", difference);
- DLOG("new percent = %f\n", new_percent);
- first->percent = new_percent;
-
- // calculate the new percentage for the second container
- double s_percent = second->percent;
- second->percent = s_percent + difference;
- DLOG("second->percent = %f\n", second->percent);
-
- // now we must make sure that the sum of the percentages remain 1.0
- con_fix_percent(first->parent);
-
- return 0;
+ const bool result = resize_neighboring_cons(first, second, pixels, 0);
+ DLOG("Graphical resize %s: first->percent = %f, second->percent = %f.\n",
+ result ? "successful" : "failed", first->percent, second->percent);
}
diff --git a/src/scratchpad.c b/src/scratchpad.c
index 95154014..d564bf32 100644
--- a/src/scratchpad.c
+++ b/src/scratchpad.c
@@ -84,7 +84,7 @@ void scratchpad_move(Con *con) {
* can press the same key to quickly look something up).
*
*/
-void scratchpad_show(Con *con) {
+bool scratchpad_show(Con *con) {
DLOG("should show scratchpad window %p\n", con);
Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
Con *floating;
@@ -97,7 +97,7 @@ void scratchpad_show(Con *con) {
floating->scratchpad_state != SCRATCHPAD_NONE) {
DLOG("Focused window is a scratchpad window, hiding it.\n");
scratchpad_move(focused);
- return;
+ return true;
}
/* If the current con or any of its parents are in fullscreen mode, we
@@ -124,7 +124,7 @@ void scratchpad_show(Con *con) {
* window inside this scratch container in order to
* keep the focus the same within this container */
con_activate(con_descend_tiling_focused(walk_con));
- return;
+ return true;
}
}
@@ -141,7 +141,8 @@ void scratchpad_show(Con *con) {
DLOG("Found a visible scratchpad window on another workspace,\n");
DLOG("moving it to this workspace: con = %p\n", walk_con);
con_move_to_workspace(walk_con, focused_ws, true, false, false);
- return;
+ con_activate(con_descend_focused(walk_con));
+ return true;
}
}
@@ -149,7 +150,7 @@ void scratchpad_show(Con *con) {
* is actually in the scratchpad */
if (con && con->parent->scratchpad_state == SCRATCHPAD_NONE) {
DLOG("Window is not in the scratchpad, doing nothing.\n");
- return;
+ return false;
}
/* If this was 'scratchpad show' with criteria, we check if it matches a
@@ -165,7 +166,7 @@ void scratchpad_show(Con *con) {
if (current == active) {
DLOG("Window is a scratchpad window, hiding it.\n");
scratchpad_move(con);
- return;
+ return true;
}
}
@@ -178,7 +179,7 @@ void scratchpad_show(Con *con) {
if (!con) {
LOG("You don't have any scratchpad windows yet.\n");
LOG("Use 'move scratchpad' to move a window to the scratchpad.\n");
- return;
+ return false;
}
} else {
/* We used a criterion, so we need to do what follows (moving,
@@ -206,6 +207,8 @@ void scratchpad_show(Con *con) {
}
con_activate(con_descend_focused(con));
+
+ return true;
}
/*
diff --git a/src/sighandler.c b/src/sighandler.c
index 12ed0ef9..e49c30ba 100644
--- a/src/sighandler.c
+++ b/src/sighandler.c
@@ -62,14 +62,13 @@ static int sighandler_backtrace(void) {
char *filename = NULL;
int suffix = 0;
- struct stat bt;
/* Find a unique filename for the backtrace (since the PID of i3 stays the
* same), so that we don’t overwrite earlier backtraces. */
do {
FREE(filename);
sasprintf(&filename, "%s/i3-backtrace.%d.%d.txt", tmpdir, pid_parent, suffix);
suffix++;
- } while (stat(filename, &bt) == 0);
+ } while (path_exists(filename));
pid_t pid_gdb = fork();
if (pid_gdb < 0) {
@@ -130,7 +129,7 @@ static int sighandler_backtrace(void) {
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
DLOG("GDB did not run properly\n");
return -1;
- } else if (stat(filename, &bt) == -1) {
+ } else if (!path_exists(filename)) {
DLOG("GDB executed successfully, but no backtrace was generated\n");
return -1;
}
@@ -300,7 +299,7 @@ static void sighandler_handle_key_press(xcb_key_press_event_t *event) {
}
}
-void handle_signal(int sig, siginfo_t *info, void *data) {
+static void handle_signal(int sig, siginfo_t *info, void *data) {
DLOG("i3 crashed. SIG: %d\n", sig);
struct sigaction action;
diff --git a/src/startup.c b/src/startup.c
index 166842e0..6302d811 100644
--- a/src/startup.c
+++ b/src/startup.c
@@ -49,6 +49,7 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) {
if (!sequence) {
DLOG("Sequence already deleted, nevermind.\n");
+ free(w);
return;
}
@@ -94,7 +95,7 @@ static int _prune_startup_sequences(void) {
return active_sequences;
}
-/**
+/*
* Deletes a startup sequence, ignoring whether its timeout has elapsed.
* Useful when e.g. a window is moved between workspaces and its children
* shouldn't spawn on the original workspace.
@@ -117,10 +118,10 @@ void startup_sequence_delete(struct Startup_Sequence *sequence) {
}
/*
- * Starts the given application by passing it through a shell. We use double fork
- * to avoid zombie processes. As the started application’s parent exits (immediately),
- * the application is reparented to init (process-id 1), which correctly handles
- * childs, so we don’t have to do it :-).
+ * Starts the given application by passing it through a shell. We use double
+ * fork to avoid zombie processes. As the started application’s parent exits
+ * (immediately), the application is reparented to init (process-id 1), which
+ * correctly handles children, so we don’t have to do it :-).
*
* The shell used to start applications is the system's bourne shell (i.e.,
* /bin/sh).
@@ -190,7 +191,7 @@ void start_application(const char *command, bool no_startup_id) {
if (!no_startup_id)
sn_launcher_context_setup_child_process(context);
- execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void *)NULL);
+ execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL);
/* not reached */
}
_exit(0);
@@ -256,7 +257,7 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) {
}
}
-/**
+/*
* Renames workspaces that are mentioned in the startup sequences.
*
*/
@@ -272,7 +273,7 @@ void startup_sequence_rename_workspace(const char *old_name, const char *new_nam
}
}
-/**
+/*
* Gets the stored startup sequence for the _NET_STARTUP_ID of a given window.
*
*/
diff --git a/src/sync.c b/src/sync.c
new file mode 100644
index 00000000..dafbc355
--- /dev/null
+++ b/src/sync.c
@@ -0,0 +1,28 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * sync.c: i3 sync protocol: https://i3wm.org/docs/testsuite.html#i3_sync
+ *
+ */
+#include "all.h"
+
+void sync_respond(xcb_window_t window, uint32_t rnd) {
+ DLOG("[i3 sync protocol] Sending random value %d back to X11 window 0x%08x\n", rnd, window);
+
+ void *reply = scalloc(32, 1);
+ xcb_client_message_event_t *ev = reply;
+
+ ev->response_type = XCB_CLIENT_MESSAGE;
+ ev->window = window;
+ ev->type = A_I3_SYNC;
+ ev->format = 32;
+ ev->data.data32[0] = window;
+ ev->data.data32[1] = rnd;
+
+ xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)ev);
+ xcb_flush(conn);
+ free(reply);
+}
diff --git a/src/tree.c b/src/tree.c
index 6c6a614e..e3849873 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -92,6 +92,10 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) {
DLOG("appended tree, using new root\n");
croot = TAILQ_FIRST(&(croot->nodes_head));
+ if (!croot) {
+ /* tree_append_json failed. Continuing here would segfault. */
+ goto out;
+ }
DLOG("new root = %p\n", croot);
Con *out = TAILQ_FIRST(&(croot->nodes_head));
DLOG("out = %p\n", out);
@@ -175,16 +179,6 @@ Con *tree_open_con(Con *con, i3Window *window) {
return new;
}
-static bool _is_con_mapped(Con *con) {
- Con *child;
-
- TAILQ_FOREACH(child, &(con->nodes_head), nodes)
- if (_is_con_mapped(child))
- return true;
-
- return con->mapped;
-}
-
/*
* Closes the given container including all children.
* Returns true if the container was killed or false if just WM_DELETE was sent
@@ -193,22 +187,10 @@ static bool _is_con_mapped(Con *con) {
* The dont_kill_parent flag is specified when the function calls itself
* recursively while deleting a containers children.
*
- * The force_set_focus flag is specified in the case of killing a floating
- * window: tree_close_internal() will be invoked for the CT_FLOATINGCON (the parent
- * container) and focus should be set there.
- *
*/
-bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus) {
- bool was_mapped = con->mapped;
+bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent) {
Con *parent = con->parent;
- if (!was_mapped) {
- /* Even if the container itself is not mapped, its children may be
- * mapped (for example split containers don't have a mapped window on
- * their own but usually contain mapped children). */
- was_mapped = _is_con_mapped(con);
- }
-
/* remove the urgency hint of the workspace (if set) */
if (con->urgent) {
con_set_urgency(con, false);
@@ -216,10 +198,6 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
workspace_update_urgent_flag(con_get_workspace(con));
}
- /* Get the container which is next focused */
- Con *next = con_next_focused(con);
- DLOG("next = %p, focused = %p\n", next, focused);
-
DLOG("closing %p, kill_window = %d\n", con, kill_window);
Con *child, *nextchild;
bool abort_kill = false;
@@ -228,8 +206,9 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
for (child = TAILQ_FIRST(&(con->nodes_head)); child;) {
nextchild = TAILQ_NEXT(child, nodes);
DLOG("killing child=%p\n", child);
- if (!tree_close_internal(child, kill_window, true, false))
+ if (!tree_close_internal(child, kill_window, true)) {
abort_kill = true;
+ }
child = nextchild;
}
@@ -281,17 +260,8 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
Con *ws = con_get_workspace(con);
/* Figure out which container to focus next before detaching 'con'. */
- if (con_is_floating(con)) {
- if (con == focused) {
- DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws);
- next = con_next_focused(parent);
-
- dont_kill_parent = true;
- DLOG("Alright, focusing %p\n", next);
- } else {
- next = NULL;
- }
- }
+ Con *next = (con == focused) ? con_next_focused(con) : NULL;
+ DLOG("next = %p, focused = %p\n", next, focused);
/* Detach the container so that it will not be rendered anymore. */
con_detach(con);
@@ -324,12 +294,6 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
/* kill the X11 part of this container */
x_con_kill(con);
- if (con_is_floating(con)) {
- DLOG("Container was floating, killing floating container\n");
- tree_close_internal(parent, DONT_KILL_WINDOW, false, (con == focused));
- DLOG("parent container killed\n");
- }
-
if (ws == con) {
DLOG("Closing a workspace container, updating EWMH atoms\n");
ewmh_update_number_of_desktops();
@@ -339,30 +303,10 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
con_free(con);
- /* in the case of floating windows, we already focused another container
- * when closing the parent, so we can exit now. */
- if (!next) {
- DLOG("No next container, i will just exit now\n");
- return true;
- }
-
- if (was_mapped || con == focused) {
- if ((kill_window != DONT_KILL_WINDOW) || !dont_kill_parent || con == focused) {
- DLOG("focusing %p / %s\n", next, next->name);
- if (next->type == CT_DOCKAREA) {
- /* Instead of focusing the dockarea, we need to restore focus to the workspace */
- con_activate(con_descend_focused(output_get_content(next->parent)));
- } else {
- if (!force_set_focus && con != focused)
- DLOG("not changing focus, the container was not focused before\n");
- else
- con_activate(next);
- }
- } else {
- DLOG("not focusing because we're not killing anybody\n");
- }
+ if (next) {
+ con_activate(next);
} else {
- DLOG("not focusing, was not mapped\n");
+ DLOG("not changing focus, the container was not focused before\n");
}
/* check if the parent container is empty now and close it */
@@ -567,9 +511,16 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
if (!workspace)
return false;
- Con *focus = con_descend_tiling_focused(workspace);
- if (focus == workspace) {
- focus = con_descend_focused(workspace);
+ /* Use descend_focused first to give higher priority to floating or
+ * tiling fullscreen containers. */
+ Con *focus = con_descend_focused(workspace);
+ if (focus->fullscreen_mode == CF_NONE) {
+ Con *focus_tiling = con_descend_tiling_focused(workspace);
+ /* If descend_tiling returned a workspace then focus is either a
+ * floating container or the same workspace. */
+ if (focus_tiling != workspace) {
+ focus = focus_tiling;
+ }
}
workspace_show(workspace);
@@ -748,7 +699,7 @@ void tree_flatten(Con *con) {
/* 4: close the redundant cons */
DLOG("closing redundant cons\n");
- tree_close_internal(con, DONT_KILL_WINDOW, true, false);
+ tree_close_internal(con, DONT_KILL_WINDOW, true);
/* Well, we got to abort the recursion here because we destroyed the
* container. However, if tree_flatten() is called sufficiently often,
diff --git a/src/util.c b/src/util.c
index dc3444f7..85f359c0 100644
--- a/src/util.c
+++ b/src/util.c
@@ -217,7 +217,7 @@ static char **add_argument(char **original, char *opt_char, char *opt_arg, char
#define y(x, ...) yajl_gen_##x(gen, ##__VA_ARGS__)
#define ystr(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str))
-char *store_restart_layout(void) {
+static char *store_restart_layout(void) {
setlocale(LC_NUMERIC, "C");
yajl_gen gen = yajl_gen_alloc(NULL);
@@ -501,9 +501,16 @@ ssize_t slurp(const char *path, char **buf) {
fclose(f);
if ((ssize_t)n != stbuf.st_size) {
ELOG("File \"%s\" could not be read entirely: got %zd, want %" PRIi64 "\n", path, n, (int64_t)stbuf.st_size);
- free(*buf);
- *buf = NULL;
+ FREE(*buf);
return -1;
}
return (ssize_t)n;
}
+
+/*
+ * Convert a direction to its corresponding orientation.
+ *
+ */
+orientation_t orientation_from_direction(direction_t direction) {
+ return (direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT;
+}
diff --git a/src/workspace.c b/src/workspace.c
index 8c46a949..a2d9b0e8 100644
--- a/src/workspace.c
+++ b/src/workspace.c
@@ -19,6 +19,34 @@ static char *previous_workspace_name = NULL;
* keybindings. */
static char **binding_workspace_names = NULL;
+/*
+ * Returns the workspace with the given name or NULL if such a workspace does
+ * not exist.
+ *
+ */
+Con *get_existing_workspace_by_name(const char *name) {
+ Con *output, *workspace = NULL;
+ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+ GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, name));
+ }
+
+ return workspace;
+}
+
+/*
+ * Returns the workspace with the given number or NULL if such a workspace does
+ * not exist.
+ *
+ */
+Con *get_existing_workspace_by_num(int num) {
+ Con *output, *workspace = NULL;
+ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+ GREP_FIRST(workspace, output_get_content(output), child->num == num);
+ }
+
+ return workspace;
+}
+
/*
* Sets ws->layout to splith/splitv if default_orientation was specified in the
* configfile. Otherwise, it uses splith/splitv depending on whether the output
@@ -39,6 +67,52 @@ static void _workspace_apply_default_orientation(Con *ws) {
}
}
+/*
+ * Returns the first output that is assigned to a workspace specified by the
+ * given name or number or NULL if no such output exists. If there is a
+ * workspace with a matching name and another workspace with a matching number,
+ * the output assigned to the first one is returned.
+ * The order of the 'ws_assignments' queue is respected: if multiple assignments
+ * match the specified workspace, the first one is returned.
+ * If 'name' is NULL it will be ignored.
+ * If 'parsed_num' is -1 it will be ignored.
+ *
+ */
+static Con *get_assigned_output(const char *name, long parsed_num) {
+ Con *output = NULL;
+ struct Workspace_Assignment *assignment;
+ TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
+ if (name && strcmp(assignment->name, name) == 0) {
+ DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output);
+ Output *assigned_by_name = get_output_by_name(assignment->output, true);
+ if (assigned_by_name) {
+ /* When the name matches exactly, skip numbered assignments. */
+ return assigned_by_name->con;
+ }
+ } else if (!output && /* Only keep the first numbered assignment. */
+ parsed_num != -1 &&
+ name_is_digits(assignment->name) &&
+ ws_name_to_number(assignment->name) == parsed_num) {
+ DLOG("Found workspace number assignment to output \"%s\"\n", assignment->output);
+ Output *assigned_by_num = get_output_by_name(assignment->output, true);
+ if (assigned_by_num) {
+ output = assigned_by_num->con;
+ }
+ }
+ }
+
+ return output;
+}
+
+/*
+ * Returns true if the first output assigned to a workspace with the given
+ * workspace assignment is the same as the given output.
+ */
+bool output_triggers_assignment(Output *output, struct Workspace_Assignment *assignment) {
+ Con *assigned = get_assigned_output(assignment->name, -1);
+ return assigned && assigned == output->con;
+}
+
/*
* Returns a pointer to the workspace with the given number (starting at 0),
* creating the workspace if necessary (by allocating the necessary amount of
@@ -46,32 +120,20 @@ static void _workspace_apply_default_orientation(Con *ws) {
*
*/
Con *workspace_get(const char *num, bool *created) {
- Con *output, *workspace = NULL;
-
- TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
- GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num));
+ Con *workspace = get_existing_workspace_by_name(num);
if (workspace == NULL) {
LOG("Creating new workspace \"%s\"\n", num);
- /* unless an assignment is found, we will create this workspace on the current output */
- output = con_get_output(focused);
- /* look for assignments */
- struct Workspace_Assignment *assignment;
/* We set workspace->num to the number if this workspace’s name begins
* with a positive number. Otherwise it’s a named ws and num will be
* -1. */
long parsed_num = ws_name_to_number(num);
- TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
- if (strcmp(assignment->name, num) == 0) {
- DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output);
- GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
- break;
- } else if (parsed_num != -1 && name_is_digits(assignment->name) && ws_name_to_number(assignment->name) == parsed_num) {
- DLOG("Found workspace number assignment to output \"%s\"\n", assignment->output);
- GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
- }
+ Con *output = get_assigned_output(num, parsed_num);
+ /* if an assignment is not found, we create this workspace on the current output */
+ if (!output) {
+ output = con_get_output(focused);
}
Con *content = output_get_content(output);
@@ -172,7 +234,6 @@ void extract_workspace_names_from_bindings(void) {
*/
Con *create_workspace_on_output(Output *output, Con *content) {
/* add a workspace to this output */
- Con *out, *current;
char *name;
bool exists = true;
Con *ws = con_new(NULL, NULL);
@@ -184,24 +245,12 @@ Con *create_workspace_on_output(Output *output, Con *content) {
/* Ensure that this workspace is not assigned to a different output —
* otherwise we would create it, then move it over to its output, then
* find a new workspace, etc… */
- bool assigned = false;
- struct Workspace_Assignment *assignment;
- TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
- if (strcmp(assignment->name, target_name) != 0 ||
- strcmp(assignment->output, output_primary_name(output)) == 0)
- continue;
-
- assigned = true;
- break;
+ Con *assigned = get_assigned_output(target_name, -1);
+ if (assigned && assigned != output->con) {
+ continue;
}
- if (assigned)
- continue;
-
- current = NULL;
- TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
- GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, target_name));
- exists = (current != NULL);
+ exists = (get_existing_workspace_by_name(target_name) != NULL);
if (!exists) {
ws->name = sstrdup(target_name);
/* Set ->num to the number of the workspace, if the name actually
@@ -219,16 +268,11 @@ Con *create_workspace_on_output(Output *output, Con *content) {
int c = 0;
while (exists) {
c++;
-
- ws->num = c;
-
- current = NULL;
- TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
- GREP_FIRST(current, output_get_content(out), child->num == ws->num);
- exists = (current != NULL);
-
+ Con *assigned = get_assigned_output(NULL, c);
+ exists = (get_existing_workspace_by_num(c) || (assigned && assigned != output->con));
DLOG("result for ws %d: exists = %d\n", c, exists);
}
+ ws->num = c;
sasprintf(&(ws->name), "%d", c);
}
con_attach(ws, content, false);
@@ -264,7 +308,7 @@ bool workspace_is_visible(Con *ws) {
* XXX: we need to clean up all this recursive walking code.
*
*/
-Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
+static Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
Con *current;
TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
@@ -340,7 +384,7 @@ static void workspace_reassign_sticky(Con *con) {
/*
* Callback to reset the urgent flag of the given con to false. May be started by
- * _workspace_show to avoid urgency hints being lost by switching to a workspace
+ * workspace_show to avoid urgency hints being lost by switching to a workspace
* focusing the con.
*
*/
@@ -360,9 +404,12 @@ static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents
}
}
-static void _workspace_show(Con *workspace) {
+/*
+ * Switches to the given workspace
+ *
+ */
+void workspace_show(Con *workspace) {
Con *current, *old = NULL;
- Con *old_focus = focused;
/* safe-guard against showing i3-internal workspaces like __i3_scratch */
if (con_is_internal(workspace))
@@ -385,6 +432,13 @@ static void _workspace_show(Con *workspace) {
return;
}
+ /* Used to correctly update focus when pushing sticky windows. Holds the
+ * previously focused container in the same output as workspace. For
+ * example, if a sticky window is focused and then we switch focus to a
+ * workspace in another output and then switch to a third workspace in the
+ * first output, the sticky window needs to be refocused. */
+ Con *old_focus = old ? con_descend_focused(old) : NULL;
+
/* Remember currently focused workspace for switching back to it later with
* the 'workspace back_and_forth' command.
* NOTE: We have to duplicate the name as the original will be freed when
@@ -393,10 +447,8 @@ static void _workspace_show(Con *workspace) {
* focused) are skipped, see bug #868. */
if (current && !con_is_internal(current)) {
FREE(previous_workspace_name);
- if (current) {
- previous_workspace_name = sstrdup(current->name);
- DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name);
- }
+ previous_workspace_name = sstrdup(current->name);
+ DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name);
}
workspace_reassign_sticky(workspace);
@@ -450,7 +502,7 @@ static void _workspace_show(Con *workspace) {
if (!workspace_is_visible(old)) {
LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL);
- tree_close_internal(old, DONT_KILL_WINDOW, false, false);
+ tree_close_internal(old, DONT_KILL_WINDOW, false);
const unsigned char *payload;
ylength length;
@@ -487,14 +539,6 @@ static void _workspace_show(Con *workspace) {
output_push_sticky_windows(old_focus);
}
-/*
- * Switches to the given workspace
- *
- */
-void workspace_show(Con *workspace) {
- _workspace_show(workspace);
-}
-
/*
* Looks up the workspace by name and switches to it.
*
@@ -502,7 +546,7 @@ void workspace_show(Con *workspace) {
void workspace_show_by_name(const char *num) {
Con *workspace;
workspace = workspace_get(num, NULL);
- _workspace_show(workspace);
+ workspace_show(workspace);
}
/*
@@ -876,7 +920,7 @@ Con *workspace_attach_to(Con *ws) {
return new;
}
-/**
+/*
* Creates a new container and re-parents all of children from the given
* workspace into it.
*
@@ -911,12 +955,12 @@ Con *workspace_encapsulate(Con *ws) {
return new;
}
-/**
+/*
* Move the given workspace to the specified output.
* This returns true if and only if moving the workspace was successful.
*/
-bool workspace_move_to_output(Con *ws, const char *name) {
- LOG("Trying to move workspace %p / %s to output \"%s\".\n", ws, ws->name, name);
+bool workspace_move_to_output(Con *ws, Output *output) {
+ LOG("Trying to move workspace %p / %s to output %p / \"%s\".\n", ws, ws->name, output, output_primary_name(output));
Output *current_output = get_output_for_con(ws);
if (current_output == NULL) {
@@ -924,12 +968,6 @@ bool workspace_move_to_output(Con *ws, const char *name) {
return false;
}
- Output *output = get_output_from_string(current_output, name);
- if (!output) {
- ELOG("Could not get output from string \"%s\"\n", name);
- return false;
- }
-
Con *content = output_get_content(output->con);
LOG("got output %p with content %p\n", output, content);
@@ -944,16 +982,13 @@ bool workspace_move_to_output(Con *ws, const char *name) {
bool used_assignment = false;
struct Workspace_Assignment *assignment;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
- if (assignment->output == NULL || strcmp(assignment->output, output_primary_name(current_output)) != 0)
+ if (!output_triggers_assignment(current_output, assignment)) {
continue;
-
+ }
/* check if this workspace is already attached to the tree */
- Con *workspace = NULL, *out;
- TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
- GREP_FIRST(workspace, output_get_content(out),
- !strcasecmp(child->name, assignment->name));
- if (workspace != NULL)
+ if (get_existing_workspace_by_name(assignment->name) != NULL) {
continue;
+ }
/* so create the workspace referenced to by this assignment */
LOG("Creating workspace from assignment %s.\n", assignment->name);
diff --git a/src/x.c b/src/x.c
index 7829079b..5b54d145 100644
--- a/src/x.c
+++ b/src/x.c
@@ -42,7 +42,7 @@ typedef struct con_state {
bool child_mapped;
bool is_hidden;
- /** The con for which this state is. */
+ /* The con for which this state is. */
Con *con;
/* For reparenting, we have a flag (need_reparent) and the X ID of the old
@@ -99,6 +99,27 @@ static con_state *state_for_frame(xcb_window_t window) {
return NULL;
}
+/*
+ * Changes the atoms on the root window and the windows themselves to properly
+ * reflect the current focus for ewmh compliance.
+ *
+ */
+static void change_ewmh_focus(xcb_window_t new_focus, xcb_window_t old_focus) {
+ if (new_focus == old_focus) {
+ return;
+ }
+
+ ewmh_update_active_window(new_focus);
+
+ if (new_focus != XCB_WINDOW_NONE) {
+ ewmh_update_focused(new_focus, true);
+ }
+
+ if (old_focus != XCB_WINDOW_NONE) {
+ ewmh_update_focused(old_focus, false);
+ }
+}
+
/*
* Initializes the X11 part for the given container. Called exactly once for
* every container from con_new().
@@ -232,11 +253,7 @@ void x_move_win(Con *src, Con *dest) {
}
}
-/*
- * Kills the window decoration associated with the given container.
- *
- */
-void x_con_kill(Con *con) {
+static void _x_con_kill(Con *con) {
con_state *state;
if (con->colormap != XCB_NONE) {
@@ -245,7 +262,6 @@ void x_con_kill(Con *con) {
draw_util_surface_free(conn, &(con->frame));
draw_util_surface_free(conn, &(con->frame_buffer));
- xcb_destroy_window(conn, con->frame.id);
xcb_free_pixmap(conn, con->frame_buffer.id);
state = state_for_frame(con->frame.id);
CIRCLEQ_REMOVE(&state_head, state, state);
@@ -258,6 +274,24 @@ void x_con_kill(Con *con) {
focused_id = last_focused = XCB_NONE;
}
+/*
+ * Kills the window decoration associated with the given container.
+ *
+ */
+void x_con_kill(Con *con) {
+ _x_con_kill(con);
+ xcb_destroy_window(conn, con->frame.id);
+}
+
+/*
+ * Completely reinitializes the container's frame, without destroying the old window.
+ *
+ */
+void x_con_reframe(Con *con) {
+ _x_con_kill(con);
+ x_con_init(con);
+}
+
/*
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
*
@@ -474,14 +508,12 @@ void x_draw_decoration(Con *con) {
/* 3: draw a rectangle in border color around the client */
if (p->border_style != BS_NONE && p->con_is_leaf) {
/* We might hide some borders adjacent to the screen-edge */
- adjacent_t borders_to_hide = ADJ_NONE;
- borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
-
+ adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
Rect br = con_border_style_rect(con);
/* These rectangles represent the border around the child window
* (left, bottom and right part). We don’t just fill the whole
- * rectangle because some childs are not freely resizable and we want
+ * rectangle because some children are not freely resizable and we want
* their background color to "shine through". */
if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) {
draw_util_rectangle(&(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height);
@@ -572,6 +604,8 @@ void x_draw_decoration(Con *con) {
goto after_title;
}
+ const int title_padding = logical_px(2);
+ const int deco_width = (int)con->deco_rect.width;
int mark_width = 0;
if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
char *formatted_mark = sstrdup("");
@@ -593,12 +627,17 @@ void x_draw_decoration(Con *con) {
i3String *mark = i3string_from_utf8(formatted_mark);
mark_width = predict_text_width(mark);
+ int mark_offset_x = (config.title_align == ALIGN_RIGHT)
+ ? title_padding
+ : deco_width - mark_width - title_padding;
+
draw_util_text(mark, &(parent->frame_buffer),
p->color->text, p->color->background,
- con->deco_rect.x + con->deco_rect.width - mark_width - logical_px(2),
+ con->deco_rect.x + mark_offset_x,
con->deco_rect.y + text_offset_y, mark_width);
-
I3STRING_FREE(mark);
+
+ mark_width += title_padding;
}
FREE(formatted_mark);
@@ -609,11 +648,33 @@ void x_draw_decoration(Con *con) {
goto copy_pixmaps;
}
+ int title_offset_x;
+ switch (config.title_align) {
+ case ALIGN_LEFT:
+ /* (pad)[text ](pad)[mark + its pad) */
+ title_offset_x = title_padding;
+ break;
+ case ALIGN_CENTER:
+ /* (pad)[ text ](pad)[mark + its pad)
+ * To center the text inside its allocated space, the surface
+ * between the brackets, we use the formula
+ * (surface_width - predict_text_width) / 2
+ * where surface_width = deco_width - 2 * pad - mark_width
+ * so, offset = pad + (surface_width - predict_text_width) / 2 =
+ * = … = (deco_width - mark_width - predict_text_width) / 2 */
+ title_offset_x = max(title_padding, (deco_width - mark_width - predict_text_width(title)) / 2);
+ break;
+ case ALIGN_RIGHT:
+ /* [mark + its pad](pad)[ text](pad) */
+ title_offset_x = max(title_padding + mark_width, deco_width - title_padding - predict_text_width(title));
+ break;
+ }
+
draw_util_text(title, &(parent->frame_buffer),
p->color->text, p->color->background,
- con->deco_rect.x + logical_px(2),
+ con->deco_rect.x + title_offset_x,
con->deco_rect.y + text_offset_y,
- con->deco_rect.width - mark_width - 2 * logical_px(2));
+ deco_width - mark_width - 2 * title_padding);
if (con->title_format != NULL) {
I3STRING_FREE(title);
@@ -766,11 +827,8 @@ void x_push_node(Con *con) {
* background and only afterwards change the window size. This reduces
* flickering. */
- /* As the pixmap only depends on the size and not on the position, it
- * is enough to check if width/height have changed. Also, we don’t
- * create a pixmap at all when the window is actually not visible
- * (height == 0) or when it is not needed. */
- bool has_rect_changed = (state->rect.width != rect.width || state->rect.height != rect.height);
+ bool has_rect_changed = (state->rect.x != rect.x || state->rect.y != rect.y ||
+ state->rect.width != rect.width || state->rect.height != rect.height);
/* Check if the container has an unneeded pixmap left over from
* previously having a border or titlebar. */
@@ -1120,7 +1178,7 @@ void x_push_changes(Con *con) {
to_focus, focused, focused->name);
send_take_focus(to_focus, last_timestamp);
- ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE));
+ change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused);
if (to_focus != last_focused && is_con_attached(focused))
ipc_send_window_event("focus", focused);
@@ -1139,7 +1197,7 @@ void x_push_changes(Con *con) {
xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
}
- ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE));
+ change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused);
if (to_focus != XCB_NONE && to_focus != last_focused && focused->window != NULL && is_con_attached(focused))
ipc_send_window_event("focus", focused);
@@ -1154,7 +1212,8 @@ void x_push_changes(Con *con) {
* root window in order to avoid an X11 fallback mechanism causing a ghosting effect (see #1378). */
DLOG("Still no window focused, better set focus to the EWMH support window (%d)\n", ewmh_window);
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, ewmh_window, last_timestamp);
- ewmh_update_active_window(XCB_WINDOW_NONE);
+ change_ewmh_focus(XCB_WINDOW_NONE, last_focused);
+
focused_id = ewmh_window;
}
@@ -1226,7 +1285,7 @@ void x_set_name(Con *con, const char *name) {
* Set up the I3_SHMLOG_PATH atom.
*
*/
-void update_shmlog_atom() {
+void update_shmlog_atom(void) {
if (*shmlogname == '\0') {
xcb_delete_property(conn, root, A_I3_SHMLOG_PATH);
} else {
diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c
index 5506d67e..520b0213 100644
--- a/testcases/inject_randr1.5.c
+++ b/testcases/inject_randr1.5.c
@@ -34,7 +34,7 @@ static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents);
static char *sun_path = NULL;
-void cleanup_socket(void) {
+static void cleanup_socket(void) {
if (sun_path != NULL) {
unlink(sun_path);
free(sun_path);
diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in
index e754c0c1..5734eca7 100644
--- a/testcases/lib/i3test.pm.in
+++ b/testcases/lib/i3test.pm.in
@@ -26,6 +26,7 @@ use Data::Dumper ();
use Exporter ();
our @EXPORT = qw(
get_workspace_names
+ get_output_for_workspace
get_unused_workspace
fresh_workspace
get_ws_content
@@ -51,6 +52,7 @@ our @EXPORT = qw(
kill_all_windows
events_for
listen_for_binding
+ is_net_wm_state_focused
);
=head1 NAME
@@ -401,6 +403,29 @@ sub get_workspace_names {
[ map { $_->{name} } @cons ]
}
+=head2 get_output_for_workspace()
+
+Returns the name of the output on which this workspace resides
+
+ cmd 'focus output fake-1';
+ cmd 'workspace 1';
+ is(get_output_for_workspace('1', 'fake-0', 'Workspace 1 in output fake-0');
+
+=cut
+sub get_output_for_workspace {
+ my $ws_name = shift @_;
+ my $i3 = i3(get_socket_path());
+ my $tree = $i3->get_tree->recv;
+ my @outputs = @{$tree->{nodes}};
+
+ foreach (grep { not $_->{name} =~ /^__/ } @outputs) {
+ my $output = $_->{name};
+ foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) {
+ return $output if $_->{nodes}[0]->{name} =~ $ws_name;
+ }
+ }
+}
+
=head2 get_unused_workspace
Returns a workspace name which has not yet been used. See also
@@ -1026,6 +1051,40 @@ sub listen_for_binding {
return $command;
}
+=head2 is_net_wm_state_focused
+
+Returns true if the given window has the _NET_WM_STATE_FOCUSED atom.
+
+ ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
+
+=cut
+sub is_net_wm_state_focused {
+ my ($window) = @_;
+
+ sync_with_i3;
+ my $atom = $x->atom(name => '_NET_WM_STATE_FOCUSED');
+ my $cookie = $x->get_property(
+ 0,
+ $window->{id},
+ $x->atom(name => '_NET_WM_STATE')->id,
+ GET_PROPERTY_TYPE_ANY,
+ 0,
+ 4096
+ );
+
+ my $reply = $x->get_property_reply($cookie->{sequence});
+ my $len = $reply->{length};
+ return 0 if $len == 0;
+
+ my @atoms = unpack("L$len", $reply->{value});
+ for (my $i = 0; $i < $len; $i++) {
+ return 1 if $atoms[$i] == $atom->id;
+ }
+
+ return 0;
+}
+
+
=head1 AUTHOR
Michael Stapelberg
diff --git a/testcases/t/113-urgent.t b/testcases/t/113-urgent.t
index 1e2644ad..0de90193 100644
--- a/testcases/t/113-urgent.t
+++ b/testcases/t/113-urgent.t
@@ -332,6 +332,34 @@ for ($type = 1; $type <= 2; $type++) {
ok(!$source_ws->{urgent}, 'Source workspace is no longer marked urgent');
is($target_ws->{urgent}, 1, 'Target workspace is now marked urgent');
+##############################################################################
+# Test that moving an unfocused container doesn't reset its urgency hint.
+##############################################################################
+ $tmp = fresh_workspace;
+ $win1 = open_window;
+ $win2 = open_window;
+ cmd 'split v';
+ $win3 = open_window;
+ set_urgency($win1, 1, $type);
+ sync_with_i3;
+
+ my $win1_info;
+
+ @content = @{get_ws_content($tmp)};
+ $win1_info = first { $_->{window} == $win1->id } @content;
+ ok($win1_info->{urgent}, 'win1 window is marked urgent');
+
+ cmd '[id="' . $win1->id . '"] move right';
+ cmd '[id="' . $win1->id . '"] move right';
+ @content = @{get_ws_content($tmp)};
+ $win1_info = first { $_->{window} == $win1->id } @content;
+ ok($win1_info->{urgent}, 'win1 window is still marked urgent after moving');
+
+ cmd '[id="' . $win1->id . '"] focus';
+ @content = @{get_ws_content($tmp)};
+ $win1_info = first { $_->{window} == $win1->id } @content;
+ ok(!$win1_info->{urgent}, 'win1 window is not marked urgent after focusing');
+
##############################################################################
exit_gracefully($pid);
diff --git a/testcases/t/132-move-workspace.t b/testcases/t/132-move-workspace.t
index 1a4beacc..60705f96 100644
--- a/testcases/t/132-move-workspace.t
+++ b/testcases/t/132-move-workspace.t
@@ -339,4 +339,15 @@ $ws = get_ws($tmp2);
is_num_children($tmp2, 0, 'no regular nodes on second workspace');
is(@{$ws->{floating_nodes}}, 1, 'one floating node on second workspace');
+###################################################################
+# Check that moving an empty workspace using criteria doesn't
+# create unfocused empty workspace.
+###################################################################
+$tmp2 = get_unused_workspace();
+$tmp = fresh_workspace();
+cmd 'mark a';
+cmd "[con_mark=a] move to workspace $tmp2";
+
+is (get_ws($tmp2), undef, 'No empty workspace created');
+
done_testing;
diff --git a/testcases/t/134-invalid-command.t b/testcases/t/134-invalid-command.t
index 4557bbda..c10771c8 100644
--- a/testcases/t/134-invalid-command.t
+++ b/testcases/t/134-invalid-command.t
@@ -14,7 +14,7 @@
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
-#
+#
#
use i3test;
diff --git a/testcases/t/135-floating-focus.t b/testcases/t/135-floating-focus.t
index 97c4e4cd..168151f4 100644
--- a/testcases/t/135-floating-focus.t
+++ b/testcases/t/135-floating-focus.t
@@ -105,18 +105,14 @@ cmd 'split v';
cmd 'layout stacked';
$second = open_window({ background_color => '#00ff00' }); # window 6
$third = open_window({ background_color => '#0000ff' }); # window 7
-
is($x->input_focus, $third->id, 'last container focused');
-cmd 'floating enable';
-
cmd '[id="' . $second->id . '"] focus';
-
-is($x->input_focus, $second->id, 'second con focused');
-
cmd 'floating enable';
+cmd '[id="' . $third->id . '"] floating enable';
sync_with_i3;
+is($x->input_focus, $second->id, 'second con focused');
# now kill the second one. focus should fall back to the third one, which is
# also floating
@@ -240,4 +236,184 @@ $ws = get_ws($tmp);
is($ws->{floating_nodes}->[1]->{nodes}->[0]->{window}, $first->id, 'first on top');
is($ws->{floating_nodes}->[0]->{nodes}->[0]->{window}, $second->id, 'second behind');
+#############################################################################
+# 9: verify that disabling / enabling floating for a window from a different
+# workspace maintains the correct focus order.
+#############################################################################
+
+sub open_window_helper {
+ my $floating = shift if @_;
+ if ($floating){
+ return open_floating_window;
+ }
+ else {
+ return open_window;
+ }
+}
+
+for my $floating (0, 1){
+ $tmp = fresh_workspace;
+ $first = open_window;
+ $second = open_window_helper($floating);
+ is($x->input_focus, $second->id, "second window focused");
+
+ fresh_workspace;
+ cmd "[id=" . $second->id . "] floating toggle";
+ cmd "workspace $tmp";
+ sync_with_i3;
+
+ my $workspace = get_ws($tmp);
+ is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $second->id, 'second window on first workspace, floating') unless $floating;
+ is($workspace->{nodes}->[1]->{window}, $second->id, 'second window on first workspace, right') unless !$floating;
+ is($x->input_focus, $second->id, 'second window still focused');
+}
+
+#############################################################################
+# 10: verify that toggling floating for an unfocused window on another
+# workspace doesn't make it focused.
+#############################################################################
+
+for my $floating (0, 1){
+ $tmp = fresh_workspace;
+ $first = open_window_helper($floating);
+ $second = open_window;
+ is($x->input_focus, $second->id, 'second (tiling) window focused');
+
+ fresh_workspace;
+ cmd "[id=" . $first->id . "] floating toggle";
+ cmd "workspace $tmp";
+ sync_with_i3;
+
+ my $workspace = get_ws($tmp);
+ is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $first->id, 'first window on first workspace, floating') unless $floating;
+ is($workspace->{nodes}->[1]->{window}, $first->id, 'first window on first workspace, right') unless !$floating;
+ is($x->input_focus, $second->id, 'second window still focused');
+}
+
+#############################################################################
+# 11: verify that toggling floating for a focused window on another workspace
+# which has another, unfocused floating window maintains the focus of the
+# first window.
+#############################################################################
+for my $floating (0, 1){
+ $tmp = fresh_workspace;
+ $first = open_window;
+ $second = open_floating_window;
+ is($x->input_focus, $second->id, 'second (floating) window focused');
+ $third = open_window_helper($floating);
+ is($x->input_focus, $third->id, "third (floating = $floating) window focused");
+
+ fresh_workspace;
+ cmd "[id=" . $third->id . "] floating toggle";
+ cmd "workspace $tmp";
+ sync_with_i3;
+
+ my $workspace = get_ws($tmp);
+ is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $third->id, 'third window on first workspace, floating') unless $floating;
+ is($workspace->{nodes}->[1]->{window}, $third->id, 'third window on first workspace, right') unless !$floating;
+ is($x->input_focus, $third->id, 'third window still focused');
+}
+
+#############################################################################
+# 12: verify that toggling floating for an unfocused window on another
+# workspace which has another, focused floating window doesn't change focus.
+#############################################################################
+
+for my $floating (0, 1){
+ $tmp = fresh_workspace;
+ $first = open_window;
+ $second = open_window_helper($floating);
+ is($x->input_focus, $second->id, "second (floating = $floating) window focused");
+ $third = open_floating_window;
+ is($x->input_focus, $third->id, 'third (floating) window focused');
+
+ fresh_workspace;
+ cmd "[id=" . $second->id . "] floating toggle";
+ cmd "workspace $tmp";
+ sync_with_i3;
+
+ my $workspace = get_ws($tmp);
+ is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $second->id, 'second window on first workspace, floating') unless $floating;
+ is($workspace->{nodes}->[1]->{window}, $second->id, 'second window on first workspace, right') unless !$floating;
+ is($x->input_focus, $third->id, 'third window still focused');
+}
+
+#############################################################################
+# 13: For layout [H1 [A V1[ B F ] ] ] verify that toggling F's floating
+# mode maintains its focus.
+#############################################################################
+
+for my $floating (0, 1){
+ $tmp = fresh_workspace;
+ $first = open_window;
+ $second = open_window;
+ cmd "split v";
+ sync_with_i3;
+ is($x->input_focus, $second->id, "second (floating = $floating) window focused");
+ $third = open_window_helper($floating);
+ is($x->input_focus, $third->id, 'third (floating) window focused');
+
+ fresh_workspace;
+ cmd "[id=" . $third->id . "] floating toggle";
+ cmd "workspace $tmp";
+ sync_with_i3;
+
+ my $workspace = get_ws($tmp);
+ is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $third->id, 'third window on first workspace, floating') unless $floating;
+ is($workspace->{nodes}->[1]->{nodes}->[1]->{window}, $third->id, 'third window on first workspace') unless !$floating;
+ is($x->input_focus, $third->id, 'third window still focused');
+}
+
+#############################################################################
+# 14: For layout [H1 [A V1[ H2 [B H2 [ C V2 [ F D ] ] ] ] ] ] verify that
+# toggling F's floating mode maintains its focus.
+#############################################################################
+
+sub kill_and_confirm_focus {
+ my $focus = shift;
+ my $msg = shift;
+ cmd "kill";
+ sync_with_i3;
+ is($x->input_focus, $focus, $msg);
+}
+
+$tmp = fresh_workspace;
+my $A = open_window;
+my $B = open_window;
+cmd "split v";
+my $C = open_window;
+cmd "split h";
+my $F = open_window;
+cmd "split v";
+my $D = open_window;
+is($x->input_focus, $D->id, "D is focused");
+
+sync_with_i3;
+my $workspace = get_ws($tmp);
+is($workspace->{nodes}->[1]->{nodes}->[1]->{nodes}->[1]->{nodes}->[0]->{window}, $F->id, 'F opened in its expected position');
+
+fresh_workspace;
+cmd "[id=" . $F->id . "] floating enable";
+cmd "workspace $tmp";
+sync_with_i3;
+
+$workspace = get_ws($tmp);
+is($workspace->{floating_nodes}->[0]->{nodes}->[0]->{window}, $F->id, 'F on first workspace, floating');
+is($workspace->{nodes}->[1]->{nodes}->[1]->{nodes}->[1]->{nodes}->[0]->{window}, $D->id, 'D where F used to be');
+is($x->input_focus, $D->id, 'D still focused');
+
+fresh_workspace;
+cmd "[id=" . $F->id . "] floating disable";
+cmd "workspace $tmp";
+sync_with_i3;
+
+$workspace = get_ws($tmp);
+is($workspace->{nodes}->[1]->{nodes}->[1]->{nodes}->[1]->{nodes}->[1]->{window}, $F->id, 'F where D used to be');
+is($x->input_focus, $D->id, 'D still focused');
+
+kill_and_confirm_focus($F->id, 'F focused after D is killed');
+kill_and_confirm_focus($C->id, 'C focused after F is killed');
+kill_and_confirm_focus($B->id, 'B focused after C is killed');
+kill_and_confirm_focus($A->id, 'A focused after B is killed');
+
done_testing;
diff --git a/testcases/t/141-resize.t b/testcases/t/141-resize.t
index 0e7fd526..5a264169 100644
--- a/testcases/t/141-resize.t
+++ b/testcases/t/141-resize.t
@@ -141,6 +141,147 @@ cmp_float($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%');
cmp_float($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%');
cmp_float($nodes->[3]->{percent}, 0.50, 'fourth window got 50%');
+################################################################################
+# Same but using pixels instead of ppt.
+################################################################################
+
+# Use two windows
+$tmp = fresh_workspace;
+
+$left = open_window;
+$right = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+my @widths = ($nodes->[0]->{rect}->{width}, $nodes->[1]->{rect}->{width});
+
+cmd 'resize grow width 10 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, $widths[0] - 10, 'left window is 10px smaller');
+cmp_float($nodes->[1]->{rect}->{width}, $widths[1] + 10, 'right window is 10px larger');
+
+# Now test it with four windows
+$tmp = fresh_workspace;
+
+open_window for (1..4);
+
+($nodes, $focus) = get_ws_content($tmp);
+my $width = $nodes->[0]->{rect}->{width};
+
+cmd 'resize grow width 10 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[3]->{rect}->{width}, $width + 10, 'last window is 10px larger');
+
+################################################################################
+# Same but for height
+################################################################################
+
+# Use two windows
+$tmp = fresh_workspace;
+cmd 'split v';
+
+$left = open_window;
+$right = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+my @heights = ($nodes->[0]->{rect}->{height}, $nodes->[1]->{rect}->{height});
+
+cmd 'resize grow height 10 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{height}, $heights[0] - 10, 'left window is 10px smaller');
+cmp_float($nodes->[1]->{rect}->{height}, $heights[1] + 10, 'right window is 10px larger');
+
+# Now test it with four windows
+$tmp = fresh_workspace;
+cmd 'split v';
+
+open_window for (1..4);
+
+($nodes, $focus) = get_ws_content($tmp);
+my $height = $nodes->[0]->{rect}->{height};
+
+cmd 'resize grow height 10 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[3]->{rect}->{height}, $height + 10, 'last window is 10px larger');
+
+################################################################################
+# Check that we can grow tiled windows by pixels
+################################################################################
+
+$tmp = fresh_workspace;
+
+$left = open_window;
+$right = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, 640, 'left window is 640px');
+cmp_float($nodes->[1]->{rect}->{width}, 640, 'right window is 640px');
+
+cmd 'resize grow left 10px';
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, 630, 'left window is 630px');
+cmp_float($nodes->[1]->{rect}->{width}, 650, 'right window is 650px');
+
+################################################################################
+# Check that we can shrink tiled windows by pixels
+################################################################################
+
+$tmp = fresh_workspace;
+
+$left = open_window;
+$right = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, 640, 'left window is 640px');
+cmp_float($nodes->[1]->{rect}->{width}, 640, 'right window is 640px');
+
+cmd 'resize shrink left 10px';
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{width}, 650, 'left window is 650px');
+cmp_float($nodes->[1]->{rect}->{width}, 630, 'right window is 630px');
+
+
+################################################################################
+# Check that we can shrink vertical tiled windows by pixels
+################################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'split v';
+
+$top = open_window;
+$bottom = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+my @heights = ($nodes->[0]->{rect}->{height}, $nodes->[1]->{rect}->{height});
+
+cmd 'resize grow up 10px';
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{height}, $heights[0] - 10, 'top window is 10px larger');
+cmp_float($nodes->[1]->{rect}->{height}, $heights[1] + 10, 'bottom window is 10px smaller');
+
+################################################################################
+# Check that we can shrink vertical tiled windows by pixels
+################################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'split v';
+
+$top = open_window;
+$bottom = open_window;
+
+($nodes, $focus) = get_ws_content($tmp);
+my @heights = ($nodes->[0]->{rect}->{height}, $nodes->[1]->{rect}->{height});
+
+cmd 'resize shrink up 10px';
+($nodes, $focus) = get_ws_content($tmp);
+cmp_float($nodes->[0]->{rect}->{height}, $heights[0] + 10, 'top window is 10px smaller');
+cmp_float($nodes->[1]->{rect}->{height}, $heights[1] - 10, 'bottom window is 10px larger');
+
################################################################################
# Check that the resize grow/shrink width/height syntax works if a nested split
# was set on the container, but no sibling has been opened yet. See #2015.
diff --git a/testcases/t/156-fullscreen-focus.t b/testcases/t/156-fullscreen-focus.t
index 9c396f40..62711915 100644
--- a/testcases/t/156-fullscreen-focus.t
+++ b/testcases/t/156-fullscreen-focus.t
@@ -29,70 +29,55 @@ EOT
# | S1 | S2 |
# +----+----+
-my $tmp = fresh_workspace;
+sub verify_focus {
+ # Report original line
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($expected, $msg) = @_;
+ $expected = $expected->id;
+
+ # Ensure the expected focus if the test fails.
+ cmd "[id=$expected] focus" unless is($x->input_focus, $expected, $msg);
+}
################################################################################
-# Open the left window.
+# Verify that window opened behind fullscreen window will get focus after the
+# fullscreen window gets moved to a different workspace.
################################################################################
-my $left = open_window({ background_color => '#ff0000' });
-
-is($x->input_focus, $left->id, 'left window focused');
-
+fresh_workspace;
+my $left = open_window;
+verify_focus($left, 'left window focused');
diag("left = " . $left->id);
-################################################################################
-# Open the right window.
-################################################################################
-
-my $right = open_window({ background_color => '#00ff00' });
-
+my $right = open_window;
+cmd 'fullscreen';
diag("right = " . $right->id);
-################################################################################
-# Set the right window to fullscreen.
-################################################################################
-
-cmd 'nop setting fullscreen';
-cmd 'fullscreen';
-
-################################################################################
-# Open a third window. Since we're fullscreen, the window won't be # mapped, so
+# Open a third window. Since we're fullscreen, the window won't be mapped, so
# don't wait for it to be mapped. Instead, just send the map request and sync
# with i3 to make sure i3 recognizes it.
-################################################################################
-
-my $third = open_window({
- background_color => '#0000ff',
- name => 'Third window',
- dont_map => 1,
- });
-
+my $third = open_window({dont_map => 1});
$third->map;
-
sync_with_i3;
-
diag("third = " . $third->id);
-################################################################################
# Move the window to a different workspace, and verify that the third window now
# gets focused in the current workspace.
-################################################################################
-
my $tmp2 = get_unused_workspace;
-
cmd "move workspace $tmp2";
+verify_focus($third, 'third window focused');
-is($x->input_focus, $third->id, 'third window focused');
+kill_all_windows;
################################################################################
# Ensure that moving a window to a workspace which has a fullscreen window does
# not focus it (otherwise the user cannot get out of fullscreen mode anymore).
################################################################################
-$tmp = fresh_workspace;
+my $tmp = fresh_workspace;
-my $fullscreen_window = open_window;
+open_window;
cmd 'fullscreen';
my $nodes = get_ws_content($tmp);
@@ -111,32 +96,18 @@ is(scalar @$nodes, 2, 'precisely two windows');
is($nodes->[0]->{id}, $old_id, 'id unchanged');
is($nodes->[0]->{focused}, 1, 'fullscreen window focused');
+kill_all_windows;
+
################################################################################
# Ensure it's possible to change focus if it doesn't escape the fullscreen
# container with fullscreen global. We can't even focus a container in a
# different workspace.
################################################################################
-cmd 'fullscreen';
-
-# Focus screen 1
-sync_with_i3;
-$x->root->warp_pointer(1025, 0);
-sync_with_i3;
-
-$tmp = fresh_workspace;
-cmd "workspace $tmp";
+$tmp = fresh_workspace(output => 1);
my $diff_ws = open_window;
-# Focus screen 0
-sync_with_i3;
-$x->root->warp_pointer(0, 0);
-sync_with_i3;
-
-$tmp2 = fresh_workspace;
-cmd "workspace $tmp2";
-cmd 'split h';
-
+$tmp2 = fresh_workspace(output => 0);
$left = open_window;
my $right1 = open_window;
cmd 'split v';
@@ -146,97 +117,99 @@ cmd 'focus parent';
cmd 'fullscreen global';
cmd '[id="' . $right1->id . '"] focus';
-is($x->input_focus, $right1->id, 'upper right window focused');
+verify_focus($right1, 'upper right window focused');
cmd '[id="' . $right2->id . '"] focus';
-is($x->input_focus, $right2->id, 'bottom right window focused');
+verify_focus($right2, 'bottom right window focused');
cmd 'focus parent';
isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
cmd 'focus child';
-is($x->input_focus, $right2->id, 'bottom right window focused again');
+verify_focus($right2, 'bottom right window focused again');
cmd 'focus up';
-is($x->input_focus, $right1->id, 'allowed focus up');
+verify_focus($right1, 'allowed focus up');
cmd 'focus down';
-is($x->input_focus, $right2->id, 'allowed focus down');
+verify_focus($right2, 'allowed focus down');
cmd 'focus left';
-is($x->input_focus, $right2->id, 'prevented focus left');
+verify_focus($right2, 'prevented focus left');
cmd 'focus right';
-is($x->input_focus, $right2->id, 'prevented focus right');
+verify_focus($right2, 'prevented focus right');
cmd 'focus down';
-is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
+verify_focus($right1, 'allowed focus wrap (down)');
cmd 'focus up';
-is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
+verify_focus($right2, 'allowed focus wrap (up)');
################################################################################
+# (depends on previous layout)
# Same tests when we're in non-global fullscreen mode. It should now be possible
# to focus a container in a different workspace.
################################################################################
cmd 'focus parent';
-cmd 'fullscreen global';
+cmd 'fullscreen disable';
cmd 'fullscreen';
cmd '[id="' . $right1->id . '"] focus';
-is($x->input_focus, $right1->id, 'upper right window focused');
+verify_focus($right1, 'upper right window focused');
cmd '[id="' . $right2->id . '"] focus';
-is($x->input_focus, $right2->id, 'bottom right window focused');
+verify_focus($right2, 'bottom right window focused');
cmd 'focus parent';
isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
cmd 'focus child';
-is($x->input_focus, $right2->id, 'bottom right window focused again');
+verify_focus($right2, 'bottom right window focused again');
cmd 'focus up';
-is($x->input_focus, $right1->id, 'allowed focus up');
+verify_focus($right1, 'allowed focus up');
cmd 'focus down';
-is($x->input_focus, $right2->id, 'allowed focus down');
+verify_focus($right2, 'allowed focus down');
cmd 'focus down';
-is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
+verify_focus($right1, 'allowed focus wrap (down)');
cmd 'focus up';
-is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
+verify_focus($right2, 'allowed focus wrap (up)');
cmd 'focus left';
-is($x->input_focus, $right2->id, 'focus left wrapped (no-op)');
+verify_focus($right2, 'focus left wrapped (no-op)');
cmd 'focus right';
-is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws');
+verify_focus($diff_ws, 'allowed focus change to different ws');
cmd 'focus left';
-is($x->input_focus, $right2->id, 'focused back into fullscreen container');
+verify_focus($right2, 'focused back into fullscreen container');
cmd '[id="' . $diff_ws->id . '"] focus';
-is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws by id');
+verify_focus($diff_ws, 'allowed focus change to different ws by id');
################################################################################
+# (depends on previous layout)
# More testing of the interaction between wrapping and the fullscreen focus
# restrictions.
################################################################################
cmd '[id="' . $right1->id . '"] focus';
-is($x->input_focus, $right1->id, 'upper right window focused');
+verify_focus($right1, 'upper right window focused');
cmd 'focus parent';
-cmd 'fullscreen';
+cmd 'fullscreen disable';
cmd 'focus child';
cmd 'split v';
my $right12 = open_window;
cmd 'focus down';
-is($x->input_focus, $right2->id, 'bottom right window focused');
+verify_focus($right2, 'bottom right window focused');
cmd 'split v';
my $right22 = open_window;
@@ -246,18 +219,19 @@ cmd 'fullscreen';
cmd 'focus child';
cmd 'focus down';
-is($x->input_focus, $right2->id, 'focus did not leave parent container (1)');
+verify_focus($right2, 'focus did not leave parent container (1)');
cmd 'focus down';
-is($x->input_focus, $right22->id, 'focus did not leave parent container (2)');
+verify_focus($right22, 'focus did not leave parent container (2)');
cmd 'focus up';
-is($x->input_focus, $right2->id, 'focus did not leave parent container (3)');
+verify_focus($right2, 'focus did not leave parent container (3)');
cmd 'focus up';
-is($x->input_focus, $right22->id, 'focus did not leave parent container (4)');
+verify_focus($right22, 'focus did not leave parent container (4)');
################################################################################
+# (depends on previous layout)
# Ensure that moving in a direction doesn't violate the focus restrictions.
################################################################################
@@ -281,6 +255,7 @@ cmd 'move up';
verify_move(2, 'prevented move up');
################################################################################
+# (depends on previous layout)
# Moving to a different workspace is allowed with per-output fullscreen
# containers.
################################################################################
@@ -296,6 +271,7 @@ cmd "move to workspace prev";
verify_move(1, 'did not prevent move to workspace by position');
################################################################################
+# (depends on previous layout)
# Ensure that is not allowed with global fullscreen containers.
################################################################################
@@ -304,7 +280,7 @@ cmd "move to workspace $tmp2";
cmd "workspace $tmp2";
cmd 'focus parent';
-cmd 'fullscreen';
+cmd 'fullscreen disable';
cmd 'fullscreen global';
cmd 'focus child';
@@ -314,6 +290,8 @@ verify_move(2, 'prevented move to workspace by name');
cmd "move to workspace prev";
verify_move(2, 'prevented move to workspace by position');
+kill_all_windows;
+
################################################################################
# Ensure it's possible to focus a window using the focus command despite
# fullscreen window blocking it. Fullscreen window should lose its fullscreen
@@ -321,96 +299,94 @@ verify_move(2, 'prevented move to workspace by position');
################################################################################
# first & second tiling, focus using id
-kill_all_windows;
-
$tmp = fresh_workspace;
my $first = open_window;
my $second = open_window;
cmd 'fullscreen';
-is($x->input_focus, $second->id, 'fullscreen window focused');
+verify_focus($second, 'fullscreen window focused');
is_num_fullscreen($tmp, 1, '1 fullscreen window');
-cmd '[id="'. $first->id .'"] focus';
+cmd '[id="' . $first->id . '"] focus';
sync_with_i3;
-is($x->input_focus, $first->id, 'correctly focused using id');
+verify_focus($first, 'correctly focused using id');
is_num_fullscreen($tmp, 0, 'no fullscreen windows');
-# first floating, second tiling, focus using 'focus floating'
kill_all_windows;
+# first floating, second tiling, focus using 'focus floating'
$tmp = fresh_workspace;
$first = open_floating_window;
$second = open_window;
cmd 'fullscreen';
-is($x->input_focus, $second->id, 'fullscreen window focused');
+verify_focus($second, 'fullscreen window focused');
is_num_fullscreen($tmp, 1, '1 fullscreen window');
cmd 'focus floating';
sync_with_i3;
-is($x->input_focus, $first->id, 'correctly focused using focus floating');
+verify_focus($first, 'correctly focused using focus floating');
is_num_fullscreen($tmp, 0, 'no fullscreen windows');
-# first tiling, second floating, focus using 'focus tiling'
kill_all_windows;
+# first tiling, second floating, focus using 'focus tiling'
$tmp = fresh_workspace;
$first = open_window;
$second = open_floating_window;
cmd 'fullscreen';
-is($x->input_focus, $second->id, 'fullscreen window focused');
+verify_focus($second, 'fullscreen window focused');
is_num_fullscreen($tmp, 1, '1 fullscreen window');
cmd 'focus tiling';
sync_with_i3;
-is($x->input_focus, $first->id, 'correctly focused using focus tiling');
+verify_focus($first, 'correctly focused using focus tiling');
is_num_fullscreen($tmp, 0, 'no fullscreen windows');
+kill_all_windows;
+
################################################################################
# When the fullscreen window is in an other workspace it should maintain its
# fullscreen mode since it's not blocking the window to be focused.
################################################################################
-kill_all_windows;
-
$tmp = fresh_workspace;
$first = open_window;
$tmp2 = fresh_workspace;
$second = open_window;
cmd 'fullscreen';
-is($x->input_focus, $second->id, 'fullscreen window focused');
+verify_focus($second, 'fullscreen window focused');
is_num_fullscreen($tmp2, 1, '1 fullscreen window');
-cmd '[id="'. $first->id .'"] focus';
+cmd '[id="' . $first->id . '"] focus';
sync_with_i3;
-is($x->input_focus, $first->id, 'correctly focused using focus id');
+verify_focus($first, 'correctly focused using focus id');
is_num_fullscreen($tmp, 0, 'no fullscreen windows on first workspace');
is_num_fullscreen($tmp2, 1, 'still one fullscreen window on second workspace');
+kill_all_windows;
+
################################################################################
# But a global window in another workspace is blocking the window to be focused.
# Ensure that it loses its fullscreen mode.
################################################################################
-kill_all_windows;
-
$tmp = fresh_workspace;
$first = open_window;
$tmp2 = fresh_workspace;
$second = open_window;
cmd 'fullscreen global';
-is($x->input_focus, $second->id, 'global window focused');
+verify_focus($second, 'global window focused');
is_num_fullscreen($tmp2, 1, '1 fullscreen window');
-cmd '[id="'. $first->id .'"] focus';
+cmd '[id="' . $first->id . '"] focus';
sync_with_i3;
-is($x->input_focus, $first->id, 'correctly focused using focus id');
+verify_focus($first, 'correctly focused using focus id');
is_num_fullscreen($tmp2, 0, 'no fullscreen windows');
diff --git a/testcases/t/158-wm_take_focus.t b/testcases/t/158-wm_take_focus.t
index 41d400d5..c4b42964 100644
--- a/testcases/t/158-wm_take_focus.t
+++ b/testcases/t/158-wm_take_focus.t
@@ -55,6 +55,7 @@ subtest 'Window without WM_TAKE_FOCUS', sub {
my $window = open_window;
ok(!recv_take_focus($window), 'did not receive ClientMessage');
+ ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
my ($nodes) = get_ws_content($ws);
my $con = shift @$nodes;
@@ -91,6 +92,7 @@ subtest 'Window with WM_TAKE_FOCUS and without InputHint', sub {
$window->map;
ok(!recv_take_focus($window), 'did not receive ClientMessage');
+ ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
my ($nodes) = get_ws_content($ws);
my $con = shift @$nodes;
@@ -112,6 +114,7 @@ subtest 'Window with WM_TAKE_FOCUS and unspecified InputHint', sub {
my $window = open_window({ protocols => [ $take_focus ] });
ok(!recv_take_focus($window), 'did not receive ClientMessage');
+ ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
my ($nodes) = get_ws_content($ws);
my $con = shift @$nodes;
diff --git a/testcases/t/169-border-toggle.t b/testcases/t/169-border-toggle.t
index 51219ba6..4146fd79 100644
--- a/testcases/t/169-border-toggle.t
+++ b/testcases/t/169-border-toggle.t
@@ -28,7 +28,7 @@ is($nodes[0]->{border}, 'normal', 'border style normal');
cmd 'border 1pixel';
@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, 'pixel', 'border style 1pixel');
+is($nodes[0]->{border}, 'pixel', 'border style pixel');
is($nodes[0]->{current_border_width}, 1, 'border width = 1px');
cmd 'border none';
@@ -48,7 +48,7 @@ is($nodes[0]->{current_border_width}, 0, 'border width = 0px');
cmd 'border toggle';
@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, 'pixel', 'border style 1pixel');
+is($nodes[0]->{border}, 'pixel', 'border style pixel');
is($nodes[0]->{current_border_width}, 1, 'border width = 1px');
cmd 'border toggle';
@@ -56,4 +56,19 @@ cmd 'border toggle';
is($nodes[0]->{border}, 'normal', 'border style back to normal');
is($nodes[0]->{current_border_width}, 2, 'border width = 2px');
+cmd 'border toggle 10';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'none', 'border style back to none even with width argument');
+is($nodes[0]->{current_border_width}, 0, 'border width = 0px');
+
+cmd 'border toggle 10';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'pixel', 'border style pixel');
+is($nodes[0]->{current_border_width}, 10, 'border width = 10px');
+
+cmd 'border toggle 10';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'normal', 'border style back to normal');
+is($nodes[0]->{current_border_width}, 10, 'border width = 10px');
+
done_testing;
diff --git a/testcases/t/185-scratchpad.t b/testcases/t/185-scratchpad.t
index 147890e1..fd3827f7 100644
--- a/testcases/t/185-scratchpad.t
+++ b/testcases/t/185-scratchpad.t
@@ -471,4 +471,66 @@ is(scalar @$nodes, 0, 'no window on current ws anymore');
is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed');
+################################################################################
+# 15: Verify that 'scratchpad show' returns correct info.
+################################################################################
+
+kill_all_windows;
+
+my $result = cmd 'scratchpad show';
+is($result->[0]->{success}, 0, 'no scratchpad window and call to scratchpad failed');
+
+open_window;
+cmd 'move scratchpad';
+$result = cmd 'scratchpad show';
+is($result->[0]->{success}, 1, 'call to scratchpad succeeded');
+$result = cmd 'scratchpad show';
+is($result->[0]->{success}, 1, 'call to scratchpad succeeded');
+
+kill_all_windows;
+$result = cmd 'scratchpad show';
+is($result->[0]->{success}, 0, 'call to scratchpad failed');
+
+################################################################################
+# 16: Verify that 'scratchpad show' with the criteria returns correct info.
+################################################################################
+
+open_window(name => "scratch-match");
+cmd 'move scratchpad';
+
+$result = cmd '[title="scratch-match"] scratchpad show';
+is($result->[0]->{success}, 1, 'call to scratchpad with the criteria succeeded');
+
+$result = cmd '[title="nomatch"] scratchpad show';
+is($result->[0]->{success}, 0, 'call to scratchpad with non-matching criteria failed');
+
+################################################################################
+# 17: Open a scratchpad window on a workspace, switch to another workspace and
+# call 'scratchpad show' again. Verify that it returns correct info.
+################################################################################
+
+fresh_workspace;
+open_window;
+cmd 'move scratchpad';
+
+fresh_workspace;
+$result = cmd 'scratchpad show';
+is($result->[0]->{success}, 1, 'call to scratchpad in another workspace succeeded');
+
+################################################################################
+# 18: Disabling floating for a scratchpad window should not work.
+################################################################################
+
+kill_all_windows;
+
+$ws = fresh_workspace;
+$window = open_window;
+cmd 'move scratchpad';
+cmd '[id=' . $window->id . '] floating disable';
+
+is(scalar @{get_ws_content($ws)}, 0, 'no window in workspace');
+cmd 'scratchpad show';
+is($x->input_focus, $window->id, 'scratchpad window shown');
+
+
done_testing;
diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t
index 39da19d8..48f50e8d 100644
--- a/testcases/t/187-commands-parser.t
+++ b/testcases/t/187-commands-parser.t
@@ -86,9 +86,9 @@ is(parser_calls(
'resize shrink left 25 px or 33 ppt; ' .
'resize shrink left 25'),
"cmd_resize(shrink, left, 10, 10)\n" .
- "cmd_resize(shrink, left, 25, 10)\n" .
+ "cmd_resize(shrink, left, 25, 0)\n" .
"cmd_resize(shrink, left, 25, 33)\n" .
- "cmd_resize(shrink, left, 25, 10)",
+ "cmd_resize(shrink, left, 25, 0)",
'simple resize ok');
is(parser_calls('resize shrink left 25 px or 33 ppt,'),
diff --git a/testcases/t/189-floating-constraints.t b/testcases/t/189-floating-constraints.t
index 6b082bfd..ba5f76aa 100644
--- a/testcases/t/189-floating-constraints.t
+++ b/testcases/t/189-floating-constraints.t
@@ -21,6 +21,7 @@
#
use i3test i3_autostart => 0;
+use X11::XCB qw/PROP_MODE_REPLACE/;
################################################################################
# 1: check floating_minimum_size (with non-default limits)
@@ -218,4 +219,83 @@ is($rect->{height}, 70, 'height did not exceed minimum height');
exit_gracefully($pid);
+################################################################################
+# 8: check minimum_size and maximum_size set by WM_NORMAL_HINTS
+################################################################################
+
+$config = < sub {
+ my ($window) = @_;
+
+ my $atomname = $x->atom(name => 'WM_NORMAL_HINTS');
+ my $atomtype = $x->atom(name => 'WM_SIZE_HINTS');
+ $x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 32,
+ 13,
+ pack('C5N8', $flags, $pad, $pad, $pad, $pad, 0, 0, 0, $min_width, $min_height, $max_width, $max_height),
+ );
+ },
+ );
+
+ return $window;
+}
+
+my sub check_minsize {
+ sync_with_i3;
+ is($window->rect->{width}, $min_width, 'width = min_width');
+ is($window->rect->{height}, $min_height, 'height = min_height');
+}
+
+my sub check_maxsize {
+ sync_with_i3;
+ is($window->rect->{width}, $max_width, 'width = max_width');
+ is($window->rect->{height}, $max_height, 'height = max_height');
+}
+
+$pid = launch_with_config($config);
+
+$window = open_with_max_size;
+cmd 'floating enable';
+cmd 'border none';
+
+cmd "resize set $min_width px $min_height px";
+check_minsize;
+
+# Try to resize below minimum width
+cmd 'resize set ' . ($min_width - 10) . ' px ' . ($min_height - 50) . ' px';
+check_minsize;
+
+cmd "resize set $max_width px $max_height px";
+check_maxsize;
+
+# Try to resize above maximum width
+cmd 'resize set ' . ($max_width + 150) . ' px ' . ($max_height + 500) . ' px';
+check_maxsize;
+
+exit_gracefully($pid);
+
done_testing;
diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t
index 6857b621..c6ce22eb 100644
--- a/testcases/t/201-config-parser.t
+++ b/testcases/t/201-config-parser.t
@@ -497,10 +497,12 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: , '
fake-outputs
force_display_urgency_hint
focus_on_window_activation
+ title_align
show_marks
workspace
ipc_socket
ipc-socket
+ ipc_kill_timeout
restart_state
popup_during_fullscreen
exec_always
@@ -725,7 +727,7 @@ EOT
$expected = <<'EOT';
cfg_bar_start()
cfg_bar_output(LVDS-1)
-ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'verbose', 'colors', '}'
+ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'colors', '}'
ERROR: CONFIG: (in file )
ERROR: CONFIG: Line 1: bar {
ERROR: CONFIG: Line 2: output LVDS-1
diff --git a/testcases/t/232-cmd-move-criteria.t b/testcases/t/232-cmd-move-criteria.t
index 54e7ce9c..312c9660 100644
--- a/testcases/t/232-cmd-move-criteria.t
+++ b/testcases/t/232-cmd-move-criteria.t
@@ -45,7 +45,7 @@ is($x->input_focus, $win3->{id}, 'it should not disturb focus');
###############################################################################
# test all window types
-my %window_types = (
+my %window_types = (
'normal' => '_NET_WM_WINDOW_TYPE_NORMAL',
'dialog' => '_NET_WM_WINDOW_TYPE_DIALOG',
'utility' => '_NET_WM_WINDOW_TYPE_UTILITY',
diff --git a/testcases/t/240-focus-on-window-activation.t b/testcases/t/240-focus-on-window-activation.t
index 57060753..efcd5ca4 100644
--- a/testcases/t/240-focus-on-window-activation.t
+++ b/testcases/t/240-focus-on-window-activation.t
@@ -22,7 +22,7 @@ use List::Util qw(first);
my ($config, $pid, $first, $second, $ws1, $ws2);
sub send_net_active_window {
- my ($id) = @_;
+ my ($id) = @_;
my $msg = pack "CCSLLLLLLL",
X11::XCB::CLIENT_MESSAGE, # response_type
diff --git a/testcases/t/242-no-focus.t b/testcases/t/242-no-focus.t
index 5b507a78..6fd27fe0 100644
--- a/testcases/t/242-no-focus.t
+++ b/testcases/t/242-no-focus.t
@@ -68,7 +68,7 @@ is($x->input_focus, $first->id, 'input focus has not changed');
exit_gracefully($pid);
#####################################################################
-## 3: no_focus doesn't affect the first window opened on a workspace
+# 3: no_focus doesn't affect the first window opened on a workspace
#####################################################################
$config = < 'focusme');
sync_with_i3;
is($x->input_focus, $first->id, 'input focus has changed');
+# Also check that it counts floating windows
+# See issue #3423.
+open_floating_window(wm_class => 'focusme');
+
+sync_with_i3;
+is($x->input_focus, $first->id, 'input focus didn\'t change to floating window');
+
exit_gracefully($pid);
#####################################################################
diff --git a/testcases/t/243-move-to-mark.t b/testcases/t/243-move-to-mark.t
index abc2b4c6..5e806cd4 100644
--- a/testcases/t/243-move-to-mark.t
+++ b/testcases/t/243-move-to-mark.t
@@ -31,7 +31,7 @@ my $_NET_WM_STATE_ADD = 1;
my $_NET_WM_STATE_TOGGLE = 2;
sub set_urgency {
- my ($win, $urgent_flag) = @_;
+ my ($win, $urgent_flag) = @_;
my $msg = pack "CCSLLLLLL",
X11::XCB::CLIENT_MESSAGE, # response_type
32, # format
@@ -119,7 +119,7 @@ is($nodes->[0]->{window}, $M->{id}, 'M is left of S');
is($nodes->[1]->{window}, $S->{id}, 'S is right of M');
###############################################################################
-# Given 'S' and 'M' on different workspaces and 'S' is urgent, when 'S' is
+# Given 'S' and 'M' on different workspaces and 'S' is urgent, when 'S' is
# moved to 'M', then the urgency flag is transferred to the target workspace.
###############################################################################
@@ -321,6 +321,27 @@ sync_with_i3;
($nodes, $focus) = get_ws_content($target_ws);
is(@{$nodes}, 1, 'tiling container moved to the target workspace');
+###############################################################################
+# Given 'S' and 'M' where 'M' is inside a floating container but not its direct
+# child, when 'S' is moved to 'M', i3 should not crash.
+# See issue: #3402
+###############################################################################
+
+$target_ws = fresh_workspace;
+$S = open_window;
+open_window;
+cmd 'splitv';
+$M = open_window;
+cmd 'mark target';
+cmd 'focus parent, floating enable, focus child';
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+does_i3_live;
+
+# Note: this is not actively supported behavior.
+$nodes = get_ws($target_ws)->{floating_nodes}->[0]->{nodes}->[0]->{nodes};
+is(1, (grep { $_->{window} == $S->{id} } @{$nodes}), 'tiling container moved inside floating container');
+
###############################################################################
# Given 'S' and 'M' are the same container, when 'S' is moved to 'M', then
# the command is ignored.
@@ -336,6 +357,50 @@ sync_with_i3;
does_i3_live;
+###############################################################################
+# Given 'S' and 'M' where 'M' is a workspace and 'S' is on a different
+# workspace, then 'S' ends up as a tiling container on 'M'.
+###############################################################################
+
+fresh_workspace;
+$S = open_window;
+$target_ws = fresh_workspace;
+$M = $target_ws;
+cmd 'mark target';
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+sync_with_i3;
+
+does_i3_live;
+
+($nodes, $focus) = get_ws_content($target_ws);
+is(@{$nodes}, 1, 'tiling container moved to the target workspace');
+
+###############################################################################
+# Given 'S' and 'M' where 'S' is a workspace and 'M' is a container on a
+# different workspace, then all the contents of workspace 'S' end up in 'M's
+# workspace.
+###############################################################################
+
+$S = fresh_workspace;
+cmd 'mark S';
+open_window;
+open_window;
+cmd 'splitv';
+open_window;
+open_floating_window;
+$target_ws = fresh_workspace;
+$M = open_window;
+cmd 'mark target';
+
+cmd '[con_mark=S] move container to mark target';
+sync_with_i3;
+
+($nodes, $focus) = get_ws_content($target_ws);
+is(@{$nodes}, 2, 'there is a window and a container with the contents of the original workspace');
+is($nodes->[0]->{window}, $M->{id}, 'M remains the first window');
+is(@{get_ws($target_ws)->{floating_nodes}}, 1, 'target workspace has the floating container');
+
###############################################################################
done_testing;
diff --git a/testcases/t/245-move-position-mouse.t b/testcases/t/245-move-position-mouse.t
index 12f890f2..01e2c30e 100644
--- a/testcases/t/245-move-position-mouse.t
+++ b/testcases/t/245-move-position-mouse.t
@@ -58,7 +58,7 @@ exit_gracefully($pid);
##########################################################################
# Given a floating container and the cursor is in the left upper edge
-# of the output, when moving the container to the mouse, then the
+# of the output, when moving the container to the mouse, then the
# container is moved but adjusted to stay in-bounds.
##########################################################################
@@ -84,7 +84,7 @@ exit_gracefully($pid);
##########################################################################
# Given a floating container and the cursor is in the left right lower
-# edge of the output, when moving the container to the mouse, then the
+# edge of the output, when moving the container to the mouse, then the
# container is moved but adjusted to stay in-bounds.
##########################################################################
diff --git a/testcases/t/251-command-criteria-focused.t b/testcases/t/251-command-criteria-focused.t
index 225394f7..a880f591 100644
--- a/testcases/t/251-command-criteria-focused.t
+++ b/testcases/t/251-command-criteria-focused.t
@@ -105,6 +105,16 @@ is(@{get_ws($ws)->{nodes}}, 2, 'sanity check: workspace contains two windows');
cmd '[workspace=__focused__] move to workspace trash';
is(@{get_ws($ws)->{nodes}}, 0, '__focused__ works for workspace');
+###############################################################################
+# 6: Test that __focused__ in command criteria when no window is focused does
+# not crash i3.
+# See issue: #3406
+###############################################################################
+
+fresh_workspace;
+cmd '[class=__focused__] focus';
+does_i3_live;
+
###############################################################################
done_testing;
diff --git a/testcases/t/252-floating-size.t b/testcases/t/252-floating-size.t
index 2c8edf39..5c746de9 100644
--- a/testcases/t/252-floating-size.t
+++ b/testcases/t/252-floating-size.t
@@ -26,76 +26,92 @@ workspace ws output fake-0
EOT
################################################################################
-# Check that setting floating windows size works
+# Init variables used for all tests.
################################################################################
my $tmp = fresh_workspace;
-
open_floating_window;
-
my @content = @{get_ws($tmp)->{floating_nodes}};
is(@content, 1, 'one floating node on this ws');
-
my $oldrect = $content[0]->{rect};
-cmd 'resize set 100 px 250 px';
+sub do_test {
+ my ($width, $height) = @_;
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
-cmp_ok($content[0]->{rect}->{width}, '!=', $oldrect->{width}, 'width changed');
-cmp_ok($content[0]->{rect}->{height}, '!=', $oldrect->{width}, 'height changed');
-cmp_ok($content[0]->{rect}->{width}, '==', 100, 'width changed to 100 px');
-cmp_ok($content[0]->{rect}->{height}, '==', 250, 'height changed to 250 px');
+ cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x unchanged');
+ cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y unchanged');
+
+ @content = @{get_ws($tmp)->{floating_nodes}};
+ if ($width) {
+ cmp_ok($content[0]->{rect}->{width}, '==', $width, "width changed to $width px");
+ } else {
+ cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'width unchanged');
+ }
+ if ($height) {
+ cmp_ok($content[0]->{rect}->{height}, '==', $height, "height changed to $height px");
+ } else {
+ cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height}, 'height unchanged');
+ }
+ $oldrect = $content[0]->{rect};
+}
+
+################################################################################
+# Check that setting floating windows size works
+################################################################################
+
+cmd 'resize set 100 px 250 px';
+do_test(100, 250);
################################################################################
# Same but with ppt instead of px
################################################################################
-kill_all_windows;
-$tmp = 'ws';
-cmd "workspace $tmp";
-open_floating_window;
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-is(@content, 1, 'one floating node on this ws');
-
-$oldrect = $content[0]->{rect};
-
cmd 'resize set 33 ppt 20 ppt';
-my $expected_width = int(0.33 * 1333);
-my $expected_height = int(0.2 * 999);
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
-cmp_ok($content[0]->{rect}->{width}, '!=', $oldrect->{width}, 'width changed');
-cmp_ok($content[0]->{rect}->{height}, '!=', $oldrect->{width}, 'height changed');
-cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px");
-cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px");
+do_test(int(0.33 * 1333), int(0.2 * 999));
################################################################################
# Mix ppt and px in a single resize set command
################################################################################
cmd 'resize set 44 ppt 111 px';
-$expected_width = int(0.44 * 1333);
-$expected_height = 111;
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
-cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px");
-cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px");
+do_test(int(0.44 * 1333), 111);
cmd 'resize set 222 px 100 ppt';
-$expected_width = 222;
-$expected_height = 999;
+do_test(222, 999);
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
-cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px");
-cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px");
+################################################################################
+# Zero is interpreted as no change.
+# See issue: #3276.
+################################################################################
+
+cmd 'resize set 0 px 333 px';
+do_test(0, 333);
+
+cmd 'resize set 333 px 0 ppt';
+do_test(333, 0);
+
+cmd 'resize set 0 px 0 ppt';
+do_test(0, 0);
+
+cmd 'resize set 100 ppt 0 px';
+do_test(1333, 0);
+
+################################################################################
+# Use 'width' and 'height' keywords.
+# See issue: #3275.
+################################################################################
+
+cmd 'resize set width 200 px';
+do_test(200, 0);
+
+cmd 'resize set height 200 px';
+do_test(0, 200);
+
+cmd 'resize set width 300 px height 300 px';
+do_test(300, 300);
+
+# ppt + keyword used only for height
+cmd 'resize set 100 ppt height 100 px';
+do_test(1333, 100);
done_testing;
diff --git a/testcases/t/253-multiple-net-wm-state-atoms.t b/testcases/t/253-multiple-net-wm-state-atoms.t
index 3790f92d..3a3e7c6e 100644
--- a/testcases/t/253-multiple-net-wm-state-atoms.t
+++ b/testcases/t/253-multiple-net-wm-state-atoms.t
@@ -21,22 +21,22 @@ use X11::XCB qw(:all);
sub get_wm_state {
sync_with_i3;
- my ($con) = @_;
+ my ($con) = @_;
my $cookie = $x->get_property(
- 0,
+ 0,
$con->{id},
$x->atom(name => '_NET_WM_STATE')->id,
GET_PROPERTY_TYPE_ANY,
- 0,
+ 0,
4096
- );
+ );
my $reply = $x->get_property_reply($cookie->{sequence});
my $len = $reply->{length};
return undef if $len == 0;
my @atoms = unpack("L$len", $reply->{value});
- return \@atoms;
+ return @atoms;
}
my $wm_state_sticky = $x->atom(name => '_NET_WM_STATE_STICKY')->id;
@@ -51,18 +51,24 @@ my $wm_state_fullscreen = $x->atom(name => '_NET_WM_STATE_FULLSCREEN')->id;
fresh_workspace;
my $window = open_window;
cmd 'sticky enable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set');
+my @state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, 'sanity check: _NET_WM_STATE_STICKY is set');
cmd 'fullscreen enable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky, $wm_state_fullscreen ],
- 'both _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_STICKY are set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set');
+ok((scalar grep { $_ == $wm_state_fullscreen } @state) > 0, '_NET_WM_STATE_FULLSCREEN is set');
cmd 'sticky disable';
-is_deeply(get_wm_state($window), [ $wm_state_fullscreen ], 'only _NET_WM_STATE_FULLSCREEN is set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) == 0, '_NET_WM_STATE_STICKY is not set');
+ok((scalar grep { $_ == $wm_state_fullscreen } @state) > 0, '_NET_WM_STATE_FULLSCREEN is set');
cmd 'sticky enable';
cmd 'fullscreen disable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICKY is set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set');
+ok((scalar grep { $_ == $wm_state_fullscreen } @state) == 0, '_NET_WM_STATE_FULLSCREEN is not set');
###############################################################################
# _NET_WM_STATE is removed when the window is withdrawn.
@@ -71,7 +77,8 @@ is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICK
fresh_workspace;
$window = open_window;
cmd 'sticky enable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set');
$window->unmap;
wait_for_unmap($window);
diff --git a/testcases/t/258-keypress-release.t b/testcases/t/258-keypress-release.t
index 8bca0d86..614164ab 100644
--- a/testcases/t/258-keypress-release.t
+++ b/testcases/t/258-keypress-release.t
@@ -29,6 +29,20 @@ bindsym --release Control+Print nop Control+Print
# see issue #2442
bindsym Mod1+b nop Mod1+b
bindsym --release Mod1+Shift+b nop Mod1+Shift+b release
+
+bindsym --release Shift+x nop Shift+x
+
+# see issue #2733
+# 133 == Mod4
+bindcode 133 nop 133
+bindcode --release 133 nop 133 release
+
+mode "a_mode" {
+ # 27 == r
+ bindcode 27 --release mode "default"
+}
+bindsym Mod1+r mode "a_mode"
+bindcode 27 nop do not receive
EOT
use i3test::XTEST;
use ExtUtils::PkgConfig;
@@ -85,6 +99,72 @@ is(listen_for_binding(
'Mod1+Shift+b release',
'triggered the "Mod1+Shift+b" release keybinding');
+is(listen_for_binding(
+ sub {
+ xtest_key_press(50); # Shift
+ xtest_key_press(53); # x
+ xtest_key_release(53); # x
+ xtest_key_release(50); # Shift
+ xtest_sync_with_i3;
+ },
+ ),
+ 'Shift+x',
+ 'triggered the "Shift+x" keybinding by releasing x first');
+
+is(listen_for_binding(
+ sub {
+ xtest_key_press(50); # Shift
+ xtest_key_press(53); # x
+ xtest_key_release(50); # Shift
+ xtest_key_release(53); # x
+ xtest_sync_with_i3;
+ },
+ ),
+ 'Shift+x',
+ 'triggered the "Shift+x" keybinding by releasing Shift first');
+
+is(listen_for_binding(
+ sub {
+ xtest_key_press(133);
+ xtest_sync_with_i3;
+ },
+ ),
+ '133',
+ 'triggered the 133 keycode press binding');
+
+is(listen_for_binding(
+ sub {
+ xtest_key_release(133);
+ xtest_sync_with_i3;
+ },
+ ),
+ '133 release',
+ 'triggered the 133 keycode release binding');
+
+for my $i (1 .. 2) {
+ is(listen_for_binding(
+ sub {
+ xtest_key_press(64); # Alt_l
+ xtest_key_press(27); # r
+ xtest_key_release(27); # r
+ xtest_key_release(64); # Alt_l
+ xtest_sync_with_i3;
+ },
+ ),
+ 'mode "a_mode"',
+ "switched to mode \"a_mode\" $i/2");
+
+ is(listen_for_binding(
+ sub {
+ xtest_key_press(27); # r
+ xtest_key_release(27); # r
+ xtest_sync_with_i3;
+ },
+ ),
+ 'mode "default"',
+ "switched back to default $i/2");
+}
+
}
done_testing;
diff --git a/testcases/t/276-ipc-window-move.t b/testcases/t/276-ipc-window-move.t
index f3606b4e..708963df 100644
--- a/testcases/t/276-ipc-window-move.t
+++ b/testcases/t/276-ipc-window-move.t
@@ -34,7 +34,7 @@ sub move_subtest {
is($move[0]->{container}->{window}, $window->{id}, 'window id matches');
}
-subtest 'move right', \&move_subtest, 'move right';
+subtest 'move left', \&move_subtest, 'move left';
subtest 'move to workspace', \&move_subtest, 'move to workspace ws_new';
done_testing;
diff --git a/testcases/t/285-sticky.t b/testcases/t/285-sticky.t
index f53e4a93..8dfe9aea 100644
--- a/testcases/t/285-sticky.t
+++ b/testcases/t/285-sticky.t
@@ -16,7 +16,14 @@
#
# Tests sticky windows.
# Ticket: #1455
-use i3test;
+use i3test i3_config => < 'findme');
+open_window;
cmd 'sticky enable';
$ws = fresh_workspace;
is(@{get_ws($ws)->{nodes}}, 0, 'tiling sticky container did not move');
is(@{get_ws($ws)->{floating_nodes}}, 0, 'tiling sticky container did not move');
-cmd '[class="findme"] kill';
+kill_all_windows;
###############################################################################
# 2: Given a sticky floating container, when the workspace is switched, then
# the container moves to the new workspace.
###############################################################################
$ws = fresh_workspace;
-open_floating_window(wm_class => 'findme');
+open_floating_window;
$focused = get_focused($ws);
cmd 'sticky enable';
$ws = fresh_workspace;
is(@{get_ws($ws)->{floating_nodes}}, 1, 'floating sticky container moved to new workspace');
is(get_focused($ws), $focused, 'sticky container has focus');
-cmd '[class="findme"] kill';
+kill_all_windows;
###############################################################################
# 3: Given two sticky floating containers, when the workspace is switched,
# then both containers move to the new workspace.
###############################################################################
fresh_workspace;
-open_floating_window(wm_class => 'findme');
+open_floating_window;
cmd 'sticky enable';
-open_floating_window(wm_class => 'findme');
+open_floating_window;
cmd 'sticky enable';
$ws = fresh_workspace;
is(@{get_ws($ws)->{floating_nodes}}, 2, 'multiple sticky windows can be used at the same time');
-cmd '[class="findme"] kill';
+kill_all_windows;
###############################################################################
# 4: Given an unfocused sticky floating container and a tiling container on the
@@ -70,13 +77,13 @@ $ws = fresh_workspace;
open_window;
$focused = get_focused($ws);
fresh_workspace;
-open_floating_window(wm_class => 'findme');
+open_floating_window;
cmd 'sticky enable';
open_window;
cmd 'workspace ' . $ws;
is(get_focused($ws), $focused, 'the tiling container has focus');
-cmd '[class="findme"] kill';
+kill_all_windows;
###############################################################################
# 5: Given a focused sticky floating container and a tiling container on the
@@ -86,13 +93,13 @@ cmd '[class="findme"] kill';
$ws = fresh_workspace;
open_window;
$tmp = fresh_workspace;
-open_floating_window(wm_class => 'findme');
+open_floating_window;
$focused = get_focused($tmp);
cmd 'sticky enable';
cmd 'workspace ' . $ws;
is(get_focused($ws), $focused, 'the sticky container has focus');
-cmd '[class="findme"] kill';
+kill_all_windows;
###############################################################################
# 6: Given a floating container on a non-visible workspace, when the window
@@ -100,13 +107,31 @@ cmd '[class="findme"] kill';
# visible workspace.
###############################################################################
fresh_workspace;
-open_floating_window(wm_class => 'findme');
+open_floating_window;
cmd 'mark sticky';
$ws = fresh_workspace;
cmd '[con_mark=sticky] sticky enable';
is(@{get_ws($ws)->{floating_nodes}}, 1, 'the sticky window jumps to the front');
-cmd '[class="findme"] kill';
+kill_all_windows;
+
+###############################################################################
+# 7: Given a sticky floating container and a workspace on another output, when
+# a new workspace assigned to the first output is focused, then the sticky
+# container should jump to the new workspace and have input focus correctly.
+###############################################################################
+$ws = fresh_workspace(output => 0);
+open_floating_window;
+cmd 'sticky enabled';
+$focused = get_focused($ws);
+$ws = fresh_workspace(output => 1);
+
+is(@{get_ws($ws)->{floating_nodes}}, 0, 'the sticky window didn\'t jump to a workspace on a different output');
+$ws = 'ws-on-0';
+cmd "workspace $ws";
+is(@{get_ws($ws)->{floating_nodes}}, 1, 'the sticky window moved to new workspace on first output');
+is(get_focused($ws), $focused, 'the sticky window has focus');
+kill_all_windows;
###############################################################################
diff --git a/testcases/t/291-swap.t b/testcases/t/291-swap.t
index 3b61fdab..da2d564d 100644
--- a/testcases/t/291-swap.t
+++ b/testcases/t/291-swap.t
@@ -372,26 +372,31 @@ for my $fullscreen (@fullscreen_permutations){
# +---+---+ Layout: H2[ B, F ]
# | B | F | Focus Stacks:
# +---+---+ H2: F, B
+#
+# See issue: #3259
###############################################################################
-$ws1 = fresh_workspace;
-$A = open_window(wm_class => 'mark_A');
+for my $fullscreen (0..1){
+ $ws1 = fresh_workspace;
+ $A = open_window(wm_class => 'mark_A');
-$ws2 = fresh_workspace;
-$B = open_window(wm_class => 'mark_B');
-open_window;
-$expected_focus = get_focused($ws2);
+ $ws2 = fresh_workspace;
+ $B = open_window(wm_class => 'mark_B');
+ open_window;
+ cmd 'fullscreen enable' if $fullscreen;
+ $expected_focus = get_focused($ws2);
-cmd '[con_mark=B] swap container with mark A';
+ cmd '[con_mark=B] swap container with mark A';
-$nodes = get_ws_content($ws1);
-is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace');
+ $nodes = get_ws_content($ws1);
+ is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace');
-$nodes = get_ws_content($ws2);
-is($nodes->[0]->{window}, $A->{id}, 'A is on the left of the second workspace');
-is(get_focused($ws2), $expected_focus, 'F is still focused');
+ $nodes = get_ws_content($ws2);
+ is($nodes->[0]->{window}, $A->{id}, 'A is on the left of the second workspace');
+ is(get_focused($ws2), $expected_focus, 'F is still focused');
-kill_all_windows;
+ kill_all_windows;
+}
###############################################################################
# 1. A container cannot be swapped with its parent.
diff --git a/testcases/t/293-focus-follows-mouse.t b/testcases/t/293-focus-follows-mouse.t
index 0cd6e5c3..55958c3e 100644
--- a/testcases/t/293-focus-follows-mouse.t
+++ b/testcases/t/293-focus-follows-mouse.t
@@ -67,11 +67,8 @@ my $tmp = fresh_workspace;
my ($first_floating, $second_floating);
synced_warp_pointer(0, 0);
-$first_floating = open_floating_window;
-$first_floating->rect(X11::XCB::Rect->new(x => 1, y => 1, width => 100, height => 100));
-$second_floating = open_floating_window;
-$second_floating->rect(X11::XCB::Rect->new(x => 50, y => 50, width => 100, height => 100));
-sync_with_i3;
+$first_floating = open_floating_window(rect => [ 1, 1, 100, 100 ]);
+$second_floating = open_floating_window(rect => [ 50, 50, 100, 100 ]);
$first = open_window;
is($x->input_focus, $first->id, 'first (tiling) window focused');
diff --git a/testcases/t/294-focus-order.t b/testcases/t/294-focus-order.t
index 41c0bf07..6b16540c 100644
--- a/testcases/t/294-focus-order.t
+++ b/testcases/t/294-focus-order.t
@@ -16,7 +16,11 @@
#
# Verify that the corrent focus stack order is preserved after various
# operations.
-use i3test;
+use i3test i3_config => <= 0; $i--) {
@@ -67,7 +72,6 @@ confirm_focus('tabbed');
#####################################################################
fresh_workspace;
-
$windows[3] = open_window;
$windows[1] = open_window;
$windows[0] = open_window;
@@ -106,4 +110,135 @@ $windows[0] = open_window;
cmd 'move left';
confirm_focus('split-v + move');
+#####################################################################
+# Test that moving an unfocused container from another output
+# maintains the correct focus order.
+#####################################################################
+
+fresh_workspace(output => 0);
+$windows[3] = open_window;
+fresh_workspace(output => 1);
+$windows[2] = open_window;
+$windows[1] = open_window;
+$windows[0] = open_window;
+
+cmd '[id=' . $windows[3]->id . '] move right';
+confirm_focus('unfocused move from other output');
+
+#####################################################################
+# Test that moving an unfocused container inside its original parent
+# maintains the correct focus order.
+#####################################################################
+
+fresh_workspace;
+$windows[0] = open_window;
+$windows[1] = open_window;
+cmd 'split v';
+$windows[2] = open_window;
+$windows[3] = open_window;
+focus_windows;
+
+cmd '[id=' . $windows[2]->id . '] move up';
+confirm_focus('split-v + unfocused move inside parent');
+
+######################################################################
+# Test that moving an unfocused container maintains the correct focus
+# order.
+# Layout: H [ A V1 [ B C D ] ]
+######################################################################
+
+fresh_workspace;
+$windows[3] = open_window;
+$windows[2] = open_window;
+cmd 'split v';
+$windows[1] = open_window;
+$windows[0] = open_window;
+
+cmd '[id=' . $windows[3]->id . '] move right';
+confirm_focus('split-v + unfocused move');
+
+######################################################################
+# Test that moving an unfocused container from inside a split
+# container to another workspace doesn't focus sibling.
+######################################################################
+
+$ws = fresh_workspace;
+$windows[0] = open_window;
+$windows[1] = open_window;
+cmd 'split v';
+open_window;
+cmd 'mark a';
+
+cmd '[id=' . $windows[0]->id . '] focus';
+cmd '[con_mark=a] move to workspace ' . get_unused_workspace;
+
+is(@{get_ws_content($ws)}, 2, 'Sanity check: marked window moved');
+confirm_focus('Move unfocused window from split container');
+
+######################################################################
+# Moving containers to another workspace puts them on the top of the
+# focus stack but behind the focused container.
+######################################################################
+
+for my $new_workspace (0 .. 1) {
+ fresh_workspace;
+ $windows[2] = open_window;
+ $windows[1] = open_window;
+ fresh_workspace if $new_workspace;
+ $windows[3] = open_window;
+ $windows[0] = open_window;
+ cmd 'mark target';
+
+ cmd '[id=' . $windows[2]->id . '] move to mark target';
+ cmd '[id=' . $windows[1]->id . '] move to mark target';
+ confirm_focus('\'move to mark\' focus order' . ($new_workspace ? ' when moving containers from other workspace' : ''));
+}
+
+######################################################################
+# Same but with workspace commands.
+######################################################################
+
+fresh_workspace;
+$windows[2] = open_window;
+$windows[1] = open_window;
+$ws = fresh_workspace;
+$windows[3] = open_window;
+$windows[0] = open_window;
+cmd 'mark target';
+
+cmd '[id=' . $windows[2]->id . '] move to workspace ' . $ws;
+cmd '[id=' . $windows[1]->id . '] move to workspace ' . $ws;
+confirm_focus('\'move to workspace\' focus order when moving containers from other workspace');
+
+######################################################################
+# Test focus order with floating and tiling windows.
+# See issue: 1975
+######################################################################
+
+fresh_workspace;
+$windows[2] = open_window;
+$windows[0] = open_window;
+$windows[3] = open_floating_window;
+$windows[1] = open_floating_window;
+focus_windows;
+
+confirm_focus('mix of floating and tiling windows');
+
+######################################################################
+# Same but an unfocused tiling window is killed first.
+######################################################################
+
+fresh_workspace;
+$windows[2] = open_window;
+$windows[0] = open_window;
+$windows[3] = open_floating_window;
+$windows[1] = open_floating_window;
+focus_windows;
+
+cmd '[id=' . $windows[1]->id . '] focus';
+cmd '[id=' . $windows[0]->id . '] kill';
+
+kill_and_confirm_focus($windows[2]->id, 'window 2 focused after tiling killed');
+kill_and_confirm_focus($windows[3]->id, 'window 3 focused after tiling killed');
+
done_testing;
diff --git a/testcases/t/295-net-wm-state-focused.t b/testcases/t/295-net-wm-state-focused.t
new file mode 100644
index 00000000..fd7c2513
--- /dev/null
+++ b/testcases/t/295-net-wm-state-focused.t
@@ -0,0 +1,39 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Tests for setting and removing the _NET_WM_STATE_FOCUSED atom properly.
+# Ticket: #2273
+use i3test;
+use X11::XCB qw(:all);
+
+my ($windowA, $windowB);
+
+fresh_workspace;
+$windowA = open_window;
+ok(is_net_wm_state_focused($windowA), 'a newly opened window that is focused should have _NET_WM_STATE_FOCUSED set');
+
+$windowB = open_window;
+ok(!is_net_wm_state_focused($windowA), 'when a another window is focused, the old window should not have _NET_WM_STATE_FOCUSED set');
+ok(is_net_wm_state_focused($windowB), 'a newly opened window that is focused should have _NET_WM_STATE_FOCUSED set');
+
+# See issue #3495.
+cmd 'kill';
+ok(is_net_wm_state_focused($windowA), 'when the second window is closed, the first window should have _NET_WM_STATE_FOCUSED set');
+
+fresh_workspace;
+ok(!is_net_wm_state_focused($windowA), 'when focus moves to the ewmh support window, no window should have _NET_WM_STATE_FOCUSED set');
+
+done_testing;
diff --git a/testcases/t/296-regress-focus-behind-fullscreen-floating.t b/testcases/t/296-regress-focus-behind-fullscreen-floating.t
new file mode 100644
index 00000000..0867f082
--- /dev/null
+++ b/testcases/t/296-regress-focus-behind-fullscreen-floating.t
@@ -0,0 +1,40 @@
+#!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 directional focus gives focus to floating fullscreen containers when
+# switching workspaces.
+# Ticket: #3201
+# Bug still in: 4.15-59-gb849fe3e
+use i3test i3_config => < 0);
+my $ws = fresh_workspace(output => 1);
+open_window;
+open_floating_window;
+cmd 'fullscreen enable';
+my $expected_focus = get_focused($ws);
+
+cmd 'focus left';
+cmd 'focus right';
+
+is (get_focused($ws), $expected_focus, 'floating fullscreen window focused after directional focus');
+
+done_testing;
diff --git a/testcases/t/297-assign-workspace-to-output.t b/testcases/t/297-assign-workspace-to-output.t
new file mode 100644
index 00000000..a7b75be9
--- /dev/null
+++ b/testcases/t/297-assign-workspace-to-output.t
@@ -0,0 +1,102 @@
+#!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 assignments of workspaces to outputs.
+use i3test i3_autostart => 0;
+
+################################################################################
+# Test initial workspaces.
+################################################################################
+
+my $config = <root->warp_pointer(3, 3);
+
+# H [ T [ H [ A B ] C D V [ E F ] ] G ]
+# Inner horizontal split.
+open_window;
+cmd 'layout tabbed';
+cmd 'splith';
+my $first = open_window;
+cmd 'focus parent';
+# Simple tabs.
+open_window;
+my $second_last = open_window;
+# V-Split container
+open_window;
+cmd 'splitv';
+my $last = open_window;
+# Second child of the outer horizontal split, next to the tabbed one.
+my $outside = open_window;
+cmd 'move right, move right';
+
+cmd '[id=' . $first->id . '] focus';
+
+# Scroll from first to last.
+scroll_down;
+scroll_down;
+is($x->input_focus, $second_last->id, 'Sanity check: scrolling');
+scroll_down;
+is($x->input_focus, $last->id, 'Last window focused through scrolling');
+scroll_down;
+is($x->input_focus, $last->id, 'Scrolling again doesn\'t leave the tabbed container and doesn\'t focus the whole sibling');
+
+# Scroll from last to first.
+scroll_up;
+is($x->input_focus, $second_last->id, 'Scrolling up works');
+scroll_up;
+scroll_up;
+is($x->input_focus, $first->id, 'First window focused through scrolling');
+scroll_up;
+is($x->input_focus, $first->id, 'Scrolling again doesn\'t focus the whole sibling');
+
+# Try scrolling with another window focused
+cmd '[id=' . $outside->id . '] focus';
+scroll_up;
+is($x->input_focus, $first->id, 'Scrolling from outside the tabbed container works');
+
+done_testing;
diff --git a/testcases/t/298-ipc-misbehaving-connection.t b/testcases/t/298-ipc-misbehaving-connection.t
new file mode 100644
index 00000000..d53ee92d
--- /dev/null
+++ b/testcases/t/298-ipc-misbehaving-connection.t
@@ -0,0 +1,69 @@
+#!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 i3 will not hang if a connected client stops reading from its
+# subscription socket and that the client is killed after a delay.
+# Ticket: #2999
+# Bug still in: 4.15-180-g715cea61
+use i3test i3_config => <new(Peer => get_socket_path());
+my $magic = "i3-ipc";
+my $payload = '["workspace"]';
+my $message = $magic . pack("LL", length($payload), 2) . $payload;
+print $sock $message;
+
+# Constantly switch between 2 workspaces to generate events.
+fresh_workspace;
+open_window;
+fresh_workspace;
+open_window;
+
+eval {
+ local $SIG{ALRM} = sub { die "Timeout\n" };
+ # 500 is an arbitrarily large number to make sure that the socket becomes
+ # non-writeable.
+ for (my $i = 0; $i < 500; $i++) {
+ alarm 1;
+ cmd 'workspace back_and_forth';
+ alarm 0;
+ }
+};
+ok(!$@, 'i3 didn\'t hang');
+
+# Wait for connection timeout
+sleep 1;
+
+use IO::Select;
+my $s = IO::Select->new($sock);
+my $reached_eof = 0;
+while ($s->can_read(0.05)) {
+ if (read($sock, my $buffer, 100) == 0) {
+ $reached_eof = 1;
+ last;
+ }
+}
+ok($reached_eof, 'socket connection closed');
+
+close $sock;
+done_testing;
diff --git a/testcases/t/299-regress-scratchpad-focus.t b/testcases/t/299-regress-scratchpad-focus.t
new file mode 100644
index 00000000..504ecd32
--- /dev/null
+++ b/testcases/t/299-regress-scratchpad-focus.t
@@ -0,0 +1,33 @@
+#!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)
+#
+# Regression test: verify that a scratchpad container that was open in another
+# workspace and is moved to the current workspace after a 'scratchpad show' is
+# focused.
+# Ticket: #3361
+# Bug still in: 4.15-190-g4b3ff9cd
+use i3test;
+
+my $expected_focus = open_window;
+cmd 'move to scratchpad';
+cmd 'scratchpad show';
+my $ws = fresh_workspace;
+open_window;
+cmd 'scratchpad show';
+sync_with_i3;
+is($x->input_focus, $expected_focus->id, 'scratchpad window brought from other workspace is focused');
+
+done_testing;
diff --git a/testcases/t/300-restart-non-utf8.t b/testcases/t/300-restart-non-utf8.t
new file mode 100644
index 00000000..81b56583
--- /dev/null
+++ b/testcases/t/300-restart-non-utf8.t
@@ -0,0 +1,34 @@
+#!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)
+#
+# Verify that i3 does not crash when restart is issued while a window with a
+# title that contains non-UTF8 characters is open.
+# Ticket: #3156
+# Bug still in: 4.15-241-g9dc4df81
+use i3test;
+
+my $ws = fresh_workspace;
+open_window(name => "\x{AA} <-- invalid");
+
+cmd 'restart';
+does_i3_live;
+
+# Confirm that the invalid character got replaced with U+FFFD - "REPLACEMENT
+# CHARACTER"
+cmd '[title="^' . "\x{fffd}" . ' <-- invalid$"] fullscreen enable';
+is_num_fullscreen($ws, 1, 'title based criterion works');
+
+done_testing;
diff --git a/testcases/t/516-move.t b/testcases/t/516-move.t
index 5bcdb09a..3db8c4f0 100644
--- a/testcases/t/516-move.t
+++ b/testcases/t/516-move.t
@@ -97,4 +97,32 @@ is(scalar @{get_ws_content('left-top')}, 1, 'moved some window to left-bottom wo
$compare_window = shift @{get_ws_content('left-top')};
is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace');
+#####################################################################
+# Moving a fullscreen container should change its output.
+#####################################################################
+
+kill_all_windows;
+
+cmd 'workspace left-top';
+open_window;
+my $fs_window = open_window;
+open_window;
+
+cmd '[id=' . $fs_window->id . '] fullscreen enable, move right';
+is(scalar @{get_ws_content('right-top')}, 1, 'moved fullscreen window to right-top workspace');
+
+#####################################################################
+# Moving a global fullscreen container should not change its output.
+#####################################################################
+
+kill_all_windows;
+
+cmd 'workspace left-top';
+open_window;
+open_window;
+open_window;
+
+cmd 'fullscreen global, move right, fullscreen disable';
+is(scalar @{get_ws_content('right-top')}, 0, 'global fullscreen window didn\'t change workspace with move');
+
done_testing;
diff --git a/testcases/t/518-interpret-workspace-numbers.t b/testcases/t/518-interpret-workspace-numbers.t
index e9ea76c4..c7ab8367 100644
--- a/testcases/t/518-interpret-workspace-numbers.t
+++ b/testcases/t/518-interpret-workspace-numbers.t
@@ -26,25 +26,12 @@ workspace 1:override output fake-0
workspace 2 output fake-0
workspace 1 output fake-1
workspace 2:override output fake-1
+workspace 3 output fake-0
+workspace 3:override output doesnotexist fake-1
fake-outputs 1024x768+0+0,1024x768+1024+0
EOT
-my $i3 = i3(get_socket_path());
-$i3->connect->recv;
-
-# Returns the name of the output on which this workspace resides
-sub get_output_for_workspace {
- my $ws_name = shift @_;
-
- foreach (grep { not $_->{name} =~ /^__/ } @{$i3->get_tree->recv->{nodes}}) {
- my $output = $_->{name};
- foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) {
- return $output if $_->{nodes}[0]->{name} =~ $ws_name;
- }
- }
-}
-
################################################################################
# Workspace assignments with bare numbers should be interpreted as `workspace
# number` config directives. Any workspace beginning with that number should be
@@ -69,4 +56,10 @@ is(get_output_for_workspace('1:override'), 'fake-0',
'Assignment rules should not be affected by the order assignments are declared')
or diag 'Since workspace "1:override" is assigned by name to fake-0, it should open on fake-0';
+cmd 'focus output fake-1';
+cmd 'workspace "3:override"';
+is(get_output_for_workspace('3:override'), 'fake-1',
+ 'Assignment rules should not be affected by multiple output assignments')
+ or diag 'Since workspace "3:override" is assigned by name to fake-1, it should open on fake-1';
+
done_testing;
diff --git a/testcases/t/522-rename-assigned-workspace.t b/testcases/t/522-rename-assigned-workspace.t
index 981471f7..9897e4ee 100644
--- a/testcases/t/522-rename-assigned-workspace.t
+++ b/testcases/t/522-rename-assigned-workspace.t
@@ -28,23 +28,11 @@ workspace 1 output fake-0
workspace 2 output fake-1
workspace 3:foo output fake-1
workspace baz output fake-1
+workspace 5 output left
+workspace 6 output doesnotexist fake-0
+workspace 7 output fake-1 fake-0
EOT
-my $i3 = i3(get_socket_path());
-$i3->connect->recv;
-
-# Returns the name of the output on which this workspace resides
-sub get_output_for_workspace {
- my $ws_name = shift @_;
-
- foreach (grep { not $_->{name} =~ /^__/ } @{$i3->get_tree->recv->{nodes}}) {
- my $output = $_->{name};
- foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) {
- return $output if $_->{nodes}[0]->{name} =~ $ws_name;
- }
- }
-}
-
##########################################################################
# Renaming the workspace to an unassigned name does not move the workspace
# (regression test)
@@ -82,4 +70,62 @@ cmd 'rename workspace to baz';
is(get_output_for_workspace('baz'), 'fake-1',
'Renaming the workspace to a number and name should move it to the assigned output');
+##########################################################################
+# Renaming a workspace so that it is assigned a directional output does
+# not move the workspace or crash
+##########################################################################
+
+cmd 'focus output fake-0';
+cmd 'workspace bar';
+cmd 'rename workspace to 5';
+is(get_output_for_workspace('5'), 'fake-0',
+ 'Renaming the workspace to a workspace assigned to a directional output should not move the workspace');
+
+##########################################################################
+# Renaming an unfocused workspace, triggering an assignment to the output
+# which holds the currently focused empty workspace should result in the
+# original workspace replacing the empty one.
+# See issue #3228.
+##########################################################################
+
+cmd 'workspace baz';
+cmd 'rename workspace 5 to 2';
+is(get_output_for_workspace('2'), 'fake-1',
+ 'Renaming an unfocused workspace, triggering an assignment to the output which holds the currently focused empty workspace should result in the original workspace replacing the empty one');
+
+##########################################################################
+# Renaming an unfocused empty workspace, triggering an assignment to the
+# output which holds the currently focused non-empty workspace should
+# close the empty workspace and not crash i3.
+# See issue #3248.
+##########################################################################
+
+cmd 'workspace 1';
+cmd 'workspace 2';
+open_window;
+cmd 'rename workspace 1 to baz';
+is(get_output_for_workspace('baz'), '',
+ 'Renaming an unfocused empty workspace, triggering an assignment to the output which holds the currently focused non-empty workspace should close the empty workspace and not crash i3');
+kill_all_windows;
+
+##########################################################################
+# Renaming a workspace with multiple assignments, where the first output
+# doesn't exist.
+##########################################################################
+
+cmd 'focus output fake-1';
+cmd 'rename workspace to 6';
+is(get_output_for_workspace('6'), 'fake-0',
+ 'Renaming the workspace while first target output doesn\'t exist moves it to the second assigned output');
+
+##########################################################################
+# Renaming a workspace with multiple assignments, where both outputs exist
+# moves it to the first output.
+##########################################################################
+
+cmd 'focus output fake-0';
+cmd 'rename workspace to 7';
+is(get_output_for_workspace('7'), 'fake-1',
+ 'Renaming a workspace with multiple assignments, where both outputs exist moves it to the first output.');
+
done_testing;
diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t
index 87552785..3593ea0b 100644
--- a/testcases/t/525-i3bar-mouse-bindings.t
+++ b/testcases/t/525-i3bar-mouse-bindings.t
@@ -100,11 +100,19 @@ sub focus_subtest {
is_deeply(\@focus, $want, $msg);
}
+sub sync {
+ # Ensure XTEST events were sent to i3, which grabs and hence needs to
+ # forward any events to i3bar:
+ xtest_sync_with_i3;
+ # Ensure any pending i3bar IPC messages were handled by i3:
+ xtest_sync_with($i3bar_window);
+}
+
subtest 'button 1 moves focus left', \&focus_subtest,
sub {
xtest_button_press(1, 3, 3);
xtest_button_release(1, 3, 3);
- xtest_sync_with($i3bar_window);
+ sync;
},
[ $left->{id} ],
'button 1 moves focus left';
@@ -113,7 +121,7 @@ subtest 'button 2 moves focus right', \&focus_subtest,
sub {
xtest_button_press(2, 3, 3);
xtest_button_release(2, 3, 3);
- xtest_sync_with($i3bar_window);
+ sync;
},
[ $right->{id} ],
'button 2 moves focus right';
@@ -122,7 +130,7 @@ subtest 'button 3 moves focus left', \&focus_subtest,
sub {
xtest_button_press(3, 3, 3);
xtest_button_release(3, 3, 3);
- xtest_sync_with($i3bar_window);
+ sync;
},
[ $left->{id} ],
'button 3 moves focus left';
@@ -131,7 +139,7 @@ subtest 'button 4 moves focus right', \&focus_subtest,
sub {
xtest_button_press(4, 3, 3);
xtest_button_release(4, 3, 3);
- xtest_sync_with($i3bar_window);
+ sync;
},
[ $right->{id} ],
'button 4 moves focus right';
@@ -140,7 +148,7 @@ subtest 'button 5 moves focus left', \&focus_subtest,
sub {
xtest_button_press(5, 3, 3);
xtest_button_release(5, 3, 3);
- xtest_sync_with($i3bar_window);
+ sync;
},
[ $left->{id} ],
'button 5 moves focus left';
@@ -152,7 +160,7 @@ my $old_focus = get_focused($ws);
subtest 'button 6 does not move focus while pressed', \&focus_subtest,
sub {
xtest_button_press(6, 3, 3);
- xtest_sync_with($i3bar_window);
+ sync;
},
[],
'button 6 does not move focus while pressed';
@@ -161,7 +169,7 @@ is(get_focused($ws), $old_focus, 'focus unchanged');
subtest 'button 6 release moves focus right', \&focus_subtest,
sub {
xtest_button_release(6, 3, 3);
- xtest_sync_with($i3bar_window);
+ sync;
},
[ $right->{id} ],
'button 6 release moves focus right';
@@ -171,7 +179,7 @@ subtest 'button 6 release moves focus right', \&focus_subtest,
subtest 'button 7 press moves focus left', \&focus_subtest,
sub {
xtest_button_press(7, 3, 3);
- xtest_sync_with($i3bar_window);
+ sync;
},
[ $left->{id} ],
'button 7 press moves focus left';
@@ -179,7 +187,7 @@ subtest 'button 7 press moves focus left', \&focus_subtest,
subtest 'button 7 release moves focus right', \&focus_subtest,
sub {
xtest_button_release(7, 3, 3);
- xtest_sync_with($i3bar_window);
+ sync;
},
[ $right->{id} ],
'button 7 release moves focus right';
diff --git a/testcases/t/529-net-wm-desktop.t b/testcases/t/529-net-wm-desktop.t
index ae596203..f1da2980 100644
--- a/testcases/t/529-net-wm-desktop.t
+++ b/testcases/t/529-net-wm-desktop.t
@@ -31,15 +31,15 @@ use X11::XCB qw(:all);
sub get_net_wm_desktop {
sync_with_i3;
- my ($con) = @_;
+ my ($con) = @_;
my $cookie = $x->get_property(
- 0,
+ 0,
$con->{id},
$x->atom(name => '_NET_WM_DESKTOP')->id,
$x->atom(name => 'CARDINAL')->id,
- 0,
+ 0,
1
- );
+ );
my $reply = $x->get_property_reply($cookie->{sequence});
return undef if $reply->{length} != 1;
diff --git a/testcases/t/530-bug-2229.t b/testcases/t/530-bug-2229.t
index 91a9ef86..a177ee3c 100644
--- a/testcases/t/530-bug-2229.t
+++ b/testcases/t/530-bug-2229.t
@@ -24,8 +24,6 @@ fake-outputs 400x400+0+0,400x400+400+0
workspace_auto_back_and_forth no
EOT
-my $i3 = i3(get_socket_path());
-
# Set it up such that workspace 3 is on the left output and
# workspace 4 is on the right output
cmd 'focus output fake-0';
@@ -37,10 +35,6 @@ open_window;
cmd 'move workspace to output left';
-# ensure that workspace 3 has now vanished
-my $get_ws = $i3->get_workspaces->recv;
-my @ws_names = map { $_->{name} } @$get_ws;
-# TODO get rid of smartmatch
-ok(!('3' ~~ @ws_names), 'workspace 3 has been closed');
+ok(!workspace_exists('3'), 'workspace 3 has been closed');
done_testing;
diff --git a/testcases/t/541-resize-set-tiling.t b/testcases/t/541-resize-set-tiling.t
index 82267baf..0298fecd 100644
--- a/testcases/t/541-resize-set-tiling.t
+++ b/testcases/t/541-resize-set-tiling.t
@@ -39,11 +39,26 @@ my ($nodes, $focus) = get_ws_content($tmp);
cmp_float($nodes->[0]->{percent}, 0.25, 'left window got only 25%');
cmp_float($nodes->[1]->{percent}, 0.75, 'right window got 75%');
+# Same but use the 'width' keyword.
+cmd 'resize set width 80 ppt';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.20, 'left window got 20%');
+cmp_float($nodes->[1]->{percent}, 0.80, 'right window got 80%');
+
+# Same but with px.
+cmd 'resize set width 200 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{rect}->{width}, 200, 'right window got 200 px');
+
############################################################
# resize vertically
############################################################
-my $tmp = fresh_workspace;
+$tmp = fresh_workspace;
cmd 'split v';
@@ -56,20 +71,34 @@ is($x->input_focus, $bottom->id, 'Bottom window focused');
cmd 'resize set 0 ppt 75 ppt';
-my ($nodes, $focus) = get_ws_content($tmp);
+($nodes, $focus) = get_ws_content($tmp);
cmp_float($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
cmp_float($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+# Same but use the 'height' keyword.
+cmd 'resize set height 80 ppt';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.20, 'top window got 20%');
+cmp_float($nodes->[1]->{percent}, 0.80, 'bottom window got 80%');
+
+# Same but with px.
+cmd 'resize set height 200 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{rect}->{height}, 200, 'bottom window got 200 px');
############################################################
# resize horizontally and vertically
############################################################
-my $tmp = fresh_workspace;
+$tmp = fresh_workspace;
cmd 'split h';
-my $left = open_window;
+$left = open_window;
my $top_right = open_window;
cmd 'split v';
my $bottom_right = open_window;
@@ -80,23 +109,47 @@ is($x->input_focus, $bottom_right->id, 'Bottom-right window focused');
cmd 'resize set 75 ppt 75 ppt';
-my ($nodes, $focus) = get_ws_content($tmp);
+($nodes, $focus) = get_ws_content($tmp);
cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.25, 'top-right window got 25%');
cmp_float($nodes->[1]->{nodes}->[1]->{percent}, 0.75, 'bottom-right window got 75%');
+# Same but with px.
+cmd 'resize set 155 px 135 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{width}, 155, 'bottom-right window got 155 px width');
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{height}, 135, 'bottom-right window got 135 px height');
+
+# Without specifying mode
+cmd 'resize set 201 131';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{width}, 201, 'bottom-right window got 201 px width');
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{height}, 131, 'bottom-right window got 131 px height');
+
+# Mix ppt and px
+cmd 'resize set 75 ppt 200 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
+cmp_float($nodes->[1]->{nodes}->[1]->{rect}->{height}, 200, 'bottom-right window got 200 px height');
############################################################
# resize from inside a tabbed container
############################################################
-my $tmp = fresh_workspace;
+$tmp = fresh_workspace;
cmd 'split h';
-my $left = open_window;
+$left = open_window;
my $right1 = open_window;
cmd 'split h';
@@ -110,27 +163,33 @@ is($x->input_focus, $right2->id, '2nd right window focused');
cmd 'resize set 75 ppt 0 ppt';
-my ($nodes, $focus) = get_ws_content($tmp);
+($nodes, $focus) = get_ws_content($tmp);
cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
+# Same but with px.
+cmd 'resize set 155 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{rect}->{width}, 155, 'right container got 155 px');
############################################################
# resize from inside a stacked container
############################################################
-my $tmp = fresh_workspace;
+$tmp = fresh_workspace;
cmd 'split h';
-my $left = open_window;
-my $right1 = open_window;
+$left = open_window;
+$right1 = open_window;
cmd 'split h';
cmd 'layout stacked';
-my $right2 = open_window;
+$right2 = open_window;
diag("left = " . $left->id . ", right1 = " . $right1->id . ", right2 = " . $right2->id);
@@ -138,10 +197,16 @@ is($x->input_focus, $right2->id, '2nd right window focused');
cmd 'resize set 75 ppt 0 ppt';
-my ($nodes, $focus) = get_ws_content($tmp);
+($nodes, $focus) = get_ws_content($tmp);
cmp_float($nodes->[0]->{percent}, 0.25, 'left container got 25%');
cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
+# Same but with px.
+cmd 'resize set 130 px';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[1]->{rect}->{width}, 130, 'right container got 130 px');
done_testing;
diff --git a/travis/check-formatting.sh b/travis/check-formatting.sh
index ff406bea..d232339c 100755
--- a/travis/check-formatting.sh
+++ b/travis/check-formatting.sh
@@ -3,4 +3,4 @@
set -e
set -x
-clang-format-3.8 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
+clang-format-4.0 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
diff --git a/travis/travis-base-386.Dockerfile b/travis/travis-base-386.Dockerfile
index ddb3874e..e6ad51f8 100644
--- a/travis/travis-base-386.Dockerfile
+++ b/travis/travis-base-386.Dockerfile
@@ -13,12 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
# (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
# Install mk-build-deps (for installing the i3 build dependencies),
-# clang and clang-format-3.8 (for checking formatting and building with clang),
+# clang and clang-format-4.0 (for checking formatting and building with clang),
# lintian (for checking spelling errors),
RUN linux32 apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
dpkg-dev devscripts git equivs \
- clang clang-format-3.8 \
+ clang clang-format-4.0 \
lintian && \
rm -rf /var/lib/apt/lists/*
diff --git a/travis/travis-base-ubuntu-386.Dockerfile b/travis/travis-base-ubuntu-386.Dockerfile
index 1014407a..82e5ca29 100644
--- a/travis/travis-base-ubuntu-386.Dockerfile
+++ b/travis/travis-base-ubuntu-386.Dockerfile
@@ -13,12 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
# (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
# Install mk-build-deps (for installing the i3 build dependencies),
-# clang and clang-format-3.8 (for checking formatting and building with clang),
+# clang and clang-format-4.0 (for checking formatting and building with clang),
# lintian (for checking spelling errors),
RUN linux32 apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
dpkg-dev devscripts git equivs \
- clang clang-format-3.8 \
+ clang clang-format-4.0 \
lintian && \
rm -rf /var/lib/apt/lists/*
diff --git a/travis/travis-base-ubuntu.Dockerfile b/travis/travis-base-ubuntu.Dockerfile
index 0b4ec206..05066ec8 100644
--- a/travis/travis-base-ubuntu.Dockerfile
+++ b/travis/travis-base-ubuntu.Dockerfile
@@ -13,13 +13,13 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
# (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
# Install mk-build-deps (for installing the i3 build dependencies),
-# clang and clang-format-3.8 (for checking formatting and building with clang),
+# clang and clang-format-4.0 (for checking formatting and building with clang),
# lintian (for checking spelling errors),
# test suite dependencies (for running tests)
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
dpkg-dev devscripts git equivs \
- clang clang-format-3.8 \
+ clang clang-format-4.0 \
lintian && \
rm -rf /var/lib/apt/lists/*
diff --git a/travis/travis-base.Dockerfile b/travis/travis-base.Dockerfile
index 7eafb9fb..907002e8 100644
--- a/travis/travis-base.Dockerfile
+++ b/travis/travis-base.Dockerfile
@@ -11,19 +11,20 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
# (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
# Install mk-build-deps (for installing the i3 build dependencies),
-# clang and clang-format-3.8 (for checking formatting and building with clang),
+# clang and clang-format-4.0 (for checking formatting and building with clang),
# lintian (for checking spelling errors),
# test suite dependencies (for running tests)
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
dpkg-dev devscripts git equivs \
- clang clang-format-3.8 \
+ clang clang-format-4.0 \
lintian \
libmodule-install-perl libanyevent-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libjson-xs-perl x11-xserver-utils && \
rm -rf /var/lib/apt/lists/*
# Install i3 build dependencies.
COPY debian/control /usr/src/i3-debian-packaging/control
+COPY debian/changelog /usr/src/i3-debian-packaging/changelog
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \
rm -rf /var/lib/apt/lists/*