-Output of `i3 --moreversion 2>&- || i3 --version`:
+<!--
+PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
+-->
-_REPLACE: i3 version output_
+## I'm submitting a…
+<!-- Please check one of the following options with "x" -->
+<pre>
+[ ] Bug
+[ ] Feature Request
+[ ] Documentation Request
+[ ] Other (Please describe in detail)
+</pre>
-URL to a logfile as per https://i3wm.org/docs/debugging.html:
+## Current Behavior
+<!--
+Describe the current behavior,
+e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
+-->
-_REPLACE: URL to logfile_
+## Expected Behavior
+<!--
+Describe the desired behavior you expect after mitigation of the issue,
+e.g., »The window left next to the current window should be focused.«
+-->
-**What I did:**
+## Reproduction Instructions
+<!--
+For bug reports, please provide detailed instructions on how the bug can be reproduced.
+For feature requests you can remove this section.
-_REPLACE: e.g. "I’m pressing Alt+j (focus left)"_
+E.g., »Open three windows in a V[A H[B C]] layout on a new workspace«
+-->
-**What I saw:**
+## Environment
+<!--
+Please include your exact i3 version.
+Note that we only support the latest major release and the current development version. If you are using an older version of i3, please first update to the current release version and reproduce the issue there.
+-->
+Output of `i3 --moreversion 2>&-`:
+<pre>
+i3 version:
+</pre>
-_REPLACE: e.g. "i3 changed focus to the window ABOVE the current window"_
+<!--
+For bug reports, please include your (complete) i3 config with which the issue occurs. You can either paste the file directly or provide a link to a service such as pastebin.
-**What I expected instead:**
-_REPLACE: e.g. "Focus should be on the window to the left"_
+If you would like to help debugging the issue, please try to reduce the config such that it is as close to the default config as possible while still reproducing the issue. This can help us bisect the root cause.
+-->
+<pre>
+</pre>
+
+<!--
+Providing a logfile can help us trace the root cause of an issue much quicker. You can learn how to generate the logfile here:
+https://i3wm.org/docs/debugging.html
+
+Providing the logfile is optional.
+-->
+<pre>
+Logfile URL:
+</pre>
+
+<!--
+Please also answer the questions below to help us process your issue faster. If you have any other information to share, please add it here as well.
+-->
+<pre>
+- Linux Distribution & Version:
+- Are you using a compositor (e.g., xcompmgr or compton):
+</pre>
--- /dev/null
+---
+name: Bug report
+about: Create a report to help us improve
+---
+
+<!--
+PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
+-->
+
+## I'm submitting a…
+<!-- Please check one of the following options with "x" -->
+<pre>
+[x] Bug
+[ ] Feature Request
+[ ] Documentation Request
+[ ] Other (Please describe in detail)
+</pre>
+
+## Current Behavior
+<!--
+Describe the current behavior,
+e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
+-->
+
+## Expected Behavior
+<!--
+Describe the desired behavior you expect after mitigation of the issue,
+e.g., »The window left next to the current window should be focused.«
+-->
+
+## Reproduction Instructions
+<!--
+Please provide detailed instructions on how the bug can be reproduced.
+E.g., »Open three windows in a V[A H[B C]] layout on a new workspace«
+-->
+
+## Environment
+<!--
+Please include your exact i3 version.
+Note that we only support the latest major release and the current development version. If you are using an older version of i3, please first update to the current release version and reproduce the issue there.
+-->
+Output of `i3 --moreversion 2>&-`:
+<pre>
+i3 version:
+</pre>
+
+<!--
+Please include your (complete) i3 config with which the issue occurs. You can either paste the file directly or provide a link to a service such as pastebin.
+
+If you would like to help debugging the issue, please try to reduce the config such that it is as close to the default config as possible while still reproducing the issue. This can help us bisect the root cause.
+-->
+<pre>
+</pre>
+
+<!--
+Providing a logfile can help us trace the root cause of an issue much quicker. You can learn how to generate the logfile here:
+https://i3wm.org/docs/debugging.html
+
+Providing the logfile is optional.
+-->
+<pre>
+Logfile URL:
+</pre>
+
+<!--
+Please also answer the questions below to help us process your issue faster. If you have any other information to share, please add it here as well.
+-->
+<pre>
+- Linux Distribution & Version:
+- Are you using a compositor (e.g., xcompmgr or compton):
+</pre>
--- /dev/null
+---
+name: Feature request
+about: Suggest an idea for this project
+---
+
+<!--
+PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
+-->
+
+## I'm submitting a…
+<!-- Please check one of the following options with "x" -->
+<pre>
+[ ] Bug
+[x] Feature Request
+[ ] Documentation Request
+[ ] Other (Please describe in detail)
+</pre>
+
+## Current Behavior
+<!--
+Describe the current behavior,
+e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
+-->
+
+## Desired Behavior
+<!--
+Describe the desired behavior you expect after mitigation of the issue,
+e.g., »The window left next to the current window should be focused.«
+-->
+
+## Environment
+<!--
+Please include your exact i3 version.
+Note that we only support the latest major release and the current development version. If you are using an older version of i3, please first update to the current release version and reproduce the issue there.
+-->
+Output of `i3 --moreversion 2>&-`:
+<pre>
+i3 version:
+</pre>
+
+<!--
+Please also answer the questions below to help us process your issue faster. If you have any other information to share, please add it here as well.
+-->
+<pre>
+- Linux Distribution & Version:
+- Are you using a compositor (e.g., xcompmgr or compton):
+</pre>
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
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} } );
$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
"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/ │
-└──────────────┴────────┴────────┴───────────────────────────────────────────────────────────┘
+â\94\8câ\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¬â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¬â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¬â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\90
+│ dependency │ min. │ lkgv │ URL │
+â\94\9câ\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¼â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¼â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¼â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94¤
+│ 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/ │
+â\94\94â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94´â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94´â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94´â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\80â\94\98
¹ 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
-4.15-non-git
+4.16-non-git
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 \
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 \
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 = \
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)
include/shmlog.h \
include/sighandler.h \
include/startup.h \
+ include/sync.h \
include/tree.h \
include/util.h \
include/window.h \
src/sd-daemon.c \
src/sighandler.c \
src/startup.c \
+ src/sync.c \
src/tree.c \
src/util.c \
src/version.c \
=====================================================
[![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.
+++ /dev/null
-
- ┌────────────────────────────┐
- │ 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 <criteria> 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 <window_mode>”
- • 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 <criteria> [→] [workspace] [number] <workspace>”
- • introduce “assign <criteria> [→] output left|right|up|down|primary|<output>”
- • introduce a “focus_wrapping” option (subsumes “force_focus_wrapping”)
- • introduce percentage point resizing for floating containers:
- “resize set <width> [px | ppt] <height> [px | ppt]”
- • introduce “resize set <width> ppt <height> 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
--- /dev/null
+
+ ┌────────────────────────────┐
+ │ 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
# 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])
# 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
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
#!/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();
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;
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 {
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
+i3-wm (4.15.1-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org> Sat, 10 Mar 2018 17:27:26 +0100
+
i3-wm (4.15-1) unstable; urgency=medium
* New upstream release.
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
== 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:
---------------------------------------------------------------------
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::
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
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*:
------------------------------------------
"name": "ethernet",
"instance": "eth0",
"button": 1,
+ "modifiers": ["Shift", "Mod1"],
"x": 1320,
"y": 1400,
"relative_x": 12,
| 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:
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
"width": 1280,
"height": 782
},
+ "window_properties": {
+ "class": "Evince",
+ "instance": "evince",
+ "title": "Properties",
+ "transient_for": 52428808
+ },
"floating_nodes": [],
"nodes": [
{ "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]]
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
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
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"]
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
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*:
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 <<command_criteria>>. 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 <<command_criteria>>. The difference between +assign+ and
++for_window <criteria> 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
*Syntax*:
-------------------------------------
-workspace <workspace> output <output>
+workspace <workspace> output <output1> [output2]…
-------------------------------------
The 'output' is the name of the RandR output you attach your screen to. On a
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
---------------------------
----------------------------
You can then use the +i3-msg+ application to perform any command listed in
-the next section.
+<<list_of_commands>>.
=== Focus follows mouse
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
}
------------------------
-=== 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
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*:
}
--------------------------------------
+[[list_of_commands]]
== List of commands
Commands are what you bind to specific keypresses. You can also issue commands
# defaults to 10 pixels.
move <left|right|down|up> [<px> 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 <pos_x> [px] <pos_y> [px]
+# Moves the container to the specified pos_x and pos_y
+# coordinates on the screen.
+move position <pos_x> [px] <pos_y> [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
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 <<back_and_forth>> for this specific call
-only.
++--no-auto-back-and-forth+ to disable <<workspace_auto_back_and_forth>> for this
+specific call only.
To move containers to specific workspaces, use +move container to workspace+.
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+,
*Syntax*:
-------------------------------------------------------
resize grow|shrink <direction> [<px> px [or <ppt> ppt]]
-resize set <width> [px | ppt] <height> [px | ppt]
+resize set [width] <width> [px | ppt]
+resize set height <height> [px | ppt]
+resize set [width] <width> [px | ppt] [height] <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 <width> or <height> 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 <width> or <height> means "do not
+resize in this direction".
It is recommended to define bindings for resizing in a dedicated binding mode.
See <<binding_modes>> and the example in the i3
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
*Syntax*:
-----------------------------------------------
-border normal|pixel [<n>]
-border none|toggle
+border normal|pixel|toggle [<n>]
+border none
# legacy syntax, equivalent to "border pixel 1"
border 1pixel
# 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" {
# 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" {
($line =~ /
^\s* # skip leading whitespace
([a-z_]+ \s* = \s*|) # optional identifier
- (.*?) -> \s* # token
+ (.*?) -> \s* # token
(.*) # optional action
/x);
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-launchee.h>
+
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>
static uint8_t xkb_base_event;
static uint8_t xkb_base_error;
-static void finish();
+static void finish(void);
#include "GENERATED_config_enums.h"
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;
}
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;
}
* 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");
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;
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'},
{"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) {
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 <socket>] [-v]\n");
+ printf("i3-config-wizard [-s <socket>] [-m win|alt] [-v] [-h]\n");
return 0;
}
}
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;
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);
exit(-1);
}
+ /* Startup complete. */
+ if (sncontext) {
+ sn_launchee_context_complete(sncontext);
+ sn_launchee_context_unref(sncontext);
+ }
+
xcb_flush(conn);
xcb_generic_event_t *event;
/* 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;
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 */
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;
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') {
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 <socket>] [-t <type>] <message>\n");
+ printf("i3-msg [-s <socket>] [-t <type>] [-m] <message>\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 */
err(EXIT_FAILURE, "IPC: write()");
free(payload);
- if (quiet)
- return 0;
-
uint32_t reply_length;
uint32_t reply_type;
uint8_t *reply;
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);
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);
+ }
+
+ if (!(reply_type & I3_IPC_EVENT_MASK)) {
+ errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected an event", reply_type);
+ }
- goto exit;
+ 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);
#include <xcb/randr.h>
#include <xcb/xcb_cursor.h>
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-launchee.h>
+
#include "i3-nagbar.h"
/** This is the equivalent of XC_left_ptr. I’m not sure why xcb doesn’t have a
char *action;
int16_t x;
uint16_t width;
+ bool terminal;
} button_t;
static xcb_window_t win;
}
/*
- * 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.
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);
}
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);
{"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.");
break;
case 'h':
printf("i3-nagbar " I3_VERSION "\n");
- printf("i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
+ printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
return 0;
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);
#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;
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);
/* 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);
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)) {
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 ];
}
# 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
*/
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;
* 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);
} 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;
* depend on 'config'.
*
*/
-char *init_xcb_early();
+char *init_xcb_early(void);
/**
* Initialization which depends on 'config' being usable. Called after the
*
*/
#include "common.h"
+#include "yajl_utils.h"
#include <stdlib.h>
#include <unistd.h>
#include <yajl/yajl_gen.h>
#include <paths.h>
+#include <xcb/xcb_keysyms.h>
+
/* Global variables for child_*() */
i3bar_child child;
* 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);
* 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)
* 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)
* 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",
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;
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();
}
}
-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;
}
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);
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"))) {
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;
}
return 1;
}
+ if (!strcmp(cur_key, "modifier")) {
+ DLOG("modifier = %lld\n", val);
+ config.modifier = (uint32_t)val;
+ return 1;
+ }
+
return 0;
}
* 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 */
}
* 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);
* 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 */
}
* 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();
* 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-
/* 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);
}
* 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) {
* 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);
* 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);
* 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;
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])
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);
* 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");
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 <bar_id>\tBar ID for which to get the configuration\n");
printf("-s, --socket <sock_path>\tConnect to i3 via <sock_path>\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"
* 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");
{"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);
case 'b':
config.bar_id = sstrdup(optarg);
break;
+ case 'V':
+ config.verbose = true;
+ break;
default:
print_usage(argv[0]);
exit(EXIT_SUCCESS);
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);
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);
/* 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;
/* 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;
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) {
}
}
-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;
/*
* 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);
* 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;
}
* Unhides all bars (maps them)
*
*/
-void unhide_bars(void) {
+static void unhide_bars(void) {
if (config.hide_on_modifier != M_HIDE) {
return;
}
* 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;
/* 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;
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;
}
}
}
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;
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");
}
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;
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,
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");
* 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)) {
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);
* 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) {
}
/*
* 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)) {
* 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) {
* 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,
* 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];
/* 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;
&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
*
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) {
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 |
}
}
}
+
+ /* 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;
+ }
}
/*
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;
}
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,
I3STRING_FREE(binding.name);
binding = *current;
activated_mode = binding.name != NULL;
- return;
}
#include "fake_outputs.h"
#include "display_version.h"
#include "restore_layout.h"
+#include "sync.h"
#include "main.h"
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)
xmacro(_NET_REQUEST_FRAME_EXTENTS)
xmacro(_NET_FRAME_EXTENTS)
xmacro(_MOTIF_WM_HINTS)
+xmacro(WM_CHANGE_STATE)
* Implementation of 'move <direction> [<pixels> [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'.
* Implementation of 'move [window|container] [to] [absolute] position <px> [px] <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
*/
void cmd_bar(I3_CMD, const char *bar_type, const char *bar_value, const char *bar_id);
-/*
+/**
* Implementation of 'shmlog <size>|toggle|on|off'
*
*/
void cmd_shmlog(I3_CMD, const char *argument);
-/*
+/**
* Implementation of 'debuglog toggle|on|off'
*
*/
#include <yajl/yajl_gen.h>
-/*
+/**
* 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.
*/
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);
*/
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
*
*/
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.
*/
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
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);
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);
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);
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.
* 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;
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;
* '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;
* 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.
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;
};
/** 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;
*/
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.
*
* 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
/** 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
*
#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)
* 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;
* 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);
*/
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)
/**
* 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
*/
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
#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.
* 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.
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);
#include <config.h>
-/*
+/**
* 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.
*
*/
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);
* 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);
*/
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.
*/
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.
*/
Output *get_output_next_wrap(direction_t direction, Output *current);
-/*
+/**
* Creates an output covering the root window.
*
*/
#include <config.h>
-/* 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;
*/
void render_con(Con *con, bool render_fullscreen);
-/*
+/**
* Returns the height for the decorations
+ *
*/
int render_deco_height(void);
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);
* 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),
/* 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.
*
*/
* 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).
--- /dev/null
+/*
+ * 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 <xcb/xcb.h>
+
+void sync_respond(xcb_window_t window, uint32_t rnd);
* 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).
#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);) \
#if defined(__OpenBSD__) || defined(__APPLE__)
-/*
+/**
* Taken from FreeBSD
* Find the first occurrence of the byte string s in byte string l.
*
*
*/
ssize_t slurp(const char *path, char **buf);
+
+/**
+ * Convert a direction to its corresponding orientation.
+ *
+ */
+orientation_t orientation_from_direction(direction_t direction);
#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
* 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);
*/
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)
*
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;
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);
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.
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.
cairo_restore(surface->cr);
}
-/**
+/*
* Clears a surface with the given color.
*
*/
cairo_restore(surface->cr);
}
-/**
+/*
* Copies a surface onto another surface.
*
*/
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);
error->error_code);
}
}
- if (error != NULL) {
- free(error);
- }
+ free(error);
font.pattern = sstrdup(pattern);
LOG("Using X font %s\n", pattern);
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;
pango_font_green = foreground.green;
pango_font_blue = foreground.blue;
break;
- default:
- assert(false);
- break;
}
}
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);
}
}
draw_text_pango(text, strlen(text),
drawable, root_visual_type, x, y, max_width, false);
return;
- default:
- assert(false);
}
}
/* 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);
}
#include <stdint.h>
#include <string.h>
-#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
/*
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);
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;
}
--- /dev/null
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "libi3.h"
+
+#include <string.h>
+#include <glib.h>
+
+/* 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
/* 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 */
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) {
}
/*
- * 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) {
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, ":");
}
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;
}
* 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")".
char *sep = strrchr(copy, '/');
if (sep == NULL) {
- if (copy != NULL) {
- free(copy);
- copy = NULL;
- }
+ free(copy);
return -1;
}
*sep = '\0';
} 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);
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;
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;
*
*/
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);
}
/*
/*
* 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;
}
return str;
}
-/**
+/*
* Copies the given i3string.
* Note that this will not free the source string.
*/
}
/* 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);
== 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
i3-input(1)
-=========
+===========
Michael Stapelberg <michael+i3@stapelberg.de>
v4.1.2, April 2012
*-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.
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
# Dump the layout tree
i3-msg -t get_tree
+
+# Monitor window changes
+i3-msg -t subscribe -m '[ "window" ]'
------------------------------------------------
== ENVIRONMENT
== SYNOPSIS
-i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v]
+i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]
== OPTIONS
*-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
i3-sensible-editor(1)
-===================
+=====================
Michael Stapelberg <michael+i3@stapelberg.de>
v4.1, November 2011
i3-sensible-pager(1)
-===================
+====================
Michael Stapelberg <michael+i3@stapelberg.de>
v4.1, November 2011
* 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.
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.
# border normal|pixel [<n>]
# 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)
'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
-> 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
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 <string>
state MODE:
'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
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
-> WORKSPACE_OUTPUT_STR
state WORKSPACE_OUTPUT_STR:
- output = word
+ output = string
-> call cfg_workspace($workspace, $output)
# ipc-socket <path>
path = string
-> call cfg_ipc_socket($path)
+# ipc_kill_timeout
+state IPC_KILL_TIMEOUT:
+ timeout = number
+ -> call cfg_ipc_kill_timeout(&timeout)
+
# restart_state <path> (for testcases)
state RESTART_STATE:
path = string
'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
'}'
-> 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
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
#!/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
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
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')
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
echo " git push"
echo ""
echo " cd ${TMPDIR}/debian"
-echo " dput *.changes"
+echo " dput"
echo ""
echo " cd ${TMPDIR}"
echo " sendmail -t < email.txt"
/* 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;
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. */
*/
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
/* 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);
break;
}
}
- if (!found_keycode) {
- continue;
- }
+ }
+ if (!found_keycode) {
+ continue;
}
/* If this binding is a release binding, it matches the key which the
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;
+ if (result) {
+ break;
+ }
+ continue;
}
/* 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 ((bind->release == B_UPON_KEYPRESS && is_release)) {
continue;
}
- break;
+ 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;
}
/*
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
}
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. */
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
}
/*
*
*/
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 = 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;
- struct xkb_state *dummy_state_numlock_no_shift = xkb_state_new(xkb_keymap);
- if (dummy_state_numlock_no_shift == NULL) {
+ 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");
- return;
+ goto out;
}
- bool has_errors = false;
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)) {
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);
}
}
+#undef ADD_TRANSLATED_KEY
+
/*
* Switches the key bindings to the given mode, if the mode exists
*
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"));
return;
}
- ELOG("ERROR: Mode not found\n");
+ ELOG("Mode not found\n");
}
static int reorder_binding_cmp(const void *a, const void *b) {
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)
.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);
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;
}
}
case BORDER_BOTTOM:
search_direction = D_DOWN;
break;
- default:
- assert(false);
- break;
}
bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
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);
/* 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) {
}
}
+#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'.
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;
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;
}
ysuccess(true);
}
-/**
+/*
* Implementation of 'move [window|container] [to] workspace back_and_forth'.
*
*/
* Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace <name>'.
*
*/
-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);
* Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace number <name>'.
*
*/
-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);
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) {
- floating_con->rect.height += px;
- } else if (strcmp(direction, "down") == 0 || strcmp(direction, "height") == 0) {
+ if (orientation == VERT) {
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) */
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;
}
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);
} 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;
}
}
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 <width> [px | ppt] <height> [px | ppt]'.
*
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);
}
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);
}
}
}
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 [<n>]', 'border none|1pixel|toggle'.
*
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);
}
*
*/
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;
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;
}
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;
}
*/
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);
*/
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;
}
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;
}
owindow *current = TAILQ_FIRST(&owindows);
if (current == NULL) {
- ysuccess(false);
+ yerror("Given criteria don't match a window");
return;
}
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);
}
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;
}
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);
}
*
*/
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;
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);
}
* Implementation of 'move <direction> [<pixels> [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;
}
}
output = get_output_from_string(current_output, name);
if (!output) {
- LOG("No such output found.\n");
- ysuccess(false);
+ yerror("No such output found.");
return;
}
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;
}
* Implementation of 'move [window|container] [to] [absolute] position <px> [px] <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;
continue;
}
- if (strcmp(method, "absolute") == 0) {
- current->con->parent->rect.x = x;
- current->con->parent->rect.y = y;
-
- DLOG("moving to absolute position %ld %ld\n", x, y);
- floating_maybe_reassign_ws(current->con->parent);
- cmd_output->needs_tree_render = true;
- }
-
- if (strcmp(method, "position") == 0) {
- Rect newrect = current->con->parent->rect;
+ Rect newrect = current->con->parent->rect;
- DLOG("moving to position %ld %ld\n", x, y);
- newrect.x = x;
- newrect.y = y;
+ 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);
}
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);
}
/*
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;
}
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;
}
} 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;
}
} 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);
}
*/
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) {
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;
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. */
/* 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;
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();
* Implementation of 'bar mode dock|hide|invisible|toggle [<bar_id>]'
*
*/
-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)
* Implementation of 'bar hidden_state hide|show|toggle [<bar_id>]'
*
*/
-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)
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);
}
// 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;
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);
}
/*
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.
*
*/
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
*
*/
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.
num += con_num_windows(current);
}
+ TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
+ num += con_num_windows(current);
+ }
+
return num;
}
/* 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;
}
/* 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) {
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) {
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
/* 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) {
/* 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,
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;
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);
}
/*
*
*/
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 */
next = TAILQ_FIRST(&(next->focus_head));
}
+ if (con->type == CT_FLOATING_CON && next != con->parent) {
+ next = con_descend_focused(next);
+ }
+
return next;
}
*
*/
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;
}
* now let's activate the current layout (next in list) */
if (current_layout_found) {
new_layout = layout;
- free(tm_dup);
break;
}
current_layout_found = true;
}
}
+ free(tm_dup);
if (new_layout != L_DEFAULT) {
con_set_layout(con, new_layout);
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;
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;
}
}
/* 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
/* 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;
}
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
*
* 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);
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);
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)) {
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();
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));
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) {
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);
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;
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)
******************************************************************************/
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) {
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) {
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;
}
}
+/*
+ * 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.
*
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.
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;
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
/* 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) &&
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;
/* 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",
/* 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);
}
}
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);
}
return;
}
- const bool set_focus = (con == focused);
-
Con *ws = con_get_workspace(con);
- Con *parent = con->parent;
-
- /* 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;
+ 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);
- /* 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);
}
*
*/
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");
}
/* 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)
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;
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
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 */
+ }
}
/*
* 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;
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);
* 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;
}
/*
focused_id = XCB_NONE;
con_focus(con_descend_focused(con));
tree_render();
-
- return;
}
/*
ungrab_all_keys(conn);
translate_keysyms();
grab_all_keys(conn);
-
- return;
}
/*
add_ignore_event(event->sequence, -1);
manage_window(event->window, cookie, false);
- return;
}
/*
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 */
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) {
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;
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();
scratchpad_fix_resolution();
ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
-
- return;
}
/*
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:
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
} 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
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(
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
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);
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:
}
}
-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;
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;
/* 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);
}
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;
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;
*/
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;
/* We update focused_id because we don’t need to set focus again */
focused_id = event->event;
tree_render();
- return;
}
/*
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)
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)
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;
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.
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);
+ }
}
}
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);
}
}
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. */
}
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);
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);
y(map_open);
+ ystr("first");
+ y(bool, false);
+
ystr("payload");
yajl_gen_string(gen, (unsigned char *)message, message_size);
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,
handle_get_binding_modes,
handle_get_config,
handle_send_tick,
+ handle_sync,
};
/*
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);
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
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);
}
y(free);
}
-/**
+/*
* For the window events we send, along the usual "change" field,
* also the window container, in "container".
*/
setlocale(LC_NUMERIC, "");
}
-/**
+/*
* For the barconfig update events, we send the serialized barconfig.
*/
void ipc_send_barconfig_update_event(Barconfig *barconfig) {
/* 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);
free(marks[i]);
}
- free(marks);
- marks = NULL;
+ FREE(marks);
num_marks = 0;
}
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;
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;
}
* 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;
/* 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”:
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;
}
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);
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)) ||
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. */
}
}
}
+ 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);
/* 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
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/…
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
free(geom);
out:
free(attr);
- return;
}
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) {
}
}
- 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) {
*/
#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);
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.
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
*
*/
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);
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();
}
/*
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
/* 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);
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;
}
* 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);
}
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);
#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) {
* 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;
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));
}
}
}
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.
}
/*
- * 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;
}
/*
/* 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;
/* 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);
Con *ws = create_workspace_on_output(output, content);
/* TODO: Set focus in main.c */
- con_activate(ws);
+ con_focus(ws);
}
/*
}
/* 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) {
/* 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;
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 */
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);
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");
}
}
static int *precalculate_sizes(Con *con, render_params *p) {
+ if ((con->layout != L_SPLITH && con->layout != L_SPLITV) || p->children <= 0) {
+ return NULL;
+ }
+
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));
+ 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++] = 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;
- }
+ 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;
}
}
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) {
}
/* 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 &&
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);
+}
+
+/*
+ * 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);
+}
- /* TODO: previously, we were getting a rect containing all screens. why? */
+/*
+ * 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);
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;
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);
}
* 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;
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
* 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;
}
}
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;
}
}
* 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
if (current == active) {
DLOG("Window is a scratchpad window, hiding it.\n");
scratchpad_move(con);
- return;
+ return true;
}
}
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,
}
con_activate(con_descend_focused(con));
+
+ return true;
}
/*
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) {
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;
}
}
}
-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;
if (!sequence) {
DLOG("Sequence already deleted, nevermind.\n");
+ free(w);
return;
}
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.
}
/*
- * 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).
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);
}
}
-/**
+/*
* Renames workspaces that are mentioned in the startup sequences.
*
*/
}
}
-/**
+/*
* Gets the stored startup sequence for the _NET_STARTUP_ID of a given window.
*
*/
--- /dev/null
+/*
+ * 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);
+}
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);
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
* 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);
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;
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;
}
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);
/* 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();
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 */
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);
/* 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,
#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);
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;
+}
* 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
}
}
+/*
+ * 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
*
*/
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);
*/
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);
/* 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;
- }
-
- if (assigned)
+ Con *assigned = get_assigned_output(target_name, -1);
+ if (assigned && assigned != output->con) {
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
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);
* 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) {
/*
* 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.
*
*/
}
}
-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))
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
* 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);
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;
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.
*
void workspace_show_by_name(const char *num) {
Con *workspace;
workspace = workspace_get(num, NULL);
- _workspace_show(workspace);
+ workspace_show(workspace);
}
/*
return new;
}
-/**
+/*
* Creates a new container and re-parents all of children from the given
* workspace into it.
*
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) {
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);
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);
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
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().
}
}
-/*
- * 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) {
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);
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)
*
/* 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);
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("");
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);
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);
* 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. */
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);
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);
* 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;
}
* 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 {
static char *sun_path = NULL;
-void cleanup_socket(void) {
+static void cleanup_socket(void) {
if (sun_path != NULL) {
unlink(sun_path);
free(sun_path);
use Exporter ();
our @EXPORT = qw(
get_workspace_names
+ get_output_for_workspace
get_unused_workspace
fresh_workspace
get_ws_content
kill_all_windows
events_for
listen_for_binding
+ is_net_wm_state_focused
);
=head1 NAME
[ 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
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 <michael@i3wm.org>
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);
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;
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
-#
+#
#
use i3test;
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
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;
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.
# | S1 | S2 |
# +----+----+
-my $tmp = fresh_workspace;
-
-################################################################################
-# Open the left window.
-################################################################################
+sub verify_focus {
+ # Report original line
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
-my $left = open_window({ background_color => '#ff0000' });
+ my ($expected, $msg) = @_;
+ $expected = $expected->id;
-is($x->input_focus, $left->id, 'left window focused');
-
-diag("left = " . $left->id);
+ # Ensure the expected focus if the test fails.
+ cmd "[id=$expected] focus" unless is($x->input_focus, $expected, $msg);
+}
################################################################################
-# Open the right window.
+# Verify that window opened behind fullscreen window will get focus after the
+# fullscreen window gets moved to a different workspace.
################################################################################
-my $right = open_window({ background_color => '#00ff00' });
-
-diag("right = " . $right->id);
-
-################################################################################
-# Set the right window to fullscreen.
-################################################################################
+fresh_workspace;
+my $left = open_window;
+verify_focus($left, 'left window focused');
+diag("left = " . $left->id);
-cmd 'nop setting fullscreen';
+my $right = open_window;
cmd 'fullscreen';
+diag("right = " . $right->id);
-################################################################################
-# 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);
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';
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;
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.
################################################################################
verify_move(2, 'prevented move up');
################################################################################
+# (depends on previous layout)
# Moving to a different workspace is allowed with per-output fullscreen
# containers.
################################################################################
verify_move(1, 'did not prevent move to workspace by position');
################################################################################
+# (depends on previous layout)
# Ensure that is not allowed with global fullscreen containers.
################################################################################
cmd "workspace $tmp2";
cmd 'focus parent';
-cmd 'fullscreen';
+cmd 'fullscreen disable';
cmd 'fullscreen global';
cmd 'focus child';
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
################################################################################
# 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');
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;
$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;
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;
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';
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';
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;
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;
'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,'),
#
use i3test i3_autostart => 0;
+use X11::XCB qw/PROP_MODE_REPLACE/;
################################################################################
# 1: check floating_minimum_size (with non-default limits)
exit_gracefully($pid);
+################################################################################
+# 8: check minimum_size and maximum_size set by WM_NORMAL_HINTS
+################################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $min_width = 150;
+my $min_height = 100;
+my $max_width = 250;
+my $max_height = 200;
+
+my sub open_with_max_size {
+ # The type of the WM_NORMAL_HINTS property is WM_SIZE_HINTS
+ # https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.2.3
+ my $XCB_ICCCM_SIZE_HINT_P_MIN_SIZE = 0x32;
+ my $XCB_ICCCM_SIZE_HINT_P_MAX_SIZE = 0x16;
+
+ my $flags = $XCB_ICCCM_SIZE_HINT_P_MIN_SIZE | $XCB_ICCCM_SIZE_HINT_P_MAX_SIZE;
+
+ my $pad = 0x00;
+
+ my $window = open_window(
+ before_map => 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;
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
$expected = <<'EOT';
cfg_bar_start()
cfg_bar_output(LVDS-1)
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'verbose', 'colors', '}'
+ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'colors', '}'
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: bar {
ERROR: CONFIG: Line 2: output LVDS-1
###############################################################################
# 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',
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
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 = <<EOT;
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);
#####################################################################
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
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.
###############################################################################
($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.
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;
##########################################################################
# 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.
##########################################################################
##########################################################################
# 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.
##########################################################################
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;
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) = @_;
+
+ 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};
+}
-@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');
+################################################################################
+# 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;
+cmd 'resize set 33 ppt 20 ppt';
+do_test(int(0.33 * 1333), int(0.2 * 999));
-@content = @{get_ws($tmp)->{floating_nodes}};
-is(@content, 1, 'one floating node on this ws');
+################################################################################
+# Mix ppt and px in a single resize set command
+################################################################################
-$oldrect = $content[0]->{rect};
+cmd 'resize set 44 ppt 111 px';
+do_test(int(0.44 * 1333), 111);
-cmd 'resize set 33 ppt 20 ppt';
-my $expected_width = int(0.33 * 1333);
-my $expected_height = int(0.2 * 999);
+cmd 'resize set 222 px 100 ppt';
+do_test(222, 999);
+
+################################################################################
+# Zero is interpreted as no change.
+# See issue: #3276.
+################################################################################
-@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");
+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);
################################################################################
-# Mix ppt and px in a single resize set command
+# Use 'width' and 'height' keywords.
+# See issue: #3275.
################################################################################
-cmd 'resize set 44 ppt 111 px';
-$expected_width = int(0.44 * 1333);
-$expected_height = 111;
+cmd 'resize set width 200 px';
+do_test(200, 0);
-@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");
+cmd 'resize set height 200 px';
+do_test(0, 200);
-cmd 'resize set 222 px 100 ppt';
-$expected_width = 222;
-$expected_height = 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");
+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;
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;
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.
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);
# 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;
'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;
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;
#
# Tests sticky windows.
# Ticket: #1455
-use i3test;
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+workspace ws-on-0 output fake-0
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
my ($ws, $tmp, $focused);
# nothing happens.
###############################################################################
fresh_workspace;
-open_window(wm_class => '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
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
$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
# 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;
###############################################################################
# +---+---+ 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.
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');
#
# Verify that the corrent focus stack order is preserved after various
# operations.
-use i3test;
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
sub kill_and_confirm_focus {
my $focus = shift;
}
my @windows;
+my $ws;
sub focus_windows {
for (my $i = $#windows; $i >= 0; $i--) {
#####################################################################
fresh_workspace;
-
$windows[3] = open_window;
$windows[1] = open_window;
$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;
--- /dev/null
+#!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;
--- /dev/null
+#!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 => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
+
+fresh_workspace(output => 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;
--- /dev/null
+#!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 = <<EOT;
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
+
+bindsym Mod1+x workspace bindingname
+
+workspace 9 output doesnotexist
+workspace special output fake-0
+workspace 1 output doesnotexist
+workspace dontusethisname output doesnotexist
+workspace donotoverride output fake-0
+workspace 2 output fake-0
+workspace 3 output fake-0
+EOT
+
+my $pid = launch_with_config($config);
+
+sub check_output {
+ my ($workspace, $output, $msg) = @_;
+ is(get_output_for_workspace($workspace), $output, $msg);
+}
+
+check_output('9', '', 'Numbered workspace with a big number that is assigned to output that does not exist is not used');
+check_output('special', 'fake-0', 'Output gets special workspace because of assignment');
+check_output('bindingname', 'fake-1', 'Bindings give workspace names');
+check_output('1', 'fake-2', 'Numbered workspace that is assigned to output that does not exist is used');
+check_output('2', '', 'Numbered workspace assigned to output with existing workspace is not used');
+check_output('3', '', 'Numbered workspace assigned to output with existing workspace is not used');
+check_output('4', 'fake-3', 'First available number that is not assigned to existing output is used');
+check_output('dontusethisname', '', 'Named workspace that is assigned to output that does not exist is not used');
+check_output('donotoverride', '', 'Named workspace assigned to already occupied output is not used');
+
+exit_gracefully($pid);
+
+################################################################################
+# Test multiple assignments
+################################################################################
+
+sub do_test {
+ my ($workspace, $output, $msg) = @_;
+ cmd 'focus output ' . ($output eq 'fake-3' ? 'fake-0' : 'fake-3');
+
+ cmd "workspace $workspace";
+ check_output($workspace, $output, $msg)
+}
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0P,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
+
+workspace 1 output fake-0
+workspace 1 output fake-1 fake-2
+workspace 2 output fake-3 fake-4 fake-0 fake-1
+workspace 3 output these outputs do not exist but these do: fake-0 fake-3
+workspace 4 output whitespace fake-0
+workspace special output doesnotexist1 doesnotexist2 doesnotexist3
+EOT
+
+$pid = launch_with_config($config);
+
+do_test('1', 'fake-0', 'Multiple assignments do not override a single one');
+do_test('2', 'fake-3', 'First output out of multiple assignments is used');
+do_test('3', 'fake-0', 'First existing output is used');
+do_test('4', 'fake-0', 'Excessive whitespace is ok');
+do_test('5', 'fake-1', 'Numbered initialization for fake-1');
+do_test('6', 'fake-2', 'Numbered initialization for fake-2');
+
+cmd 'focus output fake-0, workspace special';
+check_output('special', 'fake-0', 'Workspace with only non-existing assigned outputs opened in current output.');
+
+# Moving assigned workspaces.
+cmd 'workspace 2, move workspace to output left';
+check_output('2', 'fake-2', 'Moved assigned workspace up');
+
+exit_gracefully($pid);
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Tests if scrolling the tab bar on a tabbed container works and verifies that
+# only one window is focused as a result.
+# Ticket: #3215 (PR)
+# Bug still in: 4.15-92-g666aa9e0
+use i3test;
+use i3test::XTEST;
+
+sub scroll_down {
+ # button5 = scroll down
+ xtest_button_press(5, 3, 3);
+ xtest_button_release(5, 3, 3);
+ xtest_sync_with_i3;
+}
+
+sub scroll_up {
+ # button4 = scroll up
+ xtest_button_press(4, 3, 3);
+ xtest_button_release(4, 3, 3);
+ xtest_sync_with_i3;
+}
+
+# Decoration of top left window.
+$x->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;
--- /dev/null
+#!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 => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+# Set the timeout to 500ms to reduce the duration of this test.
+ipc_kill_timeout 500
+EOT
+
+# Manually connect to i3 so that we can choose to not read events
+use IO::Socket::UNIX;
+my $sock = IO::Socket::UNIX->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;
--- /dev/null
+#!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;
--- /dev/null
+#!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;
$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;
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
'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;
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)
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;
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';
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';
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';
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';
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';
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';
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';
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';
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';
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;
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';
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;
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';
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;
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';
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);
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;
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)
# (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/*
# (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/*
# (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/*
# (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/*