--- /dev/null
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+
+[*.{c,h}]
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_UBUNTU} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_386} linux32 ./travis/debian-build.sh deb/debian-i386/DIST
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_UBUNTU_386} linux32 ./travis/debian-build.sh deb/ubuntu-i386/DIST
- - ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/clang-analyze.sh
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/docs.sh
- ./travis/skip-pkg.sh || travis/prep-bintray.sh
use AnyEvent;
use Encode;
use Scalar::Util qw(tainted);
+use Carp;
=head1 NAME
use constant TYPE_GET_VERSION => 7;
use constant TYPE_GET_BINDING_MODES => 8;
use constant TYPE_GET_CONFIG => 9;
+use constant TYPE_SEND_TICK => 10;
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_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK)
] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
barconfig_update => ($event_mask | 4),
binding => ($event_mask | 5),
shutdown => ($event_mask | 6),
+ tick => ($event_mask | 7),
_error => 0xFFFFFFFF,
);
# We use getpwuid() instead of $ENV{HOME} because the latter is tainted
# and thus produces warnings when running tests with perl -T
my $home = (getpwuid($<))[7];
- die "Could not get home directory" unless $home and -d $home;
+ confess "Could not get home directory" unless $home and -d $home;
$path =~ s/~/$home/g;
}
sub message {
my ($self, $type, $content) = @_;
- die "No message type specified" unless defined($type);
+ confess "No message type specified" unless defined($type);
- die "No connection to i3" unless defined($self->{ipchdl});
+ confess "No connection to i3" unless defined($self->{ipchdl});
my $payload = "";
if ($content) {
return if defined($self->{ipchdl});
- $self->connect->recv or die "Unable to connect to i3 (socket path " . $self->{path} . ")";
+ $self->connect->recv or confess "Unable to connect to i3 (socket path " . $self->{path} . ")";
}
=head2 get_workspaces
$self->message(TYPE_GET_CONFIG);
}
+=head2 send_tick
+
+Sends a tick event. Requires i3 >= 4.15
+
+=cut
+sub send_tick {
+ my ($self, $payload) = @_;
+
+ $self->_ensure_connection;
+
+ $self->message(TYPE_SEND_TICK, $payload);
+}
=head2 command($content)
-4.14.1-non-git
+4.15-non-git
AnyEvent-I3/t/manifest.t \
AnyEvent-I3/t/pod-coverage.t \
AnyEvent-I3/t/pod.t \
+ contrib/dump-asy.pl \
+ contrib/gtk-tree-watch.pl \
+ contrib/i3-wsbar \
+ contrib/per-workspace-layout.pl \
+ contrib/trivial-bar-script.sh \
docs/asciidoc-git.conf \
docs/bigpicture.png \
docs/i3-pod2html \
I3_VERSION \
LICENSE \
PACKAGE-MAINTAINER \
- RELEASE-NOTES-4.14.1 \
+ RELEASE-NOTES-4.15 \
generate-command-parser.pl \
parser-specs/commands.spec \
parser-specs/config.spec \
--- /dev/null
+![Logo](docs/logo-30.png) i3: A tiling window manager
+=====================================================
+
+[![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)
+
+i3 is a tiling window manager for X11.
+
+For more information about i3, please see [the project's website](https://i3wm.org/) and [online documentation](https://i3wm.org/docs/).
+
+For information about contributing to i3, please see [CONTRIBUTING.md](.github/CONTRIBUTING.md).
--- /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
# Run autoreconf -fi to generate a configure script from this file.
AC_PREREQ([2.69])
-AC_INIT([i3], [4.14.1], [https://github.com/i3/i3/issues])
+AC_INIT([i3], [4.15], [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])
use Data::Dumper;
use AnyEvent::I3;
use File::Temp;
+use File::Basename;
use v5.10;
+use IPC::Cmd qw[can_run];
+
+# 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';
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};
+ my $na = ($n->{name} or "[Empty]");
$na =~ s/#/\\#/g;
$na =~ s/\$/\\\$/g;
$na =~ s/&/\\&/g;
$na =~ s/~/\\textasciitilde{}/g;
my $type = 'leaf';
if (!defined($n->{window})) {
- $type = $n->{orientation} . '-split';
+ $type = $n->{layout};
}
my $name = qq|``$na'' ($type)|;
close($tmp);
my $rep = "$tmp";
$rep =~ s/asy$/eps/;
-system("cd /tmp && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep");
+my $tmp_dir = dirname($rep);
+system("cd $tmp_dir && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep");
use AnyEvent;
use AnyEvent::I3;
use v5.10;
+use utf8;
my %layouts = (
'4' => 'tabbed',
+i3-wm (4.14.2-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org> Mon, 25 Sep 2017 08:55:22 +0200
+
+i3-wm (4.14.1-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org> Sun, 24 Sep 2017 19:21:15 +0200
+
i3-wm (4.14-1) unstable; urgency=medium
* New upstream release.
* Bugfix: Use correct format string in load_layout (fixes crash in restart)
* Bugfix: Fix border rendering (border lines were "cutting" through)
* Bugfix: Raise floating windows immediately when dragging/resizing
- * Bugfix: Make focus switching work accross outputs again
+ * Bugfix: Make focus switching work across outputs again
* Bugfix: migration-script: handle resize top/bottom correctly
* Bugfix: Fix focus issue when moving containers to workspaces
* Bugfix: Warp cursor when changing outputs again
debian/tmp/etc
debian/tmp/usr
+contrib/dump-asy.pl usr/share/doc/i3-wm/examples/
+contrib/gtk-tree-watch.pl usr/share/doc/i3-wm/examples/
+contrib/i3-wsbar usr/share/doc/i3-wm/examples/
+contrib/per-workspace-layout.pl usr/share/doc/i3-wm/examples/
+contrib/trivial-bar-script.sh usr/share/doc/i3-wm/examples/
# The default is /usr/share/doc/i3
dh_auto_configure -- --docdir=/usr/share/doc/i3-wm
+override_dh_builddeb:
+ # bintray does not support xz currently.
+ dh_builddeb -- -Zgzip
+
%:
dh $@ --parallel --builddirectory=build --with=autoreconf
you found the section which clearly highlights the problem, additional
information might be necessary to completely diagnose the problem.
-When debugging with us in IRC, be prepared to use a so called nopaste service
+When debugging with us in IRC, be prepared to use a so-called nopaste service
such as https://pastebin.com because pasting large amounts of text in IRC
sometimes leads to incomplete lines (servers have line length limitations) or
flood kicks.
markup::
A string that indicates how the text of the block should be parsed. Set to
+"pango"+ to use https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup].
- Set to +"none"+ to not use any markup (default).
+ Set to +"none"+ to not use any markup (default). Pango markup only works
+ if you use a pango font.
If you want to put in your own entries into a block, prefix the key with an
underscore (_). i3bar will ignore all keys it doesn’t understand, and prefixing
X11 root window coordinates where the click occurred
button::
X11 button ID (for example 1 to 3 for left/middle/right mouse button)
+relative_x, relative_y::
+ Coordinates where the click occurred, with respect to the top left corner
+ of the block
+width, height::
+ Width and height (in px) of the block
*Example*:
------------------------------------------
"instance": "eth0",
"button": 1,
"x": 1320,
- "y": 1400
+ "y": 1400,
+ "relative_x": 12,
+ "relative_y": 8,
+ "width": 50,
+ "height": 22
}
------------------------------------------
| 7 | +GET_VERSION+ | <<_version_reply,VERSION>> | Gets the i3 version.
| 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.
|======================================================
So, a typical message could look like this:
Reply to the GET_BINDING_MODES message.
GET_CONFIG (9)::
Reply to the GET_CONFIG message.
+TICK (10)::
+ Reply to the SEND_TICK message.
[[_command_reply]]
=== COMMAND reply
{ "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" }
-------------------
+[[_tick_reply]]
+=== TICK reply
+
+The reply is a map containing the "success" member. After the reply was
+received, the tick event has been written to all IPC connections which subscribe
+to tick events. UNIX sockets are usually buffered, but you can be certain that
+once you receive the tick event you just triggered, you must have received all
+events generated prior to the +SEND_TICK+ message (happened-before relation).
+
+*Example:*
+-------------------
+{ "success": true }
+-------------------
== Events
mouse
shutdown (6)::
Sent when the ipc shuts down because of a restart or exit by user command
+tick (7)::
+ Sent when the ipc client subscribes to the tick event (with +"first":
+ true+) or when any ipc client sends a SEND_TICK message (with +"first":
+ false+).
*Example:*
--------------------------------------------------------------------
}
---------------------------
+=== tick event
+
+This event is triggered by a subscription to tick events or by a +SEND_TICK+
+message.
+
+*Example (upon subscription):*
+--------------------------------------------------------------------------------
+{
+ "first": true,
+ "payload": ""
+}
+--------------------------------------------------------------------------------
+
+*Example (upon +SEND_TICK+ with a payload of +arbitrary string+):*
+--------------------------------------------------------------------------------
+{
+ "first": false,
+ "payload": "arbitrary string"
+}
+--------------------------------------------------------------------------------
+
== See also (existing libraries)
[[libraries]]
* https://github.com/drmgc/i3ipcpp
Go::
* https://github.com/mdirkse/i3ipc-go
+ * https://github.com/i3/go-i3
JavaScript::
* https://github.com/acrisci/i3ipc-gjs
Lua::
payload. Then, receive the pending +COMMAND+ message reply in big endian.
5. From here on out, send/receive all messages using the detected byte order.
+
+Find an example implementation of this technique in
+https://github.com/i3/go-i3/blob/master/byteorder.go
always be found under the symlink +latest/+. Unless told differently, it will
run the tests on a separate X server instance (using Xephyr).
-Xephyr will open a window where you can inspect the running test. You can run
-the tests without an X session with Xvfb, such as with +xvfb-run
-./complete-run+. This will also speed up the tests significantly especially on
-machines without a powerful video card.
+Xephyr will open a window where you can inspect the running test. By default,
+tests are run under Xvfb.
.Example invocation of +complete-run.pl+
---------------------------------------
i3 User’s Guide
===============
Michael Stapelberg <michael@i3wm.org>
-March 2013
This document contains all the information you need to configure and use the i3
window manager. If it does not, please check https://www.reddit.com/r/i3wm/
== Default keybindings
For the "too long; didn’t read" people, here is an overview of the default
-keybindings (click to see the full size image):
+keybindings (click to see the full-size image):
*Keys to use with $mod (Alt):*
Throughout this guide, the keyword +$mod+ will be used to refer to the
configured modifier. This is the Alt key (+Mod1+) by default, with the Windows
-key (+Mod4+) being a popular alternative.
+key (+Mod4+) being a popular alternative that largely prevents conflicts with
+application-defined shortcuts.
=== Opening terminals and moving around
=== The tree consists of Containers
-The building blocks of our tree are so called +Containers+. A +Container+ can
+The building blocks of our tree are so-called +Containers+. A +Container+ can
host a window (meaning an X11 window, one that you can actually see and use,
like a browser). Alternatively, it could contain one or more +Containers+. A
simple example is the workspace: When you start i3 with a single monitor, a
=== The floating modifier
To move floating windows with your mouse, you can either grab their titlebar
-or configure the so called floating modifier which you can then press and
+or configure the so-called floating modifier which you can then press and
click anywhere in the window itself to move it. The most common setup is to
use the same key you use for managing windows (Mod1 for example). Then
you can press Mod1, click into a window using your left mouse button, and drag
workspace_layout tabbed
---------------------
-=== Border style for new windows
+=== Default border style for new windows
This option determines which border style new windows will have. The default is
-+normal+. Note that new_float applies only to windows which are starting out as
++normal+. Note that default_floating_border applies only to windows which are starting out as
floating windows, e.g., dialog windows, but not windows that are floated later on.
*Syntax*:
---------------------------------------------
-new_window normal|none|pixel
-new_window normal|pixel <px>
-new_float normal|none|pixel
-new_float normal|pixel <px>
+default_border normal|none|pixel
+default_border normal|pixel <px>
+default_floating_border normal|none|pixel
+default_floating_border normal|pixel <px>
---------------------------------------------
+Please note that +new_window+ and +new_float+ have been deprecated in favor of the above options
+and will be removed in a future release. We strongly recommend using the new options instead.
+
*Example*:
---------------------
-new_window pixel
+default_border pixel
---------------------
The "normal" and "pixel" border styles support an optional border width in
*Example*:
---------------------
-# The same as new_window none
-new_window pixel 0
+# The same as default_border none
+default_border pixel 0
# A 3 px border
-new_window pixel 3
+default_border pixel 3
---------------------
window (mapping means actually displaying it on the screen), you’d need to have
to match on 'Firefox' in this case.
+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
+workspace such as +left+ and +down+.
+
Assignments are processed by i3 in the order in which they appear in the config
file. The first one which matches the window wins and later assignments are not
considered.
*Syntax*:
------------------------------------------------------------
-assign <criteria> [→] [workspace] <workspace>
+assign <criteria> [→] [workspace] [number] <workspace>
+assign <criteria> [→] output left|right|up|down|primary|<output>
------------------------------------------------------------
*Examples*:
# Assignment to a named workspace
assign [class="^URxvt$"] → work
+# Assign to the workspace with number 2, regardless of name
+assign [class="^URxvt$"] → number 2
+
+# You can also specify a number + name. If the workspace with number 2 exists, assign will skip the text part.
+assign [class="^URxvt$"] → number "2: work"
+
# Start urxvt -name irssi
assign [class="^URxvt$" instance="^irssi$"] → 3
+
+# Assign urxvt to the output right of the current one
+assign [class="^URxvt$"] → output right
+
+# Assign urxvt to the primary output
+assign [class="^URxvt$"] → output primary
----------------------
-Note that the arrow is not required, it just looks good :-). If you decide to
+Note that you might not have a primary output configured yet. To do so, run:
+-------------------------
+xrandr --output <output> --primary
+-------------------------
+
+Also, the arrow is not required, it just looks good :-). If you decide to
use it, it has to be a UTF-8 encoded arrow, not `->` or something like that.
To get the class and instance, you can use +xprop+. After clicking on the
=== Focus wrapping
-When being in a tabbed or stacked container, the first container will be
-focused when you use +focus down+ on the last container -- the focus wraps. If
-however there is another stacked/tabbed container in that direction, focus will
-be set on that container. This is the default behavior so you can navigate to
-all your windows without having to use +focus parent+.
+By default, when in a container with several windows or child containers, the
+opposite window will be focused when trying to move the focus over the edge of
+a container (and there are no other containers in that direction) -- the focus
+wraps.
+
+If desired, you can disable this behavior by setting the +focus_wrapping+
+configuration directive to the value +no+.
+
+When enabled, focus wrapping does not occur by default if there is another
+window or container in the specified direction, and focus will instead be set
+on that window or container. This is the default behavior so you can navigate
+to all your windows without having to use +focus parent+.
If you want the focus to *always* wrap and you are aware of using +focus
-parent+ to switch to different containers, you can use the
-+force_focus_wrapping+ configuration directive. After enabling it, the focus
-will always wrap.
+parent+ to switch to different containers, you can instead set +focus_wrapping+
+to the value +force+.
*Syntax*:
---------------------------
-force_focus_wrapping yes|no
----------------------------
+focus_wrapping yes|no|force
-*Example*:
-------------------------
+# Legacy syntax, equivalent to "focus_wrapping force"
force_focus_wrapping yes
-------------------------
+---------------------------
+
+*Examples*:
+-----------------
+# Disable focus wrapping
+focus_wrapping no
+
+# Force focus wrapping
+focus_wrapping force
+-----------------
=== Forcing Xinerama
*Syntax*:
----------------------------
-bindsym button<n> <command>
+bindsym [--release] button<n> <command>
----------------------------
*Example*:
bar {
# disable clicking on workspace buttons
bindsym button1 nop
+ # Take a screenshot by right clicking on the bar
+ bindsym --release button3 exec --no-startup-id import /tmp/latest-screenshot.png
# execute custom script when scrolling downwards
bindsym button5 exec ~/.i3/scripts/custom_wheel_down
}
To change focus, you can use the +focus+ command. The following options are
available:
+<criteria>::
+ Sets focus to the container that matches the specified criteria.
+ See <<command_criteria>>.
left|right|up|down::
Sets focus to the nearest container in the given direction.
parent::
*Syntax*:
----------------------------------------------
+<criteria> focus
focus left|right|down|up
focus parent|child|floating|tiling|mode_toggle
focus output left|right|up|down|primary|<output>
*Examples*:
-------------------------------------------------
+# Focus firefox
+bindsym $mod+F1 [class="Firefox"] focus
+
# Focus container on the left, bottom, top, right
bindsym $mod+j focus left
bindsym $mod+k focus down
bindsym $mod+x move container to output primary
--------------------------------------------------------
--------------------------------
Note that you might not have a primary output configured yet. To do so, run:
-------------------------
xrandr --output <output> --primary
*Syntax*:
-------------------------------------------------------
resize grow|shrink <direction> [<px> px [or <ppt> ppt]]
-resize set <width> [px] <height> [px]
+resize set <width> [px | ppt] <height> [px | ppt]
-------------------------------------------------------
Direction can either be one of +up+, +down+, +left+ or +right+. Or you can be
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). Note that +resize set+ will only work for
-floating containers.
+default is 10 percentage points).
+
+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.
It is recommended to define bindings for resizing in a dedicated binding mode.
See <<binding_modes>> and the example in the i3
*Examples*:
---------------------------------------
# Read 1 character and mark the current window with this character
-bindsym $mod+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
+bindsym $mod+m exec i3-input -F 'mark %s' -l 1 -P 'Mark: '
# Read 1 character and go to the window with the character
-bindsym $mod+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
+bindsym $mod+g exec i3-input -F '[con_mark="%s"] focus' -l 1 -P 'Goto: '
---------------------------------------
Alternatively, if you do not want to mess with +i3-input+, you could create
# The font above is very space-efficient, that is, it looks good, sharp and
# clear in small sizes. However, its unicode glyph coverage is limited, the old
# X core fonts rendering does not support right-to-left and this being a bitmap
-# font, it doesn’t scale on retina/hidpi displays.
+# font, it doesn't scale on retina/hidpi displays.
# use these keys for focus, movement, and resize directions when reaching for
# the arrows is not convenient
# If there are multiple scratchpad windows, this command cycles through them.
bindsym Mod1+minus scratchpad show
+# Define names for default workspaces for which we configure key bindings later on.
+# We use variables to avoid repeating the names in multiple places.
+set $ws1 "1"
+set $ws2 "2"
+set $ws3 "3"
+set $ws4 "4"
+set $ws5 "5"
+set $ws6 "6"
+set $ws7 "7"
+set $ws8 "8"
+set $ws9 "9"
+set $ws10 "10"
+
+
# switch to workspace
-bindsym Mod1+1 workspace 1
-bindsym Mod1+2 workspace 2
-bindsym Mod1+3 workspace 3
-bindsym Mod1+4 workspace 4
-bindsym Mod1+5 workspace 5
-bindsym Mod1+6 workspace 6
-bindsym Mod1+7 workspace 7
-bindsym Mod1+8 workspace 8
-bindsym Mod1+9 workspace 9
-bindsym Mod1+0 workspace 10
+bindsym Mod1+1 workspace $ws1
+bindsym Mod1+2 workspace $ws2
+bindsym Mod1+3 workspace $ws3
+bindsym Mod1+4 workspace $ws4
+bindsym Mod1+5 workspace $ws5
+bindsym Mod1+6 workspace $ws6
+bindsym Mod1+7 workspace $ws7
+bindsym Mod1+8 workspace $ws8
+bindsym Mod1+9 workspace $ws9
+bindsym Mod1+0 workspace $ws10
# move focused container to workspace
-bindsym Mod1+Shift+1 move container to workspace 1
-bindsym Mod1+Shift+2 move container to workspace 2
-bindsym Mod1+Shift+3 move container to workspace 3
-bindsym Mod1+Shift+4 move container to workspace 4
-bindsym Mod1+Shift+5 move container to workspace 5
-bindsym Mod1+Shift+6 move container to workspace 6
-bindsym Mod1+Shift+7 move container to workspace 7
-bindsym Mod1+Shift+8 move container to workspace 8
-bindsym Mod1+Shift+9 move container to workspace 9
-bindsym Mod1+Shift+0 move container to workspace 10
+bindsym Mod1+Shift+1 move container to workspace $ws1
+bindsym Mod1+Shift+2 move container to workspace $ws2
+bindsym Mod1+Shift+3 move container to workspace $ws3
+bindsym Mod1+Shift+4 move container to workspace $ws4
+bindsym Mod1+Shift+5 move container to workspace $ws5
+bindsym Mod1+Shift+6 move container to workspace $ws6
+bindsym Mod1+Shift+7 move container to workspace $ws7
+bindsym Mod1+Shift+8 move container to workspace $ws8
+bindsym Mod1+Shift+9 move container to workspace $ws9
+bindsym Mod1+Shift+0 move container to workspace $ws10
# reload the configuration file
bindsym Mod1+Shift+c reload
bindsym Up resize shrink height 10 px or 10 ppt
bindsym Right resize grow width 10 px or 10 ppt
- # back to normal: Enter or Escape
+ # back to normal: Enter or Escape or Mod1+r
bindsym Return mode "default"
bindsym Escape mode "default"
+ bindsym Mod1+r mode "default"
}
bindsym Mod1+r mode "resize"
# focus the child container
#bindsym $mod+d focus child
+# Define names for default workspaces for which we configure key bindings later on.
+# We use variables to avoid repeating the names in multiple places.
+set $ws1 "1"
+set $ws2 "2"
+set $ws3 "3"
+set $ws4 "4"
+set $ws5 "5"
+set $ws6 "6"
+set $ws7 "7"
+set $ws8 "8"
+set $ws9 "9"
+set $ws10 "10"
+
# switch to workspace
-bindcode $mod+10 workspace 1
-bindcode $mod+11 workspace 2
-bindcode $mod+12 workspace 3
-bindcode $mod+13 workspace 4
-bindcode $mod+14 workspace 5
-bindcode $mod+15 workspace 6
-bindcode $mod+16 workspace 7
-bindcode $mod+17 workspace 8
-bindcode $mod+18 workspace 9
-bindcode $mod+19 workspace 10
+bindcode $mod+10 workspace $ws1
+bindcode $mod+11 workspace $ws2
+bindcode $mod+12 workspace $ws3
+bindcode $mod+13 workspace $ws4
+bindcode $mod+14 workspace $ws5
+bindcode $mod+15 workspace $ws6
+bindcode $mod+16 workspace $ws7
+bindcode $mod+17 workspace $ws8
+bindcode $mod+18 workspace $ws9
+bindcode $mod+19 workspace $ws10
# move focused container to workspace
-bindcode $mod+Shift+10 move container to workspace 1
-bindcode $mod+Shift+11 move container to workspace 2
-bindcode $mod+Shift+12 move container to workspace 3
-bindcode $mod+Shift+13 move container to workspace 4
-bindcode $mod+Shift+14 move container to workspace 5
-bindcode $mod+Shift+15 move container to workspace 6
-bindcode $mod+Shift+16 move container to workspace 7
-bindcode $mod+Shift+17 move container to workspace 8
-bindcode $mod+Shift+18 move container to workspace 9
-bindcode $mod+Shift+19 move container to workspace 10
+bindcode $mod+Shift+10 move container to workspace $ws1
+bindcode $mod+Shift+11 move container to workspace $ws2
+bindcode $mod+Shift+12 move container to workspace $ws3
+bindcode $mod+Shift+13 move container to workspace $ws4
+bindcode $mod+Shift+14 move container to workspace $ws5
+bindcode $mod+Shift+15 move container to workspace $ws6
+bindcode $mod+Shift+16 move container to workspace $ws7
+bindcode $mod+Shift+17 move container to workspace $ws8
+bindcode $mod+Shift+18 move container to workspace $ws9
+bindcode $mod+Shift+19 move container to workspace $ws10
# reload the configuration file
bindcode $mod+Shift+54 reload
bindcode 111 resize shrink height 10 px or 10 ppt
bindcode 114 resize grow width 10 px or 10 ppt
- # back to normal: Enter or Escape
+ # back to normal: Enter or Escape or $mod+r
bindcode 36 mode "default"
bindcode 9 mode "default"
+ bindcode $mod+27 mode "default"
}
bindcode $mod+27 mode "resize"
open(my $enumfh, '>', "GENERATED_${prefix}_enums.h");
-# XXX: we might want to have a way to do this without a trailing comma, but gcc
-# seems to eat it.
my %statenum;
say $enumfh 'typedef enum {';
my $cnt = 0;
for my $state (@keys, '__CALL') {
- say $enumfh " $state = $cnt,";
+ say $enumfh ',' if $cnt > 0;
+ print $enumfh " $state = $cnt";
$statenum{$state} = $cnt;
$cnt++;
}
-say $enumfh '} cmdp_state;';
+say $enumfh "\n} cmdp_state;";
close($enumfh);
# Third step: Generate the call function.
$next_state = '__CALL';
}
my $identifier = $token->{identifier};
- say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } }, |;
+ say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } },|;
}
say $tokfh '};';
}
#error "SYSCONFDIR not defined"
#endif
-#define FREE(pointer) \
- do { \
- if (pointer != NULL) { \
- free(pointer); \
- pointer = NULL; \
- } \
+#define FREE(pointer) \
+ do { \
+ free(pointer); \
+ pointer = NULL; \
} while (0)
#include "xcb.h"
static i3Font font;
static i3Font bold_font;
static int char_width;
-static char *socket_path;
+static char *socket_path = NULL;
static xcb_window_t win;
static surface_t surface;
static xcb_key_symbols_t *symbols;
int main(int argc, char *argv[]) {
char *xdg_config_home;
- socket_path = getenv("I3SOCK");
char *pattern = "pango:monospace 8";
char *patternbold = "pango:monospace bold 8";
int o, option_index = 0;
&xkb_base_error) != 1)
errx(EXIT_FAILURE, "Could not setup XKB extension.");
- if (socket_path == NULL)
- socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);
-
- if (socket_path == NULL)
- socket_path = "/tmp/i3-ipc.sock";
-
keysyms = xcb_key_symbols_alloc(conn);
xcb_get_modifier_mapping_cookie_t modmap_cookie;
modmap_cookie = xcb_get_modifier_mapping(conn);
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
+#include <signal.h>
#include "libi3.h"
#include "shmlog.h"
static i3_shmlog_header *header;
static char *logbuffer,
*walk;
+static int ipcfd = -1;
+
+static volatile bool interrupted = false;
+
+static void sighandler(int signal) {
+ interrupted = true;
+}
+
+static void disable_shmlog(void) {
+ const char *disablecmd = "debuglog off; shmlog off";
+ if (ipc_send_message(ipcfd, strlen(disablecmd),
+ I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)disablecmd) != 0)
+ err(EXIT_FAILURE, "IPC send");
+
+ /* Ensure the command was sent by waiting for the reply: */
+ uint32_t reply_length = 0;
+ uint8_t *reply = NULL;
+ if (ipc_recv_message(ipcfd, I3_IPC_REPLY_TYPE_COMMAND,
+ &reply_length, &reply) != 0) {
+ err(EXIT_FAILURE, "IPC recv");
+ }
+ free(reply);
+}
static int check_for_wrap(void) {
if (wrap_count == header->wrap_count)
walk += len;
}
+void errorlog(char *fmt, ...) {
+ va_list args;
+
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+}
+
int main(int argc, char *argv[]) {
int o, option_index = 0;
bool verbose = false;
exit(1);
}
if (root_atom_contents("I3_CONFIG_PATH", conn, screen) != NULL) {
- fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled.\n\n");
- if (!is_debug_build()) {
+ fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled. Enabling SHM log until cancelled\n\n");
+ ipcfd = ipc_connect(NULL);
+ const char *enablecmd = "debuglog on; shmlog 5242880";
+ if (ipc_send_message(ipcfd, strlen(enablecmd),
+ I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)enablecmd) != 0)
+ err(EXIT_FAILURE, "IPC send");
+ /* By the time we receive a reply, I3_SHMLOG_PATH is set: */
+ uint32_t reply_length = 0;
+ uint8_t *reply = NULL;
+ if (ipc_recv_message(ipcfd, I3_IPC_REPLY_TYPE_COMMAND,
+ &reply_length, &reply) != 0) {
+ err(EXIT_FAILURE, "IPC recv");
+ }
+ free(reply);
+
+ atexit(disable_shmlog);
+
+ /* Retry: */
+ shmname = root_atom_contents("I3_SHMLOG_PATH", NULL, 0);
+ if (shmname == NULL && !is_debug_build()) {
fprintf(stderr, "You seem to be using a release version of i3:\n %s\n\n", I3_VERSION);
fprintf(stderr, "Release versions do not use SHM logging by default,\ntherefore i3-dump-log does not work.\n\n");
fprintf(stderr, "Please follow this guide instead:\nhttps://i3wm.org/docs/debugging-release-version.html\n");
exit(1);
}
}
- errx(EXIT_FAILURE, "Cannot get I3_SHMLOG_PATH atom contents. Is i3 running on this display?");
+ if (shmname == NULL) {
+ errx(EXIT_FAILURE, "Cannot get I3_SHMLOG_PATH atom contents. Is i3 running on this display?");
+ }
}
if (*shmname == '\0')
print_till_end();
#if !defined(__OpenBSD__)
- if (follow) {
- /* Since pthread_cond_wait() expects a mutex, we need to provide one.
+ if (!follow) {
+ return 0;
+ }
+
+ /* Handle SIGINT gracefully to invoke atexit handlers, if any. */
+ struct sigaction action;
+ action.sa_handler = sighandler;
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+ sigaction(SIGINT, &action, NULL);
+
+ /* Since pthread_cond_wait() expects a mutex, we need to provide one.
* To not lock i3 (that’s bad, mhkay?) we just define one outside of
* the shared memory. */
- pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
- pthread_mutex_lock(&dummy_mutex);
- while (1) {
- pthread_cond_wait(&(header->condvar), &dummy_mutex);
- /* If this was not a spurious wakeup, print the new lines. */
- if (header->offset_next_write != offset_next_write) {
- offset_next_write = header->offset_next_write;
- print_till_end();
- }
+ pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
+ pthread_mutex_lock(&dummy_mutex);
+ while (!interrupted) {
+ pthread_cond_wait(&(header->condvar), &dummy_mutex);
+ /* If this was not a spurious wakeup, print the new lines. */
+ if (header->offset_next_write != offset_next_write) {
+ offset_next_write = header->offset_next_write;
+ print_till_end();
}
}
-#endif
+#endif
+ exit(0);
return 0;
}
#include <err.h>
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
-#define FREE(pointer) \
- do { \
- if (pointer != NULL) { \
- free(pointer); \
- pointer = NULL; \
- } \
+#define FREE(pointer) \
+ do { \
+ free(pointer); \
+ pointer = NULL; \
} while (0)
extern xcb_window_t root;
* the command will be sent to i3 */
static char *format;
-static char *socket_path;
static int sockfd;
static xcb_key_symbols_t *symbols;
static bool modeswitch_active = false;
int main(int argc, char *argv[]) {
format = sstrdup("%s");
- socket_path = getenv("I3SOCK");
+ char *socket_path = NULL;
char *pattern = sstrdup("pango:monospace 8");
int o, option_index = 0;
if (!conn || xcb_connection_has_error(conn))
die("Cannot open display\n");
- if (socket_path == NULL)
- socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);
-
- if (socket_path == NULL)
- socket_path = "/tmp/i3-ipc.sock";
-
sockfd = ipc_connect(socket_path);
root_screen = xcb_aux_get_screen(conn, screen);
#include <i3/ipc.h>
-static char *socket_path;
-
/*
* Having verboselog() and errorlog() is necessary when using libi3.
*
if (pledge("stdio rpath unix", NULL) == -1)
err(EXIT_FAILURE, "pledge");
#endif
- char *env_socket_path = getenv("I3SOCK");
- if (env_socket_path)
- socket_path = sstrdup(env_socket_path);
- else
- socket_path = NULL;
+ char *socket_path = NULL;
int o, option_index = 0;
uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND;
char *payload = NULL;
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
if (o == 's') {
- if (socket_path != NULL)
- free(socket_path);
+ free(socket_path);
socket_path = sstrdup(optarg);
} else if (o == 't') {
if (strcasecmp(optarg, "command") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION;
} else if (strcasecmp(optarg, "get_config") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
+ } else if (strcasecmp(optarg, "send_tick") == 0) {
+ message_type = I3_IPC_MESSAGE_TYPE_SEND_TICK;
} 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\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");
exit(EXIT_FAILURE);
}
} else if (o == 'q') {
}
}
- if (socket_path == NULL)
- socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
-
- /* Fall back to the default socket path */
- if (socket_path == NULL)
- socket_path = sstrdup("/tmp/i3-ipc.sock");
-
/* 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 */
if (!payload)
payload = sstrdup("");
- int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
- if (sockfd == -1)
- err(EXIT_FAILURE, "Could not create socket");
-
- struct sockaddr_un addr;
- memset(&addr, 0, sizeof(struct sockaddr_un));
- addr.sun_family = AF_LOCAL;
- strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
- if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
- err(EXIT_FAILURE, "Could not connect to i3 on socket \"%s\"", socket_path);
-
+ int sockfd = ipc_connect(socket_path);
if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1)
err(EXIT_FAILURE, "IPC: write()");
free(payload);
#include <err.h>
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
-#define FREE(pointer) \
- do { \
- if (pointer != NULL) { \
- free(pointer); \
- pointer = NULL; \
- } \
+#define FREE(pointer) \
+ do { \
+ free(pointer); \
+ pointer = NULL; \
} while (0)
#define xmacro(atom) xcb_atom_t A_##atom;
case XCB_CONFIGURE_NOTIFY: {
xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event;
- draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height);
+ if (configure_notify->width > 0 && configure_notify->height > 0) {
+ draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height);
+ }
break;
}
}
# mechanism to find the preferred editor
# Hopefully one of these is installed (no flamewars about preference please!):
-for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mcedit; do
+for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mcedit gvim; do
if command -v "$editor" > /dev/null 2>&1; then
exec "$editor" "$@"
fi
# 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; 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; do
if command -v "$terminal" > /dev/null 2>&1; then
exec "$terminal" "$@"
fi
* Generates a click event, if enabled.
*
*/
-void send_block_clicked(int button, const char *name, const char *instance, int x, int y);
+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);
typedef struct binding_t {
int input_code;
char *command;
+ bool release;
TAILQ_ENTRY(binding_t)
bindings;
*/
void init_outputs(void);
+/*
+ * free() all outputs data structures.
+ *
+ */
+void free_outputs(void);
+
/*
* Returns the output with the given name
*
#define STARTS_WITH(string, len, needle) (((len) >= strlen((needle))) && strncasecmp((string), (needle), strlen((needle))) == 0)
/* Securely free p */
-#define FREE(p) \
- do { \
- if (p != NULL) { \
- free(p); \
- p = NULL; \
- } \
+#define FREE(p) \
+ do { \
+ free(p); \
+ p = NULL; \
} while (0)
/* Securely free single-linked list */
ATOM_DO(_NET_SYSTEM_TRAY_COLORS)
ATOM_DO(_XEMBED_INFO)
ATOM_DO(_XEMBED)
+ATOM_DO(I3_SYNC)
#undef ATOM_DO
va_list args;
va_start(args, format);
if (vasprintf(&message, format, args) == -1) {
- return;
+ goto finish;
}
struct status_block *err_block = scalloc(1, sizeof(struct status_block));
TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks);
TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
+finish:
FREE(message);
va_end(args);
}
* Generates a click event, if enabled.
*
*/
-void send_block_clicked(int button, const char *name, const char *instance, int x, int y) {
+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) {
if (!child.click_events) {
return;
}
child_click_events_key("y");
yajl_gen_integer(gen, y);
+ child_click_events_key("relative_x");
+ yajl_gen_integer(gen, x_rel);
+
+ child_click_events_key("relative_y");
+ yajl_gen_integer(gen, y_rel);
+
+ child_click_events_key("width");
+ yajl_gen_integer(gen, width);
+
+ child_click_events_key("height");
+ yajl_gen_integer(gen, height);
+
yajl_gen_map_close(gen);
child_write_output();
}
if (!strcmp(cur_key, "mode")) {
DLOG("mode = %.*s, len = %d\n", len, val, len);
- config.hide_on_modifier = (len == 4 && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK
- : (len == 4 && !strncmp((const char *)val, "hide", strlen("hide")) ? M_HIDE
- : M_INVISIBLE));
+ config.hide_on_modifier = (len == strlen("dock") && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK
+ : (len == strlen("hide") && !strncmp((const char *)val, "hide", strlen("hide")) ? M_HIDE
+ : M_INVISIBLE));
return 1;
}
if (!strcmp(cur_key, "hidden_state")) {
DLOG("hidden_state = %.*s, len = %d\n", len, val, len);
- config.hidden_state = (len == 4 && !strncmp((const char *)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW);
+ config.hidden_state = (len == strlen("hide") && !strncmp((const char *)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW);
return 1;
}
if (!strcmp(cur_key, "modifier")) {
DLOG("modifier = %.*s\n", len, val);
- if (len == 4 && !strncmp((const char *)val, "none", strlen("none"))) {
+ if (len == strlen("none") && !strncmp((const char *)val, "none", strlen("none"))) {
config.modifier = XCB_NONE;
return 1;
}
- if (len == 5 && !strncmp((const char *)val, "shift", strlen("shift"))) {
+ if (len == strlen("shift") && !strncmp((const char *)val, "shift", strlen("shift"))) {
config.modifier = ShiftMask;
return 1;
}
- if (len == 4 && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
+ if (len == strlen("ctrl") && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
config.modifier = ControlMask;
return 1;
}
- if (len == 4 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
+ if (len == strlen("Mod") + 1 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
switch (val[3]) {
case '1':
config.modifier = Mod1Mask;
if (!strcmp(cur_key, "position")) {
DLOG("position = %.*s\n", len, val);
- config.position = (len == 3 && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT);
+ config.position = (len == strlen("top") && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT);
return 1;
}
if (!strcmp(cur_key, "font")) {
DLOG("font = %.*s\n", len, val);
+ FREE(config.fontname);
sasprintf(&config.fontname, "%.*s", len, val);
return 1;
}
*
*/
static int config_boolean_cb(void *params_, int val) {
+ if (parsing_bindings) {
+ if (strcmp(cur_key, "release") == 0) {
+ binding_t *binding = TAILQ_LAST(&(config.bindings), bindings_head);
+ if (binding == NULL) {
+ ELOG("There is no binding to put the current command onto. This is a bug in i3.\n");
+ return 0;
+ }
+
+ binding->release = val;
+ return 1;
+ }
+
+ ELOG("Unknown key \"%s\" while parsing bar bindings.\n", cur_key);
+ }
+
if (!strcmp(cur_key, "binding_mode_indicator")) {
DLOG("binding_mode_indicator = %d\n", val);
config.disable_binding_mode_indicator = !val;
*/
void got_output_reply(char *reply) {
DLOG("Clearing old output configuration...\n");
- i3_output *o_walk;
- SLIST_FOREACH(o_walk, outputs, slist) {
- destroy_window(o_walk);
- }
- FREE_SLIST(outputs, i3_output);
+ free_outputs();
DLOG("Parsing outputs JSON...\n");
parse_outputs_json(reply);
DLOG("Reconfiguring windows...\n");
reconfig_windows(false);
+ i3_output *o_walk;
SLIST_FOREACH(o_walk, outputs, slist) {
kick_tray_clients(o_walk);
}
/* update the configuration with the received settings */
DLOG("Received bar config update \"%s\"\n", event);
- char *old_command = sstrdup(config.command);
+ char *old_command = config.command ? sstrdup(config.command) : NULL;
bar_display_mode_t old_mode = config.hide_on_modifier;
parse_config_json(event);
if (old_mode != config.hide_on_modifier) {
init_colors(&(config.colors));
/* restart status command process */
- if (strcmp(old_command, config.command) != 0) {
+ if (old_command && strcmp(old_command, config.command) != 0) {
kill_child();
start_child(config.command);
}
clean_xcb();
ev_default_destroy();
- free_workspaces();
-
return 0;
}
return 1;
}
+static void clear_output(i3_output *output) {
+ FREE(output->name);
+ FREE(output->workspaces);
+ FREE(output->trayclients);
+}
+
/*
* We hit the end of a map (rect or a new output)
*
if (!handle_output) {
DLOG("Ignoring output \"%s\", not configured to handle it.\n",
params->outputs_walk->name);
- FREE(params->outputs_walk->name);
- FREE(params->outputs_walk->workspaces);
- FREE(params->outputs_walk->trayclients);
+ clear_output(params->outputs_walk);
FREE(params->outputs_walk);
FREE(params->cur_key);
return 1;
target->primary = params->outputs_walk->primary;
target->ws = params->outputs_walk->ws;
target->rect = params->outputs_walk->rect;
+
+ clear_output(params->outputs_walk);
+ FREE(params->outputs_walk);
}
return 1;
}
*/
void parse_outputs_json(char *json) {
struct outputs_json_params params;
-
params.outputs_walk = NULL;
params.cur_key = NULL;
params.json = json;
yajl_free(handle);
}
+/*
+ * free() all outputs data structures.
+ *
+ */
+void free_outputs(void) {
+ free_workspaces();
+
+ i3_output *outputs_walk;
+ if (outputs == NULL) {
+ return;
+ }
+ SLIST_FOREACH(outputs_walk, outputs, slist) {
+ destroy_window(outputs_walk);
+ if (outputs_walk->trayclients != NULL && !TAILQ_EMPTY(outputs_walk->trayclients)) {
+ FREE_TAILQ(outputs_walk->trayclients, trayclient);
+ }
+ clear_output(outputs_walk);
+ }
+ FREE_SLIST(outputs, i3_output);
+}
+
/*
* Returns the output with the given name
*
/* Event watchers, to interact with the user */
ev_prepare *xcb_prep;
-ev_check *xcb_chk;
ev_io *xcb_io;
ev_io *xkb_io;
xcb_flush(xcb_connection);
}
+static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_release) {
+ binding_t *binding;
+ TAILQ_FOREACH(binding, &(config.bindings), bindings) {
+ if ((binding->input_code != input_code) || (binding->release != event_is_release))
+ continue;
+
+ i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
+ return true;
+ }
+ return false;
+}
+
/*
* Handle a button press event (i.e. a mouse click on one of our bars).
* We determine, whether the click occurred on a workspace button or if the scroll-
return;
}
- int32_t x = event->event_x >= 0 ? event->event_x : 0;
-
DLOG("Got button %d\n", event->detail);
+ /* During button release events, only check for custom commands. */
+ const bool event_is_release = (event->response_type & ~0x80) == XCB_BUTTON_RELEASE;
+ if (event_is_release) {
+ execute_custom_command(event->detail, event_is_release);
+ return;
+ }
+
+ int32_t x = event->event_x >= 0 ? event->event_x : 0;
int workspace_width = 0;
i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
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) {
- send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y);
+ 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);
return;
}
/* If a custom command was specified for this mouse button, it overrides
* the default behavior. */
- binding_t *binding;
- TAILQ_FOREACH(binding, &(config.bindings), bindings) {
- if (binding->input_code != event->detail)
- continue;
-
- i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
+ if (execute_custom_command(event->detail, event_is_release)) {
return;
}
*
*/
static void handle_client_message(xcb_client_message_event_t *event) {
- if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
- event->format == 32) {
+ 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);
+ } else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
+ event->format == 32) {
DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
/* event->data.data32[0] is the timestamp */
uint32_t op = event->data.data32[1];
}
/*
- * This function is called immediately before the main loop locks. We flush xcb
- * then (and only then)
+ * This function is called immediately before the main loop locks. We check for
+ * events from X11, handle them, then flush our outgoing queue.
*
*/
void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
- xcb_flush(xcb_connection);
-}
-
-/*
- * This function is called immediately after the main loop locks, so when one
- * of the watchers registered an event.
- * We check whether an X-Event arrived and handle it.
- *
- */
-void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
xcb_generic_event_t *event;
if (xcb_connection_has_error(xcb_connection)) {
}
break;
+ case XCB_BUTTON_RELEASE:
case XCB_BUTTON_PRESS:
/* Button press events are mouse buttons clicked on one of our bars */
handle_button((xcb_button_press_event_t *)event);
}
free(event);
}
+
+ xcb_flush(xcb_connection);
}
/*
/* The various watchers to communicate with xcb */
xcb_io = smalloc(sizeof(ev_io));
xcb_prep = smalloc(sizeof(ev_prepare));
- xcb_chk = smalloc(sizeof(ev_check));
ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
ev_prepare_init(xcb_prep, &xcb_prep_cb);
- ev_check_init(xcb_chk, &xcb_chk_cb);
-
- /* Within an event loop iteration, run the xcb_chk watcher last: other
- * watchers might call xcb_flush(), which, unexpectedly, can also read
- * events into the queue (see _xcb_conn_wait). Hence, we need to drain xcb’s
- * queue last, otherwise we risk dead-locking. */
- ev_set_priority(xcb_chk, EV_MINPRI);
ev_io_start(main_loop, xcb_io);
ev_prepare_start(main_loop, xcb_prep);
- ev_check_start(main_loop, xcb_chk);
/* Now we get the atoms and save them in a nice data structure */
get_atoms();
*
*/
void clean_xcb(void) {
- i3_output *o_walk;
- free_workspaces();
- SLIST_FOREACH(o_walk, outputs, slist) {
- destroy_window(o_walk);
- FREE(o_walk->trayclients);
- FREE(o_walk->workspaces);
- FREE(o_walk->name);
- }
- FREE_SLIST(outputs, i3_output);
- FREE(outputs);
+ free_outputs();
free_font();
xcb_aux_sync(xcb_connection);
xcb_disconnect(xcb_connection);
- ev_check_stop(main_loop, xcb_chk);
ev_prepare_stop(main_loop, xcb_prep);
ev_io_stop(main_loop, xcb_io);
- FREE(xcb_chk);
FREE(xcb_prep);
FREE(xcb_io);
}
* */
values[3] = XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
- XCB_EVENT_MASK_BUTTON_PRESS;
+ XCB_EVENT_MASK_BUTTON_PRESS |
+ XCB_EVENT_MASK_BUTTON_RELEASE;
if (config.hide_on_modifier == M_DOCK) {
/* If the bar is normally visible, catch visibility change events to suspend
* the status process when the bar is obscured by full-screened windows. */
#include <errno.h>
#include <err.h>
#include <stdint.h>
+#include <inttypes.h>
#include <math.h>
#include <limits.h>
void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth);
/**
- * Implementation of 'resize set <px> [px] <px> [px]'.
+ * Implementation of 'resize set <width> [px | ppt] <height> [px | ppt]'.
*
*/
-void cmd_resize_set(I3_CMD, long cwidth, long cheight);
+void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height);
/**
* Implementation of 'resize grow|shrink <direction> [<px> px] [or <ppt> ppt]'.
*/
void con_focus(Con *con);
+/**
+ * Sets input focus to the given container and raises it to the top.
+ *
+ */
+void con_activate(Con *con);
+
/**
* Closes the given container.
*
*/
Con *con_for_window(Con *con, i3Window *window, Match **store_match);
+/**
+ * Iterate over the container's focus stack and return an array with the
+ * containers inside it, ordered from higher focus order to lowest.
+ *
+ */
+Con **get_focus_order(Con *con);
+
+/**
+ * Clear the container's focus stack and re-add it using the provided container
+ * array. The function doesn't check if the provided array contains the same
+ * containers with the previous focus stack but will not add floating containers
+ * in the new focus stack if container is not a workspace.
+ *
+ */
+void set_focus_order(Con *con, Con **focus_order);
+
/**
* Returns the number of children of this container.
*
* visible workspace on the given output.
*
*/
-void con_move_to_output(Con *con, Output *output);
+void con_move_to_output(Con *con, Output *output, bool fix_coordinates);
+
+/**
+ * Moves the given container to the currently focused container on the
+ * visible workspace on the output specified by the given name.
+ * The current output for the container is used to resolve relative names
+ * such as left, right, up, down.
+ *
+ */
+bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates);
/**
* Moves the given container to the given mark.
CFGFUN(workspace_back_and_forth, const char *value);
CFGFUN(focus_follows_mouse, const char *value);
CFGFUN(mouse_warping, const char *value);
+CFGFUN(focus_wrapping, const char *value);
CFGFUN(force_focus_wrapping, const char *value);
CFGFUN(force_xinerama, const char *value);
CFGFUN(disable_randr15, const char *value);
CFGFUN(focus_on_window_activation, const char *mode);
CFGFUN(show_marks, const char *value);
CFGFUN(hide_edge_borders, const char *borders);
-CFGFUN(assign, const char *workspace);
+CFGFUN(assign_output, const char *output);
+CFGFUN(assign, const char *workspace, bool is_number);
CFGFUN(no_focus);
CFGFUN(ipc_socket, const char *path);
CFGFUN(restart_state, const char *path);
CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border);
CFGFUN(color_single, const char *colorclass, const char *color);
CFGFUN(floating_modifier, const char *modifiers);
-CFGFUN(new_window, const char *windowtype, const char *border, const long width);
+CFGFUN(default_border, const char *windowtype, const char *border, const long width);
CFGFUN(workspace, const char *workspace, const char *output);
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command);
CFGFUN(bar_modifier, const char *modifier);
CFGFUN(bar_wheel_up_cmd, const char *command);
CFGFUN(bar_wheel_down_cmd, const char *command);
-CFGFUN(bar_bindsym, const char *button, const char *command);
+CFGFUN(bar_bindsym, const char *button, const char *release, const char *command);
CFGFUN(bar_position, const char *position);
CFGFUN(bar_i3bar_command, const char *i3bar_command);
CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);
* comes with i3. Thus, you can turn it off entirely. */
bool disable_workspace_bar;
- /** Think of the following layout: Horizontal workspace with a tabbed
- * con on the left of the screen and a terminal on the right of the
- * screen. You are in the second container in the tabbed container and
- * focus to the right. By default, i3 will set focus to the terminal on
- * the right. If you are in the first container in the tabbed container
- * however, focusing to the left will wrap. This option forces i3 to
- * always wrap, which will result in you having to use "focus parent"
- * more often. */
- bool force_focus_wrapping;
+ /** When focus wrapping is enabled (the default), attempting to
+ * move focus past the edge of the screen (in other words, in a
+ * direction in which there are no more containers to focus) will
+ * cause the focus to wrap to the opposite edge of the current
+ * container. When it is disabled, nothing happens; the current
+ * focus is preserved.
+ *
+ * Additionally, focus wrapping may be forced. Think of the
+ * following layout: Horizontal workspace with a tabbed con on the
+ * left of the screen and a terminal on the right of the
+ * screen. You are in the second container in the tabbed container
+ * and focus to the right. By default, i3 will set focus to the
+ * terminal on the right. If you are in the first container in the
+ * tabbed container however, focusing to the left will
+ * wrap. Setting focus_wrapping to FOCUS_WRAPPING_FORCE forces i3
+ * to always wrap, which will result in you having to use "focus
+ * parent" more often. */
+ focus_wrapping_t focus_wrapping;
/** By default, use the RandR API for multi-monitor setups.
* Unfortunately, the nVidia binary graphics driver doesn't support
/** The command which is to be executed for this button. */
char *command;
+ /** If true, the command will be executed after the button is released. */
+ bool release;
+
TAILQ_ENTRY(Barbinding)
bindings;
};
POINTER_WARPING_NONE = 1
} warping_t;
+/**
+ * Focus wrapping modes.
+ */
+typedef enum {
+ FOCUS_WRAPPING_OFF = 0,
+ FOCUS_WRAPPING_ON = 1,
+ FOCUS_WRAPPING_FORCE = 2
+} focus_wrapping_t;
+
/**
* Stores a rectangle, for example the size of a window, the child window etc.
* It needs to be packed so that the compiler will not add any padding bytes.
A_ANY = 0,
A_COMMAND = (1 << 0),
A_TO_WORKSPACE = (1 << 1),
- A_NO_FOCUS = (1 << 2)
+ A_NO_FOCUS = (1 << 2),
+ A_TO_WORKSPACE_NUMBER = (1 << 3),
+ A_TO_OUTPUT = (1 << 4)
} type;
/** the criteria to check if a window matches */
union {
char *command;
char *workspace;
+ char *output;
} dest;
TAILQ_ENTRY(Assignment)
extern xcb_window_t root;
extern struct ev_loop *main_loop;
extern bool only_check_config;
+extern bool force_xinerama;
/** Request the raw last loaded i3 config. */
#define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9
+/** Send a tick event to all subscribers. */
+#define I3_IPC_MESSAGE_TYPE_SEND_TICK 10
+
/*
* Messages from i3 to clients
*
#define I3_IPC_REPLY_TYPE_VERSION 7
#define I3_IPC_REPLY_TYPE_BINDING_MODES 8
#define I3_IPC_REPLY_TYPE_CONFIG 9
+#define I3_IPC_REPLY_TYPE_TICK 10
/*
* Events from i3 to clients. Events have the first bit set high.
/** The shutdown event will be triggered when the ipc shuts down */
#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6)
+
+/** The tick event will be sent upon a tick IPC message */
+#define I3_IPC_EVENT_TICK (I3_IPC_EVENT_MASK | 7)
int num_events;
char **events;
+ /* For clients which subscribe to the tick event: whether the first tick
+ * event has been sent by i3. */
+ bool first_tick_sent;
+
TAILQ_ENTRY(ipc_client)
clients;
} ipc_client;
#include <config.h>
-bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction);
+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);
break; \
}
-#define FREE(pointer) \
- do { \
- if (pointer != NULL) { \
- free(pointer); \
- pointer = NULL; \
- } \
+#define FREE(pointer) \
+ do { \
+ free(pointer); \
+ pointer = NULL; \
} while (0)
#define CALL(obj, member, ...) obj->member(obj, ##__VA_ARGS__)
+#define SWAP(first, second, type) \
+ do { \
+ type tmp_SWAP = first; \
+ first = second; \
+ second = tmp_SWAP; \
+ } while (0)
+
int min(int a, int b);
int max(int a, int b);
bool rect_contains(Rect rect, uint32_t x, uint32_t y);
}
char *endptr;
- dpi = strtol(resource, &endptr, 10);
- if (dpi == LONG_MAX || dpi == LONG_MIN || dpi < 0 || *endptr != '\0' || endptr == resource) {
+ double in_dpi = strtod(resource, &endptr);
+ if (in_dpi == HUGE_VAL || dpi < 0 || *endptr != '\0' || endptr == resource) {
ELOG("Xft.dpi = %s is an invalid number and couldn't be parsed.\n", resource);
dpi = 0;
goto init_dpi_end;
}
+ dpi = (long)round(in_dpi);
DLOG("Found Xft.dpi = %ld.\n", dpi);
*
*/
int ipc_connect(const char *socket_path) {
+ char *path = NULL;
+ if (socket_path != NULL) {
+ path = sstrdup(socket_path);
+ }
+
+ if (path == NULL) {
+ if ((path = getenv("I3SOCK")) != NULL) {
+ path = sstrdup(path);
+ }
+ }
+
+ if (path == NULL) {
+ path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
+ }
+
+ if (path == NULL) {
+ path = sstrdup("/tmp/i3-ipc.sock");
+ }
+
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd == -1)
err(EXIT_FAILURE, "Could not create socket");
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
- strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
+ strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
- err(EXIT_FAILURE, "Could not connect to i3");
-
+ err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path);
+ free(path);
return sockfd;
}
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
+#include <inttypes.h>
#include <i3/ipc.h>
if (n == -1)
return -1;
if (n == 0) {
- return -2;
+ if (read_bytes == 0) {
+ return -2;
+ } else {
+ ELOG("IPC: unexpected EOF while reading header, got %" PRIu32 " bytes, want %" PRIu32 " bytes\n",
+ read_bytes, to_read);
+ return -3;
+ }
}
read_bytes += n;
}
if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
- ELOG("IPC: invalid magic in reply\n");
+ ELOG("IPC: invalid magic in header, got \"%.*s\", want \"%s\"\n",
+ (int)strlen(I3_IPC_MAGIC), walk, I3_IPC_MAGIC);
return -3;
}
*reply = smalloc(*reply_length);
read_bytes = 0;
- int n;
while (read_bytes < *reply_length) {
- if ((n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes)) == -1) {
+ const int n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes);
+ if (n == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
return -1;
}
+ if (n == 0) {
+ ELOG("IPC: unexpected EOF while reading payload, got %" PRIu32 " bytes, want %" PRIu32 " bytes\n",
+ read_bytes, *reply_length);
+ return -3;
+ }
read_bytes += n;
}
* jed
* gedit
* mcedit
+* gvim
Please don’t complain about the order: If the user has any preference, they will
have $VISUAL or $EDITOR set.
* tilix
* terminix
* konsole
+* kitty
+* guake
+* tilda
Please don’t complain about the order: If the user has any preference, they will
have $TERMINAL set or modified their i3 configuration file.
-> RESIZE_WIDTH
state RESIZE_WIDTH:
- 'px'
+ mode_width = 'px', 'ppt'
->
height = number
-> RESIZE_HEIGHT
state RESIZE_HEIGHT:
- 'px', end
- -> call cmd_resize_set(&width, &height)
+ mode_height = 'px', 'ppt'
+ ->
+ end
+ -> call cmd_resize_set(&width, $mode_width, &height, $mode_height)
# rename workspace <name> to <name>
# rename workspace to <name>
'floating_modifier' -> FLOATING_MODIFIER
'default_orientation' -> DEFAULT_ORIENTATION
'workspace_layout' -> WORKSPACE_LAYOUT
- windowtype = 'new_window', 'new_float' -> NEW_WINDOW
+ windowtype = 'default_border', 'new_window', 'default_floating_border', 'new_float'
+ -> DEFAULT_BORDER
'hide_edge_borders' -> HIDE_EDGE_BORDERS
'for_window' -> FOR_WINDOW
'assign' -> ASSIGN
'no_focus' -> NO_FOCUS
'focus_follows_mouse' -> FOCUS_FOLLOWS_MOUSE
'mouse_warping' -> MOUSE_WARPING
+ 'focus_wrapping' -> FOCUS_WRAPPING
'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING
'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA
'disable_randr15', 'disable-randr15' -> DISABLE_RANDR15
layout = 'default', 'stacking', 'stacked', 'tabbed'
-> call cfg_workspace_layout($layout)
-# new_window <normal|1pixel|none>
-# new_float <normal|1pixel|none>
-state NEW_WINDOW:
+# <default_border|new_window> <normal|1pixel|none>
+# <default_floating_border|new_float> <normal|1pixel|none>
+state DEFAULT_BORDER:
border = 'normal', 'pixel'
- -> NEW_WINDOW_PIXELS
+ -> DEFAULT_BORDER_PIXELS
border = '1pixel', 'none'
- -> call cfg_new_window($windowtype, $border, -1)
+ -> call cfg_default_border($windowtype, $border, -1)
-state NEW_WINDOW_PIXELS:
+state DEFAULT_BORDER_PIXELS:
end
- -> call cfg_new_window($windowtype, $border, 2)
+ -> call cfg_default_border($windowtype, $border, 2)
width = number
- -> NEW_WINDOW_PIXELS_PX
+ -> DEFAULT_BORDER_PIXELS_PX
-state NEW_WINDOW_PIXELS_PX:
+state DEFAULT_BORDER_PIXELS_PX:
'px'
->
end
- -> call cfg_new_window($windowtype, $border, &width)
+ -> call cfg_default_border($windowtype, $border, &width)
# hide_edge_borders <none|vertical|horizontal|both|smart>
# also hide_edge_borders <bool> for compatibility
command = string
-> call cfg_for_window($command)
-# assign <criteria> [→] workspace
+# assign <criteria> [→] [workspace | output] <name>
state ASSIGN:
'['
-> call cfg_criteria_init(ASSIGN_WORKSPACE); CRITERIA
state ASSIGN_WORKSPACE:
'→'
->
+ 'output'
+ -> ASSIGN_OUTPUT
'workspace'
->
+ 'number'
+ -> ASSIGN_WORKSPACE_NUMBER
workspace = string
- -> call cfg_assign($workspace)
+ -> call cfg_assign($workspace, 0)
+
+state ASSIGN_OUTPUT:
+ output = string
+ -> call cfg_assign_output($output)
+
+state ASSIGN_WORKSPACE_NUMBER:
+ number = string
+ -> call cfg_assign($number, 1)
# no_focus <criteria>
state NO_FOCUS:
value = 'none', 'output'
-> call cfg_mouse_warping($value)
+# focus_wrapping
+state FOCUS_WRAPPING:
+ value = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive', 'force'
+ -> call cfg_focus_wrapping($value)
+
# force_focus_wrapping
state FORCE_FOCUS_WRAPPING:
value = word
-> call cfg_bar_wheel_down_cmd($command); BAR
state BAR_BINDSYM:
+ release = '--release'
+ ->
button = word
-> BAR_BINDSYM_COMMAND
state BAR_BINDSYM_COMMAND:
+ release = '--release'
+ ->
command = string
- -> call cfg_bar_bindsym($button, $command); BAR
+ -> call cfg_bar_bindsym($button, $release, $command); BAR
state BAR_POSITION:
position = 'top', 'bottom'
#!/bin/zsh
# This script is used to prepare a new release of i3.
-export RELEASE_VERSION="4.13"
-export PREVIOUS_VERSION="4.12"
-export RELEASE_BRANCH="next"
+export RELEASE_VERSION="4.14.1"
+export PREVIOUS_VERSION="4.14"
+export RELEASE_BRANCH="master"
if [ ! -e "../i3.github.io" ]
then
echo " cd ${TMPDIR}"
echo " sendmail -t < email.txt"
echo ""
-echo "Update milestones on GitHub:"
-echo " Set due date of ${RELEASE_VERSION} to $(date +'%Y-$m-%d') and close the milestone"
-echo " Create milestone for the next version with unset due date"
+echo "Update milestones on GitHub (only for new major versions):"
+echo " Set due date of ${RELEASE_VERSION} to $(date +'%Y-%m-%d') and close the milestone"
+echo " Create milestone for the next major version with unset due date"
echo ""
echo "Announce on:"
echo " twitter"
int remaining = xcb_get_property_value_length(prop_reply);
for (int i = 0; i < 5 && remaining > 0; i++) {
const int len = strnlen(walk, remaining);
- remaining -= len;
switch (i) {
case 0:
sasprintf((char **)&(xkb_names->rules), "%.*s", len, walk);
}
DLOG("component %d of _XKB_RULES_NAMES is \"%.*s\"\n", i, len, walk);
walk += (len + 1);
+ remaining -= (len + 1);
}
free(atom_reply);
break;
}
- bool res = resize_find_tiling_participants(&first, &second, search_direction);
+ bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
if (!res) {
LOG("No second container in this direction found.\n");
return false;
* The splitv container will be focused. */
Con *focused = con->parent;
focused = TAILQ_FIRST(&(focused->focus_head));
- con_focus(focused);
+ con_activate(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);
}
/* 2: focus this con. */
- con_focus(con);
+ con_activate(con);
/* 3: For floating containers, we also want to raise them on click.
* We will skip handling events on floating cons in fullscreen mode */
} \
} while (0)
-/*
- * Returns true if a is definitely greater than b (using the given epsilon)
- *
- */
-static bool definitelyGreaterThan(float a, float b, float epsilon) {
- return (a - b) > ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
-}
-
/*
* Checks whether we switched to a new workspace and returns false in that case,
* signaling that further workspace switching should be done by the calling function
match_parse_property(current_match, ctype, cvalue);
}
+static void move_matches_to_workspace(Con *ws) {
+ owindow *current;
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ DLOG("matching: %p / %s\n", current->con, current->con->name);
+ con_move_to_workspace(current->con, ws, true, false, false);
+ }
+}
+
/*
* 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) {
- owindow *current;
-
DLOG("which=%s\n", which);
/* We have nothing to move:
return;
}
- TAILQ_FOREACH(current, &owindows, owindows) {
- DLOG("matching: %p / %s\n", current->con, current->con->name);
- con_move_to_workspace(current->con, ws, true, false, false);
- }
+ move_matches_to_workspace(ws);
cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply
*
*/
void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
- owindow *current;
- Con *ws;
-
- ws = workspace_back_and_forth_get();
-
+ Con *ws = workspace_back_and_forth_get();
if (ws == NULL) {
yerror("No workspace was previously active.");
return;
HANDLE_EMPTY_MATCH;
- TAILQ_FOREACH(current, &owindows, owindows) {
- DLOG("matching: %p / %s\n", current->con, current->con->name);
- con_move_to_workspace(current->con, ws, true, false, false);
- }
+ move_matches_to_workspace(ws);
cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply
}
const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
- owindow *current;
/* We have nothing to move:
* when criteria was specified but didn't match any window or
HANDLE_EMPTY_MATCH;
- TAILQ_FOREACH(current, &owindows, owindows) {
- DLOG("matching: %p / %s\n", current->con, current->con->name);
- con_move_to_workspace(current->con, ws, true, false, false);
- }
+ move_matches_to_workspace(ws);
cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply
*/
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);
- owindow *current;
/* We have nothing to move:
* when criteria was specified but didn't match any window or
LOG("should move window to workspace %s\n", which);
/* get the workspace */
- Con *output, *workspace = NULL;
+ Con *output, *ws = NULL;
long parsed_num = ws_name_to_number(which);
}
TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
- GREP_FIRST(workspace, output_get_content(output),
+ GREP_FIRST(ws, output_get_content(output),
child->num == parsed_num);
- if (!workspace) {
- workspace = workspace_get(which, NULL);
+ if (!ws) {
+ ws = workspace_get(which, NULL);
}
if (!no_auto_back_and_forth)
- workspace = maybe_auto_back_and_forth_workspace(workspace);
+ ws = maybe_auto_back_and_forth_workspace(ws);
HANDLE_EMPTY_MATCH;
- TAILQ_FOREACH(current, &owindows, owindows) {
- DLOG("matching: %p / %s\n", current->con, current->con->name);
- con_move_to_workspace(current->con, workspace, true, false, false);
- }
+ move_matches_to_workspace(ws);
cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply
else
search_direction = D_DOWN;
- bool res = resize_find_tiling_participants(&first, &second, search_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);
LOG("default percentage = %f\n", percentage);
/* resize */
- LOG("second->percent = %f\n", second->percent);
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)
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 and greater than
- * 0.05 to have a reasonable minimum size. */
- if (definitelyGreaterThan(new_first_percent, 0.05, DBL_EPSILON) &&
- definitelyGreaterThan(new_second_percent, 0.05, DBL_EPSILON)) {
- first->percent += ((double)ppt / 100.0);
- second->percent -= ((double)ppt / 100.0);
+ /* 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 {
static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
LOG("width/height resize\n");
- /* get the appropriate current container (skip stacked/tabbed cons) */
- while (current->parent->layout == L_STACKED ||
- current->parent->layout == L_TABBED)
- current = current->parent;
-
- /* Then further go up until we find one with the matching orientation. */
- orientation_t search_orientation =
- (strcmp(direction, "width") == 0 ? HORIZ : VERT);
- while (current->type != CT_WORKSPACE &&
- current->type != CT_FLOATING_CON &&
- (con_orientation(current->parent) != search_orientation || con_num_children(current->parent) == 1))
- current = current->parent;
+ /* get the appropriate current container (skip stacked/tabbed cons) */
+ Con *dummy = NULL;
+ 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);
+ return false;
+ }
/* get the default percentage */
int children = con_num_children(current->parent);
double percentage = 1.0 / children;
LOG("default percentage = %f\n", percentage);
- orientation_t orientation = con_orientation(current->parent);
-
- if ((orientation == HORIZ &&
- strcmp(direction, "height") == 0) ||
- (orientation == VERT &&
- strcmp(direction, "width") == 0)) {
- LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
- (orientation == HORIZ ? "horizontal" : "vertical"));
- ysuccess(false);
- return false;
- }
-
- if (children == 1) {
- LOG("This is the only container, cannot resize.\n");
- ysuccess(false);
- return false;
- }
-
/* Ensure all the other children have a percentage set. */
Con *child;
TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
double subtract_percent = ((double)ppt / 100.0) / (children - 1);
LOG("new_current_percent = %f\n", new_current_percent);
LOG("subtract_percent = %f\n", subtract_percent);
- /* Ensure that the new percentages are positive and greater than
- * 0.05 to have a reasonable minimum size. */
+ /* Ensure that the new percentages are positive. */
TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
if (child == current)
continue;
- if (!definitelyGreaterThan(child->percent - subtract_percent, 0.05, DBL_EPSILON)) {
+ 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 (!definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON)) {
+ if (new_current_percent <= 0.0) {
LOG("Not resizing, already at minimum size\n");
ysuccess(false);
return false;
}
- current->percent += ((double)ppt / 100.0);
+ current->percent = new_current_percent;
LOG("current->percent after = %f\n", current->percent);
TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
}
/*
- * Implementation of 'resize set <px> [px] <px> [px]'.
+ * Implementation of 'resize set <width> [px | ppt] <height> [px | ppt]'.
*
*/
-void cmd_resize_set(I3_CMD, long cwidth, long cheight) {
- DLOG("resizing to %ldx%ld px\n", cwidth, cheight);
- if (cwidth <= 0 || cheight <= 0) {
- ELOG("Resize failed: dimensions cannot be negative (was %ldx%ld)\n", cwidth, cheight);
+void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height) {
+ DLOG("resizing to %ld %s x %ld %s\n", cwidth, mode_width, cheight, mode_height);
+ if (cwidth < 0 || cheight < 0) {
+ ELOG("Resize failed: dimensions cannot be negative (was %ld %s x %ld %s)\n", cwidth, mode_width, cheight, mode_height);
return;
}
HANDLE_EMPTY_MATCH;
owindow *current;
+ bool success = true;
TAILQ_FOREACH(current, &owindows, owindows) {
Con *floating_con;
if ((floating_con = con_inside_floating(current->con))) {
+ Con *output = con_get_output(floating_con);
+ if (cwidth == 0) {
+ cwidth = output->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;
+ } else if (mode_height && strcmp(mode_height, "ppt") == 0) {
+ cheight = output->rect.height * ((double)cheight / 100.0);
+ }
floating_resize(floating_con, cwidth, cheight);
} else {
- ELOG("Resize failed: %p not a floating container\n", current->con);
+ if (current->con->window && current->con->window->dock) {
+ DLOG("This is a dock window. Not resizing (con = %p)\n)", current->con);
+ 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 (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;
+ }
+ }
}
}
cmd_output->needs_tree_render = true;
- // XXX: default reply for now, make this a better reply
- ysuccess(true);
+ ysuccess(success);
}
/*
LOG("-------------------------------------------------\n");
LOG(" NOP: %s\n", comment);
LOG("-------------------------------------------------\n");
+ ysuccess(true);
}
/*
TAILQ_FOREACH(current, &owindows, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name);
- Output *current_output = get_output_for_con(current->con);
- assert(current_output != NULL);
-
- Output *output = get_output_from_string(current_output, name);
- if (output == NULL) {
- ELOG("Could not find output \"%s\", skipping.\n", name);
- had_error = true;
- continue;
- }
-
- Con *ws = NULL;
- GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
- if (ws == NULL) {
- ELOG("Could not find a visible workspace on output %p.\n", output);
- had_error = true;
- continue;
- }
-
- con_move_to_workspace(current->con, ws, true, false, false);
+ had_error |= !con_move_to_output_name(current->con, name, true);
}
cmd_output->needs_tree_render = true;
owindow *current;
TAILQ_FOREACH(current, &owindows, owindows) {
Con *ws = con_get_workspace(current->con);
+ if (con_is_internal(ws)) {
+ continue;
+ }
+
bool success = workspace_move_to_output(ws, name);
if (!success) {
ELOG("Failed to move workspace to output.\n");
ysuccess(true);
}
+/*
+ * Focus a container and disable any other fullscreen container not permitting the focus.
+ *
+ */
+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);
+ if (fullscreen_on_ws && fullscreen_on_ws != con && !con_has_parent(con, fullscreen_on_ws)) {
+ con_disable_fullscreen(fullscreen_on_ws);
+ }
+ con_activate(con);
+}
+
/*
* Implementation of 'focus tiling|floating|mode_toggle'.
*
void cmd_focus_window_mode(I3_CMD, const char *window_mode) {
DLOG("window_mode = %s\n", window_mode);
+ bool to_floating = false;
+ if (strcmp(window_mode, "mode_toggle") == 0) {
+ to_floating = !con_inside_floating(focused);
+ } else if (strcmp(window_mode, "floating") == 0) {
+ to_floating = true;
+ } else if (strcmp(window_mode, "tiling") == 0) {
+ to_floating = false;
+ }
+
Con *ws = con_get_workspace(focused);
- if (ws != NULL) {
- if (strcmp(window_mode, "mode_toggle") == 0) {
- if (con_inside_floating(focused))
- window_mode = "tiling";
- else
- window_mode = "floating";
- }
- Con *current;
- TAILQ_FOREACH(current, &(ws->focus_head), focused) {
- if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) ||
- (strcmp(window_mode, "tiling") == 0 && current->type == CT_FLOATING_CON))
- continue;
+ Con *current;
+ bool success = false;
+ TAILQ_FOREACH(current, &(ws->focus_head), focused) {
+ if ((to_floating && current->type != CT_FLOATING_CON) ||
+ (!to_floating && current->type == CT_FLOATING_CON))
+ continue;
- con_focus(con_descend_focused(current));
- break;
- }
+ cmd_focus_force_focus(con_descend_focused(current));
+ success = true;
+ break;
}
- cmd_output->needs_tree_render = true;
- // XXX: default reply for now, make this a better reply
- ysuccess(true);
+ if (success) {
+ cmd_output->needs_tree_render = true;
+ ysuccess(true);
+ } else {
+ yerror("Failed to find a %s container in workspace.", to_floating ? "floating" : "tiling");
+ }
}
/*
if (!ws)
continue;
- /* Check the fullscreen focus constraints. */
- if (!con_fullscreen_permits_focusing(current->con)) {
- LOG("Cannot change focus while in fullscreen mode (fullscreen rules).\n");
- ysuccess(false);
- return;
- }
-
/* In case this is a scratchpad window, call scratchpad_show(). */
if (ws == __i3_scratch) {
scratchpad_show(current->con);
* So we focus 'current' to make it the currently focused window of
* the target workspace, then revert focus. */
Con *currently_focused = focused;
- con_focus(current->con);
- con_focus(currently_focused);
+ cmd_focus_force_focus(current->con);
+ con_activate(currently_focused);
/* Now switch to the workspace, then focus */
workspace_show(ws);
LOG("focusing %p / %s\n", current->con, current->con->name);
- con_focus(current->con);
+ con_activate(current->con);
count++;
}
/* the move command should not disturb focus */
if (focused != initially_focused)
- con_focus(initially_focused);
+ con_activate(initially_focused);
// XXX: default reply for now, make this a better reply
ysuccess(true);
LOG("opening new container\n");
Con *con = tree_open_con(NULL, NULL);
con->layout = L_SPLITH;
- con_focus(con);
+ con_activate(con);
y(map_open);
ystr("success");
}
/* Restore the previous focus since con_attach messes with the focus. */
- con_focus(previously_focused);
+ con_activate(previously_focused);
cmd_output->needs_tree_render = true;
ysuccess(true);
}
}
+/*
+ * Raise container to the top if it is floating or inside some floating
+ * container.
+ *
+ */
+static void con_raise(Con *con) {
+ Con *floating = con_inside_floating(con);
+ if (floating) {
+ floating_raise_con(floating);
+ }
+}
+
+/*
+ * Sets input focus to the given container and raises it to the top.
+ *
+ */
+void con_activate(Con *con) {
+ con_focus(con);
+ con_raise(con);
+}
+
/*
* Closes the given container.
*
return NULL;
}
+static int num_focus_heads(Con *con) {
+ int focus_heads = 0;
+
+ Con *current;
+ TAILQ_FOREACH(current, &(con->focus_head), focused) {
+ focus_heads++;
+ }
+
+ return focus_heads;
+}
+
+/*
+ * Iterate over the container's focus stack and return an array with the
+ * containers inside it, ordered from higher focus order to lowest.
+ *
+ */
+Con **get_focus_order(Con *con) {
+ const int focus_heads = num_focus_heads(con);
+ Con **focus_order = smalloc(focus_heads * sizeof(Con *));
+ Con *current;
+ int idx = 0;
+ TAILQ_FOREACH(current, &(con->focus_head), focused) {
+ assert(idx < focus_heads);
+ focus_order[idx++] = current;
+ }
+
+ return focus_order;
+}
+
+/*
+ * Clear the container's focus stack and re-add it using the provided container
+ * array. The function doesn't check if the provided array contains the same
+ * containers with the previous focus stack but will not add floating containers
+ * in the new focus stack if container is not a workspace.
+ *
+ */
+void set_focus_order(Con *con, Con **focus_order) {
+ int focus_heads = 0;
+ while (!TAILQ_EMPTY(&(con->focus_head))) {
+ Con *current = TAILQ_FIRST(&(con->focus_head));
+
+ TAILQ_REMOVE(&(con->focus_head), current, focused);
+ focus_heads++;
+ }
+
+ for (int idx = 0; idx < focus_heads; idx++) {
+ /* Useful when encapsulating a workspace. */
+ if (con->type != CT_WORKSPACE && con_inside_floating(focus_order[idx])) {
+ focus_heads++;
+ continue;
+ }
+
+ TAILQ_INSERT_TAIL(&(con->focus_head), focus_order[idx], focused);
+ }
+}
+
/*
* Returns the number of children of this container.
*
Con *old_focused = focused;
if (fullscreen_mode == CF_GLOBAL && cur_ws != con_ws)
workspace_show(con_ws);
- con_focus(con);
+ con_activate(con);
if (fullscreen_mode != CF_GLOBAL && cur_ws != con_ws)
- con_focus(old_focused);
+ con_activate(old_focused);
con_set_fullscreen_mode(con, fullscreen_mode);
}
* 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_focus(con_descend_focused(con));
+ con_activate(con_descend_focused(con));
/* Restore focus if the output's focused workspace has changed. */
if (con_get_workspace(focused) != old_focus)
- con_focus(old_focus);
+ con_activate(old_focus);
}
/* 7: when moving to another workspace, we leave the focus on the current
/* 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)
- con_focus(con_descend_focused(focus_next));
+ con_activate(con_descend_focused(focus_next));
/* 8. If anything within the container is associated with a startup sequence,
* delete it so child windows won't be created on the old workspace. */
* visible workspace on the given output.
*
*/
-void con_move_to_output(Con *con, Output *output) {
+void con_move_to_output(Con *con, Output *output, bool fix_coordinates) {
Con *ws = NULL;
GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
assert(ws != NULL);
DLOG("Moving con %p to output %s\n", con, output_primary_name(output));
- con_move_to_workspace(con, ws, false, false, false);
+ con_move_to_workspace(con, ws, fix_coordinates, false, false);
+}
+
+/*
+ * Moves the given container to the currently focused container on the
+ * visible workspace on the output specified by the given name.
+ * The current output for the container is used to resolve relative names
+ * such as left, right, up, down.
+ *
+ */
+bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates) {
+ Output *current_output = get_output_for_con(con);
+ assert(current_output != NULL);
+
+ Output *output = get_output_from_string(current_output, name);
+ if (output == NULL) {
+ ELOG("Could not find output \"%s\"\n", name);
+ return false;
+ }
+
+ con_move_to_output(con, output, fix_coordinates);
+ return true;
}
/*
con->workspace_layout = ws_layout;
DLOG("Setting layout to %d\n", layout);
con->layout = layout;
- } else if (layout == L_STACKED || layout == L_TABBED) {
+ } else if (layout == L_STACKED || layout == L_TABBED || layout == L_SPLITV || layout == L_SPLITH) {
DLOG("Creating new split container\n");
/* 1: create a new split container */
Con *new = con_new(NULL, NULL);
new->layout = layout;
new->last_split_layout = con->last_split_layout;
- /* Save the container that was focused before we move containers
- * around, but only if the container is visible (otherwise focus
- * will be restored properly automatically when switching). */
- Con *old_focused = TAILQ_FIRST(&(con->focus_head));
- if (old_focused == TAILQ_END(&(con->focus_head)))
- old_focused = NULL;
- if (old_focused != NULL &&
- !workspace_is_visible(con_get_workspace(old_focused)))
- old_focused = NULL;
-
/* 3: move the existing cons of this workspace below the new con */
+ Con **focus_order = get_focus_order(con);
+
DLOG("Moving cons\n");
Con *child;
while (!TAILQ_EMPTY(&(con->nodes_head))) {
con_attach(child, new, true);
}
+ set_focus_order(new, focus_order);
+ free(focus_order);
+
/* 4: attach the new split container to the workspace */
DLOG("Attaching new split to ws\n");
con_attach(new, con, false);
- if (old_focused)
- con_focus(old_focused);
-
tree_flatten(croot);
}
con_force_split_parents_redraw(con);
* change to the opposite split layout. */
if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) {
layout = parent->last_split_layout;
+ /* In case last_split_layout was not initialized… */
+ if (layout == L_DEFAULT) {
+ layout = L_SPLITH;
+ }
} else {
layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH;
}
/* Allow it only if the container to be focused is contained within the
* current fullscreen container. */
- do {
- if (con->parent == fs)
- return true;
- con = con->parent;
- } while (con);
-
- /* Focusing con would hide it behind a fullscreen window, disallow it. */
- return false;
+ return con_has_parent(con, fs);
}
/*
Con *current_ws = con_get_workspace(old_focus);
const bool focused_within_first = (first == old_focus || con_has_parent(old_focus, first));
const bool focused_within_second = (second == old_focus || con_has_parent(old_focus, second));
+ fullscreen_mode_t first_fullscreen_mode = first->fullscreen_mode;
+ fullscreen_mode_t second_fullscreen_mode = second->fullscreen_mode;
- if (!con_fullscreen_permits_focusing(first_ws)) {
- DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", first_ws->name);
- return false;
+ if (first_fullscreen_mode != CF_NONE) {
+ con_disable_fullscreen(first);
}
-
- if (!con_fullscreen_permits_focusing(second_ws)) {
- DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", second_ws->name);
- return false;
+ if (second_fullscreen_mode != CF_NONE) {
+ con_disable_fullscreen(second);
}
double first_percent = first->percent;
* We don't need to check this for the second container because we've only
* moved the first one at this point.*/
if (first_ws != second_ws && focused_within_first) {
- con_focus(con_descend_focused(current_ws));
+ con_activate(con_descend_focused(current_ws));
}
/* Move second to where first has been originally. */
*/
if (focused_within_first) {
if (first_ws == second_ws) {
- con_focus(old_focus);
+ con_activate(old_focus);
} else {
- con_focus(con_descend_focused(second));
+ con_activate(con_descend_focused(second));
}
} else if (focused_within_second) {
if (first_ws == second_ws) {
- con_focus(old_focus);
+ con_activate(old_focus);
} else {
- con_focus(con_descend_focused(first));
+ con_activate(con_descend_focused(first));
}
}
second->percent = first_percent;
fake->percent = 0.0;
+ SWAP(first_fullscreen_mode, second_fullscreen_mode, fullscreen_mode_t);
+
swap_end:
+ /* The two windows exchange their original fullscreen status */
+ if (first_fullscreen_mode != CF_NONE) {
+ con_enable_fullscreen(first, first_fullscreen_mode);
+ }
+ if (second_fullscreen_mode != CF_NONE) {
+ con_enable_fullscreen(second, second_fullscreen_mode);
+ }
+
/* We don't actually need this since percentages-wise we haven't changed
* anything, but we'll better be safe than sorry and just make sure as we'd
* otherwise crash i3. */
if (config.workspace_urgency_timer == 0)
config.workspace_urgency_timer = 0.5;
+ config.focus_wrapping = FOCUS_WRAPPING_ON;
+
parse_configuration(override_configpath, true);
if (reload) {
config.default_layout = L_TABBED;
}
-CFGFUN(new_window, const char *windowtype, const char *border, const long width) {
+CFGFUN(default_border, const char *windowtype, const char *border, const long width) {
int border_style;
int border_width;
border_width = width;
}
- if (strcmp(windowtype, "new_window") == 0) {
+ if ((strcmp(windowtype, "default_border") == 0) ||
+ (strcmp(windowtype, "new_window") == 0)) {
DLOG("default tiled border style = %d and border width = %d (%d physical px)\n",
border_style, border_width, logical_px(border_width));
config.default_border = border_style;
config.disable_randr15 = eval_boolstr(value);
}
+CFGFUN(focus_wrapping, const char *value) {
+ if (strcmp(value, "force") == 0) {
+ config.focus_wrapping = FOCUS_WRAPPING_FORCE;
+ } else if (eval_boolstr(value)) {
+ config.focus_wrapping = FOCUS_WRAPPING_ON;
+ } else {
+ config.focus_wrapping = FOCUS_WRAPPING_OFF;
+ }
+}
+
CFGFUN(force_focus_wrapping, const char *value) {
- config.force_focus_wrapping = eval_boolstr(value);
+ /* Legacy syntax. */
+ if (eval_boolstr(value)) {
+ config.focus_wrapping = FOCUS_WRAPPING_FORCE;
+ } else {
+ /* For "force_focus_wrapping off", don't enable or disable
+ * focus wrapping, just ensure it's not forced. */
+ if (config.focus_wrapping == FOCUS_WRAPPING_FORCE) {
+ config.focus_wrapping = FOCUS_WRAPPING_ON;
+ }
+ }
}
CFGFUN(workspace_back_and_forth, const char *value) {
#undef APPLY_COLORS
}
-CFGFUN(assign, const char *workspace) {
+CFGFUN(assign_output, const char *output) {
if (match_is_empty(current_match)) {
ELOG("Match is empty, ignoring this assignment\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);
+ assignment->type = A_TO_OUTPUT;
+ assignment->dest.output = sstrdup(output);
+ TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+}
+
+CFGFUN(assign, const char *workspace, bool is_number) {
+ if (match_is_empty(current_match)) {
+ ELOG("Match is empty, ignoring this assignment\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;
+ }
+
DLOG("New assignment, using above criteria, to workspace \"%s\".\n", workspace);
Assignment *assignment = scalloc(1, sizeof(Assignment));
match_copy(&(assignment->match), current_match);
- assignment->type = A_TO_WORKSPACE;
+ assignment->type = is_number ? A_TO_WORKSPACE_NUMBER : A_TO_WORKSPACE;
assignment->dest.workspace = sstrdup(workspace);
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
}
current_bar->modifier = M_NONE;
}
-static void bar_configure_binding(const char *button, const char *command) {
+static void bar_configure_binding(const char *button, const char *release, const char *command) {
if (strncasecmp(button, "button", strlen("button")) != 0) {
ELOG("Bindings for a bar can only be mouse bindings, not \"%s\", ignoring.\n", button);
return;
ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button);
return;
}
+ const bool release_bool = release != NULL;
struct Barbinding *current;
TAILQ_FOREACH(current, &(current_bar->bar_bindings), bindings) {
- if (current->input_code == input_code) {
+ if (current->input_code == input_code && current->release == release_bool) {
ELOG("command for button %s was already specified, ignoring.\n", button);
return;
}
}
struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding));
+ new_binding->release = release_bool;
new_binding->input_code = input_code;
new_binding->command = sstrdup(command);
TAILQ_INSERT_TAIL(&(current_bar->bar_bindings), new_binding, bindings);
CFGFUN(bar_wheel_up_cmd, const char *command) {
ELOG("'wheel_up_cmd' is deprecated. Please us 'bindsym button4 %s' instead.\n", command);
- bar_configure_binding("button4", command);
+ bar_configure_binding("button4", NULL, command);
}
CFGFUN(bar_wheel_down_cmd, const char *command) {
ELOG("'wheel_down_cmd' is deprecated. Please us 'bindsym button5 %s' instead.\n", command);
- bar_configure_binding("button5", command);
+ bar_configure_binding("button5", NULL, command);
}
-CFGFUN(bar_bindsym, const char *button, const char *command) {
- bar_configure_binding(button, command);
+CFGFUN(bar_bindsym, const char *button, const char *release, const char *command) {
+ bar_configure_binding(button, release, command);
}
CFGFUN(bar_position, const char *position) {
/* read the script’s output */
int conv_size = 65535;
- char *converted = smalloc(conv_size);
+ char *converted = scalloc(conv_size, 1);
int read_bytes = 0, ret;
do {
if (read_bytes == conv_size) {
wait(&status);
if (!WIFEXITED(status)) {
fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
+ FREE(converted);
return NULL;
}
fprintf(stderr, "# i3 config file (v4)\n");
/* TODO: nag the user with a message to include a hint for i3 in their config file */
}
+ FREE(converted);
return NULL;
}
FREE(current_config);
current_config = scalloc(stbuf.st_size + 1, 1);
- fread(current_config, 1, stbuf.st_size, fstr);
+ if ((ssize_t)fread(current_config, 1, stbuf.st_size, fstr) != stbuf.st_size) {
+ die("Could not fread: %s\n", strerror(errno));
+ }
rewind(fstr);
bool invalid_sets = false;
int version = detect_version(buf);
if (version == 3) {
/* We need to convert this v3 configuration */
- char *converted = migrate_config(new, stbuf.st_size);
+ char *converted = migrate_config(new, strlen(new));
if (converted != NULL) {
ELOG("\n");
ELOG("****************************************************************\n");
*
*/
void display_running_version(void) {
- char *socket_path = root_atom_contents("I3_SOCKET_PATH", conn, conn_screen);
- if (socket_path == NULL)
- exit(EXIT_SUCCESS);
-
char *pid_from_atom = root_atom_contents("I3_PID", conn, conn_screen);
if (pid_from_atom == NULL) {
/* If I3_PID is not set, the running version is older than 4.2-200. */
printf("(Getting version from running i3, press ctrl-c to abort…)");
fflush(stdout);
- /* TODO: refactor this with the code for sending commands */
- int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
- if (sockfd == -1)
- err(EXIT_FAILURE, "Could not create socket");
-
- struct sockaddr_un addr;
- memset(&addr, 0, sizeof(struct sockaddr_un));
- addr.sun_family = AF_LOCAL;
- strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
- if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
- err(EXIT_FAILURE, "Could not connect to i3");
-
+ int sockfd = ipc_connect(NULL);
if (ipc_send_message(sockfd, 0, I3_IPC_MESSAGE_TYPE_GET_VERSION,
(uint8_t *)"") == -1)
err(EXIT_FAILURE, "IPC: write()");
yajl_free(handle);
free(reply);
free(pid_from_atom);
- free(socket_path);
}
render_con(con, false);
if (set_focus)
- con_focus(con);
+ 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. */
con_fix_percent(con->parent);
if (set_focus)
- con_focus(con);
+ con_activate(con);
floating_set_hint_atom(con, false);
ipc_send_window_event("floating", con);
Con *ws = TAILQ_FIRST(&(content->focus_head));
DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name);
con_move_to_workspace(con, ws, false, true, false);
- con_focus(con_descend_focused(con));
+ workspace_show(ws);
+ con_activate(con_descend_focused(con));
return true;
}
/* Custom data structure used to track dragging-related events. */
struct drag_x11_cb {
- ev_check check;
+ ev_prepare prepare;
/* Whether this modal event loop should be exited and with which result. */
drag_result_t result;
const void *extra;
};
-static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
+static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
xcb_motion_notify_event_t *last_motion_notify = NULL;
xcb_generic_event_t *event;
if (last_motion_notify != (xcb_motion_notify_event_t *)event)
free(event);
- if (dragloop->result != DRAGGING)
+ if (dragloop->result != DRAGGING) {
+ free(last_motion_notify);
return;
+ }
}
if (last_motion_notify == NULL)
dragloop->extra);
}
free(last_motion_notify);
+
+ xcb_flush(conn);
}
/*
.callback = callback,
.extra = extra,
};
- ev_check *check = &loop.check;
+ ev_prepare *prepare = &loop.prepare;
if (con)
loop.old_rect = con->rect;
- ev_check_init(check, xcb_drag_check_cb);
- check->data = &loop;
+ ev_prepare_init(prepare, xcb_drag_prepare_cb);
+ prepare->data = &loop;
main_set_x11_cb(false);
- ev_check_start(main_loop, check);
+ ev_prepare_start(main_loop, prepare);
while (loop.result == DRAGGING)
ev_run(main_loop, EVRUN_ONCE);
- ev_check_stop(main_loop, check);
+ ev_prepare_stop(main_loop, prepare);
main_set_x11_cb(true);
xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
DLOG("Focusing con = %p\n", con);
workspace_show(ws);
- con_focus(con);
+ 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))) {
DLOG("Marking con = %p urgent\n", con);
scratchpad_show(con);
} else {
workspace_show(ws);
- con_focus(con);
+ /* Re-set focus, even if unchanged from i3’s perspective. */
+ focused_id = XCB_NONE;
+ con_activate(con);
}
} else {
/* Request is from an application. */
if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
DLOG("Focusing con = %p\n", con);
workspace_show(ws);
- con_focus(con);
+ con_activate(con);
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
DLOG("Marking con = %p urgent\n", con);
con_set_urgency(con, true);
if (ws != con_get_workspace(focused))
workspace_show(ws);
- con_focus(con);
+ con_activate(con);
/* We update focused_id because we don’t need to set focus again */
focused_id = event->event;
tree_render();
}
DLOG("ConfigureNotify for root window 0x%08x\n", event->event);
+ if (force_xinerama) {
+ return;
+ }
randr_query_outputs();
}
y(integer, current->input_code);
ystr("command");
ystr(current->command);
+ ystr("release");
+ y(bool, current->release == B_UPON_KEYRELEASE);
y(map_close);
}
memcpy(client->events[event], s, len);
DLOG("client is now subscribed to:\n");
- for (int i = 0; i < client->num_events; i++)
+ for (int i = 0; i < client->num_events; i++) {
DLOG("event %s\n", client->events[i]);
+ }
DLOG("(done)\n");
return 1;
yajl_free(p);
const char *reply = "{\"success\":true}";
ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
+
+ if (client->first_tick_sent) {
+ return;
+ }
+
+ bool is_tick = false;
+ for (int i = 0; i < client->num_events; i++) {
+ if (strcmp(client->events[i], "tick") == 0) {
+ is_tick = true;
+ break;
+ }
+ }
+ if (!is_tick) {
+ return;
+ }
+
+ client->first_tick_sent = true;
+ const char *payload = "{\"first\":true,\"payload\":\"\"}";
+ ipc_send_message(client->fd, strlen(payload), I3_IPC_EVENT_TICK, (const uint8_t *)payload);
}
/*
y(free);
}
+/*
+ * Sends the tick event from the message payload to subscribers. Establishes a
+ * synchronization point in event-related tests.
+ */
+IPC_HANDLER(send_tick) {
+ yajl_gen gen = ygenalloc();
+
+ y(map_open);
+
+ ystr("payload");
+ yajl_gen_string(gen, (unsigned char *)message, message_size);
+
+ y(map_close);
+
+ const unsigned char *payload;
+ ylength length;
+ y(get_buf, &payload, &length);
+
+ ipc_send_event("tick", I3_IPC_EVENT_TICK, (const char *)payload);
+ y(free);
+
+ const char *reply = "{\"success\":true}";
+ ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_TICK, (const uint8_t *)reply);
+ DLOG("Sent tick event\n");
+}
+
/* The index of each callback function corresponds to the numeric
* value of the message type (see include/i3/ipc.h) */
-handler_t handlers[10] = {
+handler_t handlers[11] = {
handle_run_command,
handle_get_workspaces,
handle_subscribe,
handle_get_version,
handle_get_binding_modes,
handle_get_config,
+ handle_send_tick,
};
/*
yajl_free(hand);
if (to_focus) {
- con_focus(to_focus);
+ con_activate(to_focus);
}
}
/** The number of file descriptors passed via socket activation. */
int listen_fds;
-/* We keep the xcb_check watcher around to be able to enable and disable it
+/* We keep the xcb_prepare watcher around to be able to enable and disable it
* temporarily for drag_pointer(). */
-static struct ev_check *xcb_check;
+static struct ev_prepare *xcb_prepare;
extern Con *focused;
bool xcursor_supported = true;
bool xkb_supported = true;
+bool force_xinerama = false;
+
/*
- * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
+ * This callback is only a dummy, see xcb_prepare_cb.
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
*
*/
static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
- /* empty, because xcb_prepare_cb and xcb_check_cb are used */
+ /* empty, because xcb_prepare_cb are used */
}
/*
- * Flush before blocking (and waiting for new events)
+ * Called just before the event loop sleeps. Ensures xcb’s incoming and outgoing
+ * queues are empty so that any activity will trigger another event loop
+ * iteration, and hence another xcb_prepare_cb invocation.
*
*/
static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
- xcb_flush(conn);
-}
-
-/*
- * Instead of polling the X connection socket we leave this to
- * xcb_poll_for_event() which knows better than we can ever know.
- *
- */
-static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
+ /* Process all queued (and possibly new) events before the event loop
+ sleeps. */
xcb_generic_event_t *event;
while ((event = xcb_poll_for_event(conn)) != NULL) {
free(event);
}
+
+ /* Flush all queued events to X11. */
+ xcb_flush(conn);
}
/*
void main_set_x11_cb(bool enable) {
DLOG("Setting main X11 callback to enabled=%d\n", enable);
if (enable) {
- ev_check_start(main_loop, xcb_check);
+ ev_prepare_start(main_loop, xcb_prepare);
/* Trigger the watcher explicitly to handle all remaining X11 events.
* drag_pointer()’s event handler exits in the middle of the loop. */
- ev_feed_event(main_loop, xcb_check, 0);
+ ev_feed_event(main_loop, xcb_prepare, 0);
} else {
- ev_check_stop(main_loop, xcb_check);
+ ev_prepare_stop(main_loop, xcb_prepare);
}
}
fflush(stderr);
shm_unlink(shmlogname);
}
+ ipc_shutdown(SHUTDOWN_REASON_EXIT);
+ unlink(config.ipc_socket_path);
}
/*
- * (One-shot) Handler for all signals with default action "Term", see signal(7)
+ * (One-shot) Handler for all signals with default action "Core", see signal(7)
*
* Unlinks the SHM log and re-raises the signal.
*
*/
-static void handle_signal(int sig, siginfo_t *info, void *data) {
+static void handle_core_signal(int sig, siginfo_t *info, void *data) {
if (*shmlogname != '\0') {
shm_unlink(shmlogname);
}
raise(sig);
}
+/*
+ * (One-shot) Handler for all signals with default action "Term", see signal(7)
+ *
+ * Exits the program gracefully.
+ *
+ */
+static void handle_term_signal(struct ev_loop *loop, ev_signal *signal, int revents) {
+ /* We exit gracefully here in the sense that cleanup handlers
+ * installed via atexit are invoked. */
+ exit(128 + signal->signum);
+}
+
+/*
+ * Set up handlers for all signals with default action "Term", see signal(7)
+ *
+ */
+static void setup_term_handlers(void) {
+ static struct ev_signal signal_watchers[6];
+ size_t num_watchers = sizeof(signal_watchers) / sizeof(signal_watchers[0]);
+
+ /* We have to rely on libev functionality here and should not use
+ * sigaction handlers because we need to invoke the exit handlers
+ * and cannot do so from an asynchronous signal handling context as
+ * not all code triggered during exit is signal safe (and exiting
+ * the main loop from said handler is not easily possible). libev's
+ * signal handlers does not impose such a constraint on us. */
+ ev_signal_init(&signal_watchers[0], handle_term_signal, SIGHUP);
+ ev_signal_init(&signal_watchers[1], handle_term_signal, SIGINT);
+ ev_signal_init(&signal_watchers[2], handle_term_signal, SIGALRM);
+ ev_signal_init(&signal_watchers[3], handle_term_signal, SIGTERM);
+ ev_signal_init(&signal_watchers[4], handle_term_signal, SIGUSR1);
+ ev_signal_init(&signal_watchers[5], handle_term_signal, SIGUSR1);
+ for (size_t i = 0; i < num_watchers; i++) {
+ ev_signal_start(main_loop, &signal_watchers[i]);
+ /* The signal handlers should not block ev_run from returning
+ * and so none of the signal handlers should hold a reference to
+ * the main loop. */
+ ev_unref(main_loop);
+ }
+}
+
int main(int argc, char *argv[]) {
/* Keep a symbol pointing to the I3_VERSION string constant so that we have
* it in gdb backtraces. */
bool autostart = true;
char *layout_path = NULL;
bool delete_layout_path = false;
- bool force_xinerama = false;
bool disable_randr15 = false;
char *fake_outputs = NULL;
bool disable_signalhandler = false;
config.ipc_socket_path = sstrdup(config.ipc_socket_path);
}
+ if (config.force_xinerama) {
+ force_xinerama = true;
+ }
+
xcb_void_cookie_t cookie;
cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK});
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
fake_outputs_init(fake_outputs);
FREE(fake_outputs);
config.fake_outputs = NULL;
- } else if (force_xinerama || config.force_xinerama) {
+ } else if (force_xinerama) {
/* Force Xinerama (for drivers which don't support RandR yet, esp. the
* nVidia binary graphics driver), when specified either in the config
* file or on command-line */
output = get_first_output();
}
- con_focus(con_descend_focused(output_get_content(output->con)));
+ con_activate(con_descend_focused(output_get_content(output->con)));
free(pointerreply);
}
ewmh_update_desktop_viewport();
struct ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io));
- xcb_check = scalloc(1, sizeof(struct ev_check));
- struct ev_prepare *xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
+ xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
ev_io_start(main_loop, xcb_watcher);
- ev_check_init(xcb_check, xcb_check_cb);
- ev_check_start(main_loop, xcb_check);
-
ev_prepare_init(xcb_prepare, xcb_prepare_cb);
ev_prepare_start(main_loop, xcb_prepare);
err(EXIT_FAILURE, "pledge");
#endif
- struct sigaction action;
-
- action.sa_sigaction = handle_signal;
- action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
- sigemptyset(&action.sa_mask);
-
if (!disable_signalhandler)
setup_signal_handler();
else {
+ struct sigaction action;
+
+ action.sa_sigaction = handle_core_signal;
+ action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
+ sigemptyset(&action.sa_mask);
+
/* Catch all signals with default action "Core", see signal(7) */
if (sigaction(SIGQUIT, &action, NULL) == -1 ||
sigaction(SIGILL, &action, NULL) == -1 ||
ELOG("Could not setup signal handler.\n");
}
- /* Catch all signals with default action "Term", see signal(7) */
- if (sigaction(SIGHUP, &action, NULL) == -1 ||
- sigaction(SIGINT, &action, NULL) == -1 ||
- sigaction(SIGALRM, &action, NULL) == -1 ||
- sigaction(SIGUSR1, &action, NULL) == -1 ||
- sigaction(SIGUSR2, &action, NULL) == -1)
- ELOG("Could not setup signal handler.\n");
-
+ setup_term_handlers();
/* Ignore SIGPIPE to survive errors when an IPC client disconnects
* while we are sending them a message */
signal(SIGPIPE, SIG_IGN);
free(command);
}
- /* Make sure to destroy the event loop to invoke the cleeanup callbacks
+ /* Make sure to destroy the event loop to invoke the cleanup callbacks
* when calling exit() */
atexit(i3_exit);
Con *wm_desktop_ws = NULL;
/* If not, check if it is assigned to a specific workspace */
- if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE))) {
+ if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE)) ||
+ (assignment = assignment_for(cwindow, A_TO_WORKSPACE_NUMBER))) {
DLOG("Assignment matches (%p)\n", match);
- Con *assigned_ws = workspace_get(assignment->dest.workspace, NULL);
+
+ 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);
+ }
+ }
+ /* A_TO_WORKSPACE type assignment or fallback from A_TO_WORKSPACE_NUMBER
+ * when the target workspace number does not exist yet. */
+ if (!assigned_ws) {
+ assigned_ws = workspace_get(assignment->dest.workspace, NULL);
+ }
+
nc = con_descend_tiling_focused(assigned_ws);
DLOG("focused on ws %s: %p / %s\n", assigned_ws->name, nc, nc->name);
if (nc->type == CT_WORKSPACE)
} else
nc = tree_open_con(NULL, cwindow);
}
+
+ if ((assignment = assignment_for(cwindow, A_TO_OUTPUT))) {
+ con_move_to_output_name(nc, assignment->dest.output, true);
+ }
} else {
/* M_BELOW inserts the new window as a child of the one which was
* matched (e.g. dock areas) */
* needed e.g. for LibreOffice Impress multi-monitor
* presentations to work out of the box. */
if (output != NULL)
- con_move_to_output(nc, output);
+ con_move_to_output(nc, output, false);
con_toggle_fullscreen(nc, CF_OUTPUT);
}
fs = NULL;
* proper window event sequence. */
if (set_focus && nc->mapped) {
DLOG("Now setting focus.\n");
- con_focus(nc);
+ con_activate(nc);
}
tree_render();
attach_to_workspace(con, ws, direction);
/* fix the focus stack */
- con_focus(con);
+ con_activate(con);
/* force re-painting the indicators */
FREE(con->deco_render_params);
continue;
if (con_is_sticky(current)) {
- con_move_to_workspace(current, visible_ws, true, false, current != to_focus->parent);
+ bool ignore_focus = (to_focus == NULL) || (current != to_focus->parent);
+ con_move_to_workspace(current, visible_ws, true, false, ignore_focus);
}
}
}
Con *ws = create_workspace_on_output(output, content);
/* TODO: Set focus in main.c */
- con_focus(ws);
+ con_activate(ws);
}
/*
continue;
DLOG("Focusing primary output %s\n", output_primary_name(output));
- con_focus(con_descend_focused(output->con));
+ con_activate(con_descend_focused(output->con));
}
/* render_layout flushes */
if (next) {
DLOG("now focusing next = %p\n", next);
- con_focus(next);
+ con_activate(next);
workspace_show(con_get_workspace(next));
}
xcb_flush(conn);
}
-bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction) {
+bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides) {
DLOG("Find two participants for resizing container=%p in direction=%i\n", other, direction);
Con *first = *current;
Con *second = NULL;
/* get the counterpart for this resizement */
if (dir_backwards) {
second = TAILQ_PREV(first, nodes_head, nodes);
+ if (second == NULL && both_sides == true) {
+ second = TAILQ_NEXT(first, nodes);
+ }
} else {
second = TAILQ_NEXT(first, nodes);
+ if (second == NULL && both_sides == true) {
+ second = TAILQ_PREV(first, nodes_head, nodes);
+ }
}
if (second == NULL) {
static xcb_connection_t *restore_conn;
static struct ev_io *xcb_watcher;
-static struct ev_check *xcb_check;
static struct ev_prepare *xcb_prepare;
static void restore_handle_event(int type, xcb_generic_event_t *event);
}
static void restore_xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
- xcb_flush(restore_conn);
-}
-
-static void restore_xcb_check_cb(EV_P_ ev_check *w, int revents) {
xcb_generic_event_t *event;
if (xcb_connection_has_error(restore_conn)) {
free(event);
}
+
+ xcb_flush(restore_conn);
}
/*
/* This is not the initial connect, but a reconnect, most likely
* because our X11 connection was killed (e.g. by a user with xkill. */
ev_io_stop(main_loop, xcb_watcher);
- ev_check_stop(main_loop, xcb_check);
ev_prepare_stop(main_loop, xcb_prepare);
placeholder_state *state;
*/
xcb_disconnect(restore_conn);
free(xcb_watcher);
- free(xcb_check);
free(xcb_prepare);
}
}
xcb_watcher = scalloc(1, sizeof(struct ev_io));
- xcb_check = scalloc(1, sizeof(struct ev_check));
xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
ev_io_init(xcb_watcher, restore_xcb_got_event, xcb_get_file_descriptor(restore_conn), EV_READ);
ev_io_start(main_loop, xcb_watcher);
- ev_check_init(xcb_check, restore_xcb_check_cb);
- ev_check_start(main_loop, xcb_check);
-
ev_prepare_init(xcb_prepare, restore_xcb_prepare_cb);
ev_prepare_start(main_loop, xcb_prepare);
}
/* use con_descend_tiling_focused to get the last focused
* window inside this scratch container in order to
* keep the focus the same within this container */
- con_focus(con_descend_tiling_focused(walk_con));
+ con_activate(con_descend_tiling_focused(walk_con));
return;
}
}
workspace_show(active);
}
- con_focus(con_descend_focused(con));
+ con_activate(con_descend_focused(con));
}
/*
DLOG("parent container killed\n");
}
+ if (ws == con) {
+ DLOG("Closing a workspace container, updating EWMH atoms\n");
+ ewmh_update_number_of_desktops();
+ ewmh_update_desktop_names();
+ ewmh_update_wm_desktop();
+ }
+
con_free(con);
/* in the case of floating windows, we already focused another container
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_focus(con_descend_focused(output_get_content(next->parent)));
+ 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_focus(next);
+ con_activate(next);
}
} else {
DLOG("not focusing because we're not killing anybody\n");
/* Skip over floating containers and go directly to the grandparent
* (which should always be a workspace) */
if (focused->parent->type == CT_FLOATING_CON) {
- con_focus(focused->parent->parent);
+ con_activate(focused->parent->parent);
return true;
}
ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n");
return false;
}
- con_focus(focused->parent);
+ con_activate(focused->parent);
return true;
}
next = TAILQ_FIRST(&(next->focus_head));
}
- con_focus(next);
+ con_activate(next);
return true;
}
if (!workspace)
return false;
- workspace_show(workspace);
-
- /* If a workspace has an active fullscreen container, one of its
- * children should always be focused. The above workspace_show()
- * should be adequate for that, so return. */
- if (con_get_fullscreen_con(workspace, CF_OUTPUT))
- return true;
-
- Con *focus = con_descend_direction(workspace, direction);
-
- /* special case: if there was no tiling con to focus and the workspace
- * has a floating con in the focus stack, focus the top of the focus
- * stack (which may be floating) */
- if (focus == workspace)
+ Con *focus = con_descend_tiling_focused(workspace);
+ if (focus == workspace) {
focus = con_descend_focused(workspace);
-
- if (focus) {
- con_focus(focus);
- x_set_warp_to(&(focus->rect));
}
+
+ workspace_show(workspace);
+ con_activate(focus);
+ x_set_warp_to(&(focus->rect));
return true;
}
TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows);
}
- con_focus(con_descend_focused(next));
+ con_activate(con_descend_focused(next));
return true;
}
next = TAILQ_PREV(current, nodes_head, nodes);
if (!next) {
- if (!config.force_focus_wrapping) {
+ if (config.focus_wrapping != FOCUS_WRAPPING_FORCE) {
/* If there is no next/previous container, we check if we can focus one
* when going higher (without wrapping, though). If so, we are done, if
* not, we wrap */
/* 3: focus choice comes in here. at the moment we will go down
* until we find a window */
/* TODO: check for window, atm we only go down as far as possible */
- con_focus(con_descend_focused(next));
+ con_activate(con_descend_focused(next));
return true;
}
*
*/
void tree_next(char way, orientation_t orientation) {
- _tree_next(focused, way, orientation, true);
+ _tree_next(focused, way, orientation,
+ config.focus_wrapping != FOCUS_WRAPPING_OFF);
}
/*
size_t n = fread(*buf, 1, stbuf.st_size, f);
fclose(f);
if ((ssize_t)n != stbuf.st_size) {
- ELOG("File \"%s\" could not be read entirely: got %zd, want %zd\n", path, 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;
return -1;
y(free);
+ /* Avoid calling output_push_sticky_windows later with a freed container. */
+ if (old == old_focus) {
+ old_focus = NULL;
+ }
+
ewmh_update_number_of_desktops();
ewmh_update_desktop_names();
ewmh_update_desktop_viewport();
/* 2: copy layout from workspace */
split->layout = ws->layout;
- Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
-
/* 3: move the existing cons of this workspace below the new con */
+ Con **focus_order = get_focus_order(ws);
+
DLOG("Moving cons\n");
while (!TAILQ_EMPTY(&(ws->nodes_head))) {
Con *child = TAILQ_FIRST(&(ws->nodes_head));
con_attach(child, split, true);
}
+ set_focus_order(split, focus_order);
+ free(focus_order);
+
/* 4: switch workspace layout */
ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
DLOG("split->layout = %d, ws->layout = %d\n", split->layout, ws->layout);
/* 6: fix the percentages */
con_fix_percent(ws);
-
- if (old_focused)
- con_focus(old_focused);
}
/*
new->parent = ws;
new->layout = ws->layout;
+ Con **focus_order = get_focus_order(ws);
+
DLOG("Moving children of workspace %p / %s into container %p\n",
ws, ws->name, new);
-
Con *child;
while (!TAILQ_EMPTY(&(ws->nodes_head))) {
child = TAILQ_FIRST(&(ws->nodes_head));
con_attach(child, new, true);
}
+ set_focus_order(new, focus_order);
+ free(focus_order);
+
con_attach(new, ws, true);
return new;
*
*/
void update_shmlog_atom() {
- xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
- A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
- strlen(shmlogname), shmlogname);
+ if (*shmlogname == '\0') {
+ xcb_delete_property(conn, root, A_I3_SHMLOG_PATH);
+ } else {
+ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
+ A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
+ strlen(shmlogname), shmlogname);
+ }
}
/*
# subshell or situations like that.
AnyEvent::Util::close_all_fds_except(0, 1, 2);
+our @CLEANUP;
+
# convenience wrapper to write to the log file
my $log;
sub Log { say $log "@_" }
xtrace => 0,
coverage => 0,
restart => 0,
+ xvfb => 1,
);
my $keep_xserver_output = 0;
"valgrind" => \$options{valgrind},
"strace" => \$options{strace},
"xtrace" => \$options{xtrace},
+ "xvfb!" => \$options{xvfb},
"display=s" => \@displays,
"parallel=i" => \$parallel,
"help|?" => \$help,
qx(Xephyr -help 2>&1);
die "Xephyr was not found in your path. Please install Xephyr (xserver-xephyr on Debian)." if $?;
+qx(xvfb-run --help 2>&1);
+if ($? && $options{xvfb}) {
+ say "xvfb-run not found, not running tests under xvfb. Install the xvfb package to speed up tests";
+ $options{xvfb} = 0;
+}
+
+if ($options{xvfb}) {
+ for (my $n = 99; $n < 120; $n++) {
+ my $path = File::Temp::tmpnam($ENV{TMPDIR} // "/tmp", "i3-testsXXXXXX");
+ if (!defined(POSIX::mkfifo($path, 0600))) {
+ die "mkfifo: $!";
+ }
+ my $pid = fork // die "fork: $!";
+ if ($pid == 0) {
+ # Child
+
+ # Xvfb checks whether the parent ignores USR1 and sends USR1 to the
+ # parent when ready, so that the wait call will be interrupted. We
+ # can’t implement this in Perl, as Perl’s waitpid transparently
+ # handles -EINTR.
+ exec('/bin/sh', '-c', qq|trap "exit" INT; trap : USR1; (trap '' USR1; exec Xvfb :$n -screen 0 640x480x8 -nolisten tcp) & PID=\$!; wait; if ! kill -0 \$PID 2>/dev/null; then echo 1:\$PID > $path; else echo 0:\$PID > $path; wait \$PID; fi|);
+ die "exec: $!";
+ }
+ chomp(my $kill = slurp($path));
+ unlink($path);
+ my ($code, $xvfbpid) = ($kill =~ m,^([0-1]):(.*)$,);
+ next unless $code eq '0';
+
+ $ENV{DISPLAY} = ":$n";
+ say "Running tests under Xvfb display $ENV{DISPLAY}";
+
+ push(@CLEANUP, sub {
+ kill(15, $xvfbpid);
+ });
+ last;
+ }
+}
+
@displays = split(/,/, join(',', @displays));
@displays = map { s/ //g; $_ } @displays;
sub cleanup {
my $exitcode = $?;
- $_->() for our @CLEANUP;
+ $_->() for @CLEANUP;
exit $exitcode;
}
Runs i3 under xtrace to trace X11 requests/replies. The output will be
available in C<latest/xtrace-for-$test.log>.
+=item B<--xvfb>
+
+=item B<--no-xvfb>
+
+Enable or disable running tests under Xvfb. Enabled by default.
+
=item B<--coverage-testing>
Generates a test coverage report at C<latest/i3-coverage>. Exits i3 cleanly
#include <sys/resource.h>
#include <sys/mman.h>
#include <sys/stat.h>
+#include <sys/wait.h>
#include <libgen.h>
static void uds_connection_cb(EV_P_ ev_io *w, int revents);
# First get the last used display number, then increment it by one.
# Effectively falls back to 1 if no X server is running.
- my ($displaynum) = map { /(\d+)$/ } reverse sort glob($x_socketpath . '*');
+ my ($displaynum) = reverse sort { $a <=> $b } map{ /(\d+)$/ } glob($x_socketpath . '*');
$displaynum++;
say "Starting $parallel Xephyr instances, starting at :$displaynum...";
for (1 .. $parallel) {
my $socket = fork_xserver($keep_xserver_output, $displaynum,
'Xephyr', ":$displaynum", '-screen', '1280x800',
- '-nolisten', 'tcp');
+ '-nolisten', 'tcp', '-name', "i3test");
push(@displays, ":$displaynum");
push(@sockets_waiting, $socket);
$displaynum++;
use List::Util qw(first);
use Time::HiRes qw(sleep);
use Cwd qw(abs_path);
+use POSIX ':sys_wait_h';
use Scalar::Util qw(blessed);
use SocketActivation;
use i3test::Util qw(slurp);
cmd
sync_with_i3
exit_gracefully
+ exit_forcefully
workspace_exists
focused_ws
get_socket_path
wait_for_unmap
$x
kill_all_windows
+ events_for
+ listen_for_binding
);
=head1 NAME
} else {
kill(-9, $i3_pid)
- or $tester->BAIL_OUT("could not kill i3");
+ or $tester->BAIL_OUT("could not kill i3: $!");
waitpid $i3_pid, 0;
}
my ($class, %args) = @_;
my $pkg = caller;
+ $x ||= i3test::X11->new;
+ # set the pointer to a predictable position in case a previous test has
+ # disturbed it
+ $x->warp_pointer(
+ 0, # src_window (None)
+ $x->get_root_window(), # dst_window (None)
+ 0, # src_x
+ 0, # src_y
+ 0, # src_width
+ 0, # src_height
+ 0, # dst_x
+ 0); # dst_y
+ # Synchronize with X11 to ensure the pointer has been warped before i3
+ # starts up.
+ $x->get_input_focus_reply($x->get_input_focus()->{sequence});
+
$i3_autostart = delete($args{i3_autostart}) // 1;
my $i3_config = delete($args{i3_config}) // '-default';
strict->import;
warnings->import;
- $x ||= i3test::X11->new;
- # set the pointer to a predictable position in case a previous test has
- # disturbed it
- $x->root->warp_pointer(0, 0);
$cv->recv if $i3_autostart;
@_ = ($class);
sub wait_for_event {
my ($timeout, $cb) = @_;
- my $cv = AE::cv;
-
$x->flush;
- # unfortunately, there is no constant for this
- my $ae_read = 0;
-
- my $guard = AE::io $x->get_file_descriptor, $ae_read, sub {
- while (defined(my $event = $x->poll_for_event)) {
- if ($cb->($event)) {
- $cv->send(1);
- last;
- }
- }
- };
-
- # Trigger timeout after $timeout seconds (can be fractional)
- my $t = AE::timer $timeout, 0, sub { warn "timeout ($timeout secs)"; $cv->send(0) };
-
- my $result = $cv->recv;
- undef $t;
- undef $guard;
- return $result;
+ while (defined(my $event = $x->wait_for_event)) {
+ return 1 if $cb->($event);
+ }
}
=head2 wait_for_map($window)
$window->map;
wait_for_map($window);
+
+ # MapWindow is sent before i3 even starts rendering: the window is placed at
+ # temporary off-screen coordinates first, and x_push_changes() sends further
+ # X11 requests to set focus etc. Hence, we sync with i3 before continuing.
+ sync_with_i3();
+
return $window;
}
$_sync_window = open_window(
rect => [ -15, -15, 10, 10 ],
override_redirect => 1,
+ dont_map => 1,
);
}
if (!$exited) {
kill(9, $pid)
- or $tester->BAIL_OUT("could not kill i3");
+ or $tester->BAIL_OUT("could not kill i3: $!");
}
if ($socketpath =~ m,^/tmp/i3-test-socket-,) {
undef $i3_pid;
}
+=head2 exit_forcefully($pid, [ $signal ])
+
+Tries to exit i3 forcefully by sending a signal (defaults to SIGTERM).
+
+You only need to use this function if you want to test signal handling
+(in which case you must have launched i3 on your own with
+C<launch_with_config>).
+
+ use i3test i3_autostart => 0;
+ my $pid = launch_with_config($config);
+ # …
+ exit_forcefully($pid);
+
+=cut
+sub exit_forcefully {
+ my ($pid, $signal) = @_;
+ $signal ||= 'TERM';
+
+ # Send the given signal to the i3 instance and wait for up to 10s
+ # for it to terminate.
+ kill($signal, $pid)
+ or $tester->BAIL_OUT("could not kill i3: $!");
+ my $status;
+ my $timeout = 10;
+ do {
+ $status = waitpid $pid, WNOHANG;
+
+ if ($status <= 0) {
+ sleep(1);
+ $timeout--;
+ }
+ } while ($status <= 0 && $timeout > 0);
+
+ if ($status <= 0) {
+ kill('KILL', $pid)
+ or $tester->BAIL_OUT("could not kill i3: $!");
+ waitpid $pid, 0;
+ }
+ undef $i3_pid;
+}
+
=head2 get_socket_path([ $cache ])
Gets the socket path from the C<I3_SOCKET_PATH> atom stored on the X11 root
cmd '[title=".*"] kill';
}
+=head2 events_for($subscribecb, [ $rettype ], [ $eventcbs ])
+
+Helper function which returns an array containing all events of type $rettype
+which were generated by i3 while $subscribecb was running.
+
+Set $eventcbs to subscribe to multiple event types and/or perform your own event
+aggregation.
+
+=cut
+sub events_for {
+ my ($subscribecb, $rettype, $eventcbs) = @_;
+
+ my @events;
+ $eventcbs //= {};
+ if (defined($rettype)) {
+ $eventcbs->{$rettype} = sub { push @events, shift };
+ }
+ my $subscribed = AnyEvent->condvar;
+ my $flushed = AnyEvent->condvar;
+ $eventcbs->{tick} = sub {
+ my ($event) = @_;
+ if ($event->{first}) {
+ $subscribed->send($event);
+ } else {
+ $flushed->send($event);
+ }
+ };
+ my $i3 = i3(get_socket_path(0));
+ $i3->connect->recv;
+ $i3->subscribe($eventcbs)->recv;
+ $subscribed->recv;
+ # Subscription established, run the callback.
+ $subscribecb->();
+ # Now generate a tick event, which we know we’ll receive (and at which point
+ # all other events have been received).
+ my $nonce = int(rand(255)) + 1;
+ $i3->send_tick($nonce);
+
+ my $tick = $flushed->recv;
+ $tester->is_eq($tick->{payload}, $nonce, 'tick nonce received');
+ return @events;
+}
+
+=head2 listen_for_binding($cb)
+
+Helper function to evaluate whether sending KeyPress/KeyRelease events via XTEST
+triggers an i3 key binding or not. Expects key bindings to be configured in the
+form “bindsym <binding> nop <binding>”, e.g. “bindsym Mod4+Return nop
+Mod4+Return”.
+
+ is(listen_for_binding(
+ sub {
+ xtest_key_press(133); # Super_L
+ xtest_key_press(36); # Return
+ xtest_key_release(36); # Return
+ xtest_key_release(133); # Super_L
+ xtest_sync_with_i3;
+ },
+ ),
+ 'Mod4+Return',
+ 'triggered the "Mod4+Return" keybinding');
+
+=cut
+
+sub listen_for_binding {
+ my ($cb) = @_;
+ my $triggered = AnyEvent->condvar;
+ my @events = events_for(
+ $cb,
+ 'binding');
+
+ $tester->is_eq(scalar @events, 1, 'Received precisely one event');
+ $tester->is_eq($events[0]->{change}, 'run', 'change is "run"');
+ # We look at the command (which is “nop <binding>”) because that is easier
+ # than re-assembling the string representation of $event->{binding}.
+ my $command = $events[0]->{binding}->{command};
+ $command =~ s/^nop //g;
+ return $command;
+}
+
=head1 AUTHOR
Michael Stapelberg <michael@i3wm.org>
our @EXPORT = qw(
is_num_children
+ is_num_fullscreen
cmp_float
does_i3_live
);
$tb->is_num($got_num_children, $num_children, $name);
}
+=head2 is_num_fullscreen($workspace, $expected, $test_name)
+
+Gets the number of fullscreen containers on the given workspace and verifies that
+they match the expected amount.
+
+ is_num_fullscreen('1', 0, 'no fullscreen containers on workspace 1');
+
+=cut
+sub is_num_fullscreen {
+ my ($workspace, $num_fullscreen, $name) = @_;
+ my $workspace_content = i3test::get_ws($workspace);
+ my $tb = $CLASS->builder;
+
+ my $nodes = scalar grep { $_->{fullscreen_mode} != 0 } @{$workspace_content->{nodes}->[0]->{nodes}};
+ my $cons = scalar grep { $_->{fullscreen_mode} != 0 } @{$workspace_content->{nodes}};
+ my $floating = scalar grep { $_->{fullscreen_mode} != 0 } @{$workspace_content->{floating_nodes}->[0]->{nodes}};
+ $tb->is_num($nodes + $cons + $floating, $num_fullscreen, $name);
+}
+
=head2 cmp_float($a, $b)
Compares floating point numbers C<$a> and C<$b> and returns true if they differ
use Exporter ();
our @EXPORT = qw(
inlinec_connect
+ xtest_sync_with
+ xtest_sync_with_i3
set_xkb_group
xtest_key_press
xtest_key_release
xtest_button_press
xtest_button_release
- listen_for_binding
- start_binding_capture
binding_events
);
# ineffective.
my %sn_config;
BEGIN {
- %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest');
+ %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest xcb-util');
}
use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
#include <xcb/xcb.h>
#include <xcb/xkb.h>
#include <xcb/xtest.h>
+#include <xcb/xcb_aux.h>
static xcb_connection_t *conn = NULL;
+static xcb_window_t sync_window;
+static xcb_window_t root_window;
+static xcb_atom_t i3_sync_atom;
bool inlinec_connect() {
int screen;
}
free(usereply);
+ xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, xcb_intern_atom(conn, 0, strlen("I3_SYNC"), "I3_SYNC"), NULL);
+ i3_sync_atom = reply->atom;
+ free(reply);
+
+ xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
+ root_window = root_screen->root;
+ sync_window = xcb_generate_id(conn);
+ xcb_create_window(conn,
+ XCB_COPY_FROM_PARENT, // depth
+ sync_window, // window
+ root_window, // parent
+ -15, // x
+ -15, // y
+ 1, // width
+ 1, // height
+ 0, // border_width
+ XCB_WINDOW_CLASS_INPUT_OUTPUT, // class
+ XCB_COPY_FROM_PARENT, // visual
+ XCB_CW_OVERRIDE_REDIRECT, // value_mask
+ (uint32_t[]){
+ 1, // override_redirect
+ }); // value_list
+
return true;
}
+void xtest_sync_with(int window) {
+ xcb_client_message_event_t ev;
+ memset(&ev, '\0', sizeof(xcb_client_message_event_t));
+
+ const int nonce = rand() % 255;
+
+ ev.response_type = XCB_CLIENT_MESSAGE;
+ ev.window = sync_window;
+ ev.type = i3_sync_atom;
+ ev.format = 32;
+ ev.data.data32[0] = sync_window;
+ ev.data.data32[1] = nonce;
+
+ xcb_send_event(conn, false, (xcb_window_t)window, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev);
+ xcb_flush(conn);
+
+ xcb_generic_event_t *event = NULL;
+ while (1) {
+ free(event);
+ if ((event = xcb_wait_for_event(conn)) == NULL) {
+ break;
+ }
+ if (event->response_type == 0) {
+ fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
+ continue;
+ }
+
+ /* Strip off the highest bit (set if the event is generated) */
+ const int type = (event->response_type & 0x7F);
+ switch (type) {
+ case XCB_CLIENT_MESSAGE: {
+ xcb_client_message_event_t *ev = (xcb_client_message_event_t *)event;
+ {
+ const uint32_t got = ev->data.data32[0];
+ const uint32_t want = sync_window;
+ if (got != want) {
+ fprintf(stderr, "Ignoring ClientMessage: unknown window: got %d, want %d\n", got, want);
+ continue;
+ }
+ }
+ {
+ const uint32_t got = ev->data.data32[1];
+ const uint32_t want = nonce;
+ if (got != want) {
+ fprintf(stderr, "Ignoring ClientMessage: unknown nonce: got %d, want %d\n", got, want);
+ continue;
+ }
+ }
+ return;
+ }
+ default:
+ fprintf(stderr, "Unexpected X11 event of type %d received (XCB_CLIENT_MESSAGE = %d)\n", type, XCB_CLIENT_MESSAGE);
+ break;
+ }
+ }
+ free(event);
+}
+
+void xtest_sync_with_i3() {
+ xtest_sync_with((int)root_window);
+}
+
// NOTE: while |group| should be a uint8_t, Inline::C will not define the
// function unless we use an int.
bool set_xkb_group(int group) {
=cut
-my $i3;
-our @binding_events;
-
-=head2 start_binding_capture()
-
-Captures all binding events sent by i3 in the C<@binding_events> symbol, so
-that you can verify the correct number of binding events was generated.
-
- my $pid = launch_with_config($config);
- start_binding_capture;
- # …
- sync_with_i3;
- is(scalar @i3test::XTEST::binding_events, 2, 'Received exactly 2 binding events');
-
-=cut
-
-sub start_binding_capture {
- # Store a copy of each binding event so that we can count the expected
- # events in test cases.
- $i3 = i3(get_socket_path());
- $i3->connect()->recv;
- $i3->subscribe({
- binding => sub {
- my ($event) = @_;
- @binding_events = (@binding_events, $event);
- },
- })->recv;
-}
-
-=head2 listen_for_binding($cb)
-
-Helper function to evaluate whether sending KeyPress/KeyRelease events via
-XTEST triggers an i3 key binding or not (with a timeout of 0.5s). Expects key
-bindings to be configured in the form “bindsym <binding> nop <binding>”, e.g.
-“bindsym Mod4+Return nop Mod4+Return”.
-
- is(listen_for_binding(
- sub {
- xtest_key_press(133); # Super_L
- xtest_key_press(36); # Return
- xtest_key_release(36); # Return
- xtest_key_release(133); # Super_L
- },
- ),
- 'Mod4+Return',
- 'triggered the "Mod4+Return" keybinding');
-
-=cut
-
-sub listen_for_binding {
- my ($cb) = @_;
- my $triggered = AnyEvent->condvar;
- my $i3 = i3(get_socket_path());
- $i3->connect()->recv;
- $i3->subscribe({
- binding => sub {
- my ($event) = @_;
- return unless $event->{change} eq 'run';
- # We look at the command (which is “nop <binding>”) because that is
- # easier than re-assembling the string representation of
- # $event->{binding}.
- $triggered->send($event->{binding}->{command});
- },
- })->recv;
-
- my $t;
- $t = AnyEvent->timer(
- after => 0.5,
- cb => sub {
- $triggered->send('timeout');
- }
- );
-
- $cb->();
-
- my $recv = $triggered->recv;
- $recv =~ s/^nop //g;
- return $recv;
-}
-
=head2 set_xkb_group($group)
Changes the current XKB group from the default of 1 to C<$group>, which must be
Returns false when there was an X11 error, true otherwise.
+=head2 xtest_sync_with($window)
+
+Ensures the specified window has processed all X11 events which were triggered
+by this module, provided the window response to the i3 sync protocol.
+
+=head2 xtest_sync_with_i3()
+
+Ensures i3 has processed all X11 events which were triggered by this module.
+
=head1 AUTHOR
Michael Stapelberg <michael@i3wm.org>
my $tmp = fresh_workspace;
-sub fullscreen_windows {
- my $ws = $tmp;
- $ws = shift if @_;
-
- scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)}
-}
-
# get the output of this workspace
my $tree = $i3->get_tree->recv;
my @outputs = @{$tree->{nodes}};
$swindow->fullscreen(1);
sync_with_i3;
-is(fullscreen_windows(), 1, 'amount of fullscreen windows');
+is_num_fullscreen($tmp, 1, 'amount of fullscreen windows');
$window->fullscreen(0);
sync_with_i3;
-is(fullscreen_windows(), 1, 'amount of fullscreen windows');
+is_num_fullscreen($tmp, 1, 'amount of fullscreen windows');
ok($swindow->mapped, 'window mapped after other fullscreen ended');
$swindow->fullscreen(0);
sync_with_i3;
-is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling');
+is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after disabling');
cmd 'fullscreen';
-is(fullscreen_windows(), 1, 'amount of fullscreen windows after fullscreen command');
+is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after fullscreen command');
cmd 'fullscreen';
-is(fullscreen_windows(), 0, 'amount of fullscreen windows after fullscreen command');
+is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after fullscreen command');
# clean up the workspace so that it will be cleaned when switching away
cmd 'kill' for (@{get_ws_content($tmp)});
cmd 'fullscreen';
-is(fullscreen_windows($tmp2), 1, 'one fullscreen window on second ws');
+is_num_fullscreen($tmp2, 1, 'one fullscreen window on second ws');
cmd "move workspace $tmp";
-is(fullscreen_windows($tmp2), 0, 'no fullscreen windows on second ws');
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on first ws');
+is_num_fullscreen($tmp2, 0, 'no fullscreen windows on second ws');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on first ws');
$swindow->fullscreen(0);
sync_with_i3;
# Verify that $swindow was the one that initially remained fullscreen.
-is(fullscreen_windows($tmp), 0, 'no fullscreen windows on first ws');
+is_num_fullscreen($tmp, 0, 'no fullscreen windows on first ws');
################################################################################
# Verify that opening a window with _NET_WM_STATE_FULLSCREEN unfullscreens any
cmd "fullscreen";
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on ws');
is($x->input_focus, $window->id, 'fullscreen window focused');
$swindow = open_window({
fullscreen => 1
});
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on ws');
is($x->input_focus, $swindow->id, 'fullscreen window focused');
################################################################################
$window = open_window;
is($x->input_focus, $window->id, 'window focused');
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
cmd 'fullscreen enable';
is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
cmd 'fullscreen enable';
is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 1, 'still one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'still one fullscreen window on workspace');
$window->fullscreen(0);
sync_with_i3;
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
################################################################################
# Verify that command ‘fullscreen enable global’ works and is idempotent.
$window = open_window;
is($x->input_focus, $window->id, 'window focused');
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
cmd 'fullscreen enable global';
is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
cmd 'fullscreen enable global';
is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 1, 'still one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'still one fullscreen window on workspace');
$window->fullscreen(0);
sync_with_i3;
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
################################################################################
# Verify that command ‘fullscreen disable’ works and is idempotent.
$window = open_window;
is($x->input_focus, $window->id, 'window focused');
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
$window->fullscreen(1);
sync_with_i3;
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
cmd 'fullscreen disable';
is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
cmd 'fullscreen disable';
is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 0, 'still no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'still no fullscreen window on workspace');
################################################################################
# Verify that command ‘fullscreen toggle’ works.
$tmp = fresh_workspace;
$window = open_window;
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
cmd 'fullscreen toggle';
is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
cmd 'fullscreen toggle';
is($x->input_focus, $window->id, 'window still focused');
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
################################################################################
# Verify that a window’s fullscreen is disabled when another one is enabled
$other = open_window;
is($x->input_focus, $other->id, 'other window focused');
-is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
+is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
cmd 'fullscreen enable';
is($x->input_focus, $other->id, 'other window focused');
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
cmd '[id="' . $window->id . '"] fullscreen enable';
is($x->input_focus, $window->id, 'window focused');
-is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
+is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
################################################################################
# Verify that when a global fullscreen is enabled the window is focused and
cmd 'move right';
cmd '[id="' . $w3->id . '"] focus';
sync_with_i3;
- my $ws = get_ws($tmp);
+ $ws = get_ws($tmp);
ok(!$ws->{urgent}, 'urgent flag not set on workspace');
##############################################################################
use i3test;
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-################################
-# Workspaces requests and events
-################################
-
my $old_ws = get_ws(focused_ws());
-# Events
-
# We are switching to an empty workpspace from an empty workspace, so we expect
# to receive "init", "focus", and "empty".
-my $init = AnyEvent->condvar;
-my $focus = AnyEvent->condvar;
-my $empty = AnyEvent->condvar;
-$i3->subscribe({
- workspace => sub {
- my ($event) = @_;
- if ($event->{change} eq 'init') {
- $init->send($event);
- } elsif ($event->{change} eq 'focus') {
- $focus->send($event);
- } elsif ($event->{change} eq 'empty') {
- $empty->send($event);
- }
- }
-})->recv;
-
-cmd 'workspace 2';
-
-my $t;
-$t = AnyEvent->timer(
- after => 0.5,
- cb => sub {
- $init->send(0);
- $focus->send(0);
- $empty->send(0);
- }
-);
-
-my $init_event = $init->recv;
-my $focus_event = $focus->recv;
-my $empty_event = $empty->recv;
+my @events = events_for(
+ sub { cmd 'workspace 2' },
+ 'workspace');
my $current_ws = get_ws(focused_ws());
-ok($init_event, 'workspace "init" event received');
-is($init_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the initted workspace con');
+is(scalar @events, 3, 'Received 3 events');
+is($events[0]->{change}, 'init', 'First event has change = init');
+is($events[0]->{current}->{id}, $current_ws->{id}, 'the "current" property contains the initted workspace con');
-ok($focus_event, 'workspace "focus" event received');
-is($focus_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con');
-is($focus_event->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last');
+is($events[1]->{change}, 'focus', 'Second event has change = focus');
+is($events[1]->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con');
+is($events[1]->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last');
-ok($empty_event, 'workspace "empty" event received');
-is($empty_event->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con');
+is($events[2]->{change}, 'empty', 'Third event has change = empty');
+is($events[2]->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con');
done_testing;
cmd 'focus child';
is($x->input_focus, $right2->id, 'bottom right window focused again');
-cmd '[id="' . $left->id . '"] focus';
-is($x->input_focus, $right2->id, 'prevented focus change to left window');
-
cmd 'focus up';
is($x->input_focus, $right1->id, 'allowed focus up');
cmd 'focus up';
is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
-cmd '[id="' . $diff_ws->id . '"] focus';
-is($x->input_focus, $right2->id, 'prevented focus change to different ws');
-
################################################################################
# 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 child';
is($x->input_focus, $right2->id, 'bottom right window focused again');
-cmd '[id="' . $left->id . '"] focus';
-is($x->input_focus, $right2->id, 'prevented focus change to left window');
-
cmd 'focus up';
is($x->input_focus, $right1->id, 'allowed focus up');
cmd "move to workspace prev";
verify_move(2, 'prevented move to workspace by position');
-# TODO: Tests for "move to output" and "move workspace to output".
+################################################################################
+# Ensure it's possible to focus a window using the focus command despite
+# fullscreen window blocking it. Fullscreen window should lose its fullscreen
+# mode.
+################################################################################
+
+# 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');
+is_num_fullscreen($tmp, 1, '1 fullscreen window');
+
+cmd '[id="'. $first->id .'"] focus';
+sync_with_i3;
+
+is($x->input_focus, $first->id, 'correctly focused using id');
+is_num_fullscreen($tmp, 0, 'no fullscreen windows');
+
+# first floating, second tiling, focus using 'focus floating'
+kill_all_windows;
+
+$tmp = fresh_workspace;
+$first = open_floating_window;
+$second = open_window;
+cmd 'fullscreen';
+is($x->input_focus, $second->id, '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');
+is_num_fullscreen($tmp, 0, 'no fullscreen windows');
+
+# first tiling, second floating, focus using 'focus tiling'
+kill_all_windows;
+
+$tmp = fresh_workspace;
+$first = open_window;
+$second = open_floating_window;
+cmd 'fullscreen';
+is($x->input_focus, $second->id, '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');
+is_num_fullscreen($tmp, 0, 'no fullscreen 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');
+is_num_fullscreen($tmp2, 1, '1 fullscreen window');
+
+cmd '[id="'. $first->id .'"] focus';
+sync_with_i3;
+
+is($x->input_focus, $first->id, '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');
+
+################################################################################
+# 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');
+is_num_fullscreen($tmp2, 1, '1 fullscreen window');
+
+cmd '[id="'. $first->id .'"] focus';
+sync_with_i3;
+
+is($x->input_focus, $first->id, 'correctly focused using focus id');
+is_num_fullscreen($tmp2, 0, 'no fullscreen windows');
+
+
+# TODO: Tests for "move to output" and "move workspace to output".
done_testing;
sub open_special {
my %args = @_;
$args{name} //= 'special window';
+ $args{wm_class} //= 'special';
# We use dont_map because i3 will not map the window on the current
# workspace. Thus, open_window would time out in wait_for_map (2 seconds).
my $window = open_window(
%args,
- wm_class => 'special',
dont_map => 1,
);
$window->map;
return $window;
}
+sub test_workspace_assignment {
+ my $target_ws = "@_";
+
+ # initialize the target workspace, then go to a fresh one
+ ok(!($target_ws ~~ @{get_workspace_names()}), "$target_ws does not exist yet");
+ cmd "workspace $target_ws";
+ cmp_ok(@{get_ws_content($target_ws)}, '==', 0, "no containers on $target_ws yet");
+ cmd 'open';
+ cmp_ok(@{get_ws_content($target_ws)}, '==', 1, "one container on $target_ws");
+ my $tmp = fresh_workspace;
+
+ ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+ ok($target_ws ~~ @{get_workspace_names()}, "$target_ws does not exist yet");
+
+ # We use sync_with_i3 instead of wait_for_map here because i3 will not actually
+ # map the window -- it will be assigned to a different workspace and will only
+ # be mapped once you switch to that workspace
+ my $window = open_special;
+ sync_with_i3;
+
+ ok(@{get_ws_content($tmp)} == 0, 'still no containers');
+ ok(@{get_ws_content($target_ws)} == 2, "two containers on $target_ws");
+
+ return $window
+}
+
#####################################################################
# start a window and see that it does not get assigned with an empty config
#####################################################################
exit_gracefully($pid);
#####################################################################
-# start a window and see that it gets assigned to a workspace which has content
-# already, next to the existing node.
+# start a window and see that it gets assigned to a formerly unused
+# numbered workspace
#####################################################################
-$pid = launch_with_config($config);
+my $config_numbered = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign [class="special"] → workspace number 2
+EOT
+
+$pid = launch_with_config($config_numbered);
-# initialize the target workspace, then go to a fresh one
-ok(!("targetws" ~~ @{get_workspace_names()}), 'targetws does not exist yet');
-cmd 'workspace targetws';
-cmp_ok(@{get_ws_content('targetws')}, '==', 0, 'no containers on targetws yet');
-cmd 'open';
-cmp_ok(@{get_ws_content('targetws')}, '==', 1, 'one container on targetws');
$tmp = fresh_workspace;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-ok("targetws" ~~ @{get_workspace_names()}, 'targetws does not exist yet');
-
+$workspaces = get_workspace_names;
+ok(!("2" ~~ @{$workspaces}), 'workspace number 2 does not exist yet');
-# We use sync_with_i3 instead of wait_for_map here because i3 will not actually
-# map the window -- it will be assigned to a different workspace and will only
-# be mapped once you switch to that workspace
-$window = open_special(dont_map => 1);
-$window->map;
+$window = open_special;
sync_with_i3;
ok(@{get_ws_content($tmp)} == 0, 'still no containers');
-ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws');
+ok("2" ~~ @{get_workspace_names()}, 'workspace number 2 exists');
+
+$window->destroy;
+
+exit_gracefully($pid);
+
+#####################################################################
+# start a window and see that it gets assigned to a numbered
+# workspace which has content already, next to the existing node.
+#####################################################################
+
+$pid = launch_with_config($config_numbered);
+
+$window = test_workspace_assignment("2");
+$window->destroy;
+
+exit_gracefully($pid);
+
+#####################################################################
+# start a window and see that it gets assigned to a numbered workspace with
+# a name which has content already, next to the existing node.
+#####################################################################
+
+$pid = launch_with_config($config_numbered);
+
+cmd 'workspace 2'; # Make sure that we are not testing for "2" again.
+$window = test_workspace_assignment("2: targetws");
+$window->destroy;
+
+exit_gracefully($pid);
+
+#####################################################################
+# start a window and see that it gets assigned to a workspace which
+# has content already, next to the existing node.
+#####################################################################
+
+$pid = launch_with_config($config);
+
+test_workspace_assignment("targetws");
exit_gracefully($pid);
ok(@{$content->{nodes}} == 0, 'no tiling cons');
ok(@{$content->{floating_nodes}} == 1, 'one floating con');
-$window->destroy;
+kill_all_windows;
+exit_gracefully($pid);
+
+#####################################################################
+# test assignments to named outputs
+#####################################################################
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
+
+workspace ws-0 output fake-0
+workspace ws-1 output fake-1
+workspace ws-2 output fake-2
+workspace ws-3 output fake-3
+
+assign [class="special-0"] → output fake-0
+assign [class="special-1"] → output fake-1
+assign [class="special-2"] → output fake-2
+assign [class="special-3"] → output fake-3
+assign [class="special-4"] → output invalid
+
+EOT
+
+$pid = launch_with_config($config);
+
+sub open_in_output {
+ my ($num, $expected_count) = @_;
+ my $ws = "ws-$num";
+ my $class = "special-$num";
+ my $output = "fake-$num";
+
+ is_num_children($ws, $expected_count - 1,
+ "before: " . ($expected_count - 1) . " containers on output $output");
+ $window = open_special(wm_class => $class);
+ sync_with_i3;
+ is_num_children($ws, $expected_count,
+ "after: $expected_count containers on output $output");
+}
+
+cmd "workspace ws-0";
+open_in_output(0, 1);
+my $focused = $x->input_focus;
+
+open_in_output(1, 1);
+is($x->input_focus, $focused, 'focus remains on output fake-0');
+
+open_in_output(2, 1);
+is($x->input_focus, $focused, 'focus remains on output fake-0');
+
+for my $i (1 .. 5){
+ open_in_output(3, $i);
+ is($x->input_focus, $focused, 'focus remains on output fake-0');
+}
+
+# Check invalid output
+$tmp = fresh_workspace;
+open_special(wm_class => "special-4");
+sync_with_i3;
+is_num_children($tmp, 1, 'window assigned to invalid output opened in current workspace');
+open_special(wm_class => "special-3");
+sync_with_i3;
+is_num_children($tmp, 1, 'but window assigned to valid output did not');
+
+kill_all_windows;
+exit_gracefully($pid);
+
+#####################################################################
+# Test assignments to outputs with relative names
+#####################################################################
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
+
+workspace left-top output fake-0
+workspace right-top output fake-1
+workspace right-bottom output fake-2
+workspace left-bottom output fake-3
+
+assign [class="current"] → output current
+assign [class="left"] → output left
+assign [class="right"] → output right
+assign [class="up"] → output up
+assign [class="down"] → output down
+EOT
+
+$pid = launch_with_config($config);
+
+cmd 'workspace left-top';
+
+is_num_children('left-top', 0, 'no childreon on left-top');
+for my $i (1 .. 5){
+ open_special(wm_class => 'current');
+}
+sync_with_i3;
+is_num_children('left-top', 5, 'windows opened in current workspace');
+
+is_num_children('right-top', 0, 'no children on right-top');
+open_special(wm_class => 'right');
+sync_with_i3;
+is_num_children('right-top', 1, 'one child on right-top');
+
+is_num_children('left-bottom', 0, 'no children on left-bottom');
+open_special(wm_class => 'down');
+sync_with_i3;
+is_num_children('left-bottom', 1, 'one child on left-bottom');
+
+cmd 'workspace right-bottom';
+
+open_special(wm_class => 'up');
+sync_with_i3;
+is_num_children('right-top', 2, 'two children on right-top');
+
+open_special(wm_class => 'left');
+sync_with_i3;
+is_num_children('left-bottom', 2, 'two children on left-bottom');
+kill_all_windows;
exit_gracefully($pid);
#####################################################################
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
my @docked = get_dock_clients;
-is(@docked, 0, 'one dock client yet');
+is(@docked, 0, 'no dock client yet');
$window = open_special(
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed');
+exit_gracefully($pid);
+
+#####################################################################
+# 16: Check that the command 'layout toggle split' works regardless
+# of what layout we're using.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+workspace_layout default
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+my @layouts = ('splith', 'splitv', 'tabbed', 'stacked');
+my $first_layout;
+
+foreach $first_layout (@layouts) {
+ cmd 'layout ' . $first_layout;
+ $first = open_window;
+ $second = open_window;
+ cmd 'layout toggle split';
+ @content = @{get_ws_content($tmp)};
+ if ($first_layout eq 'splith') {
+ is($content[0]->{layout}, 'splitv', 'layout toggles to splitv');
+ } else {
+ is($content[0]->{layout}, 'splith', 'layout toggles to splith');
+ }
+
+ cmd '[id="' . $first->id . '"] kill';
+ cmd '[id="' . $second->id . '"] kill';
+ sync_with_i3;
+}
exit_gracefully($pid);
+#####################################################################
+# 17: Check about setting a new layout.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+workspace_layout default
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+my $second_layout;
+
+foreach $first_layout (@layouts) {
+ foreach $second_layout (@layouts) {
+ cmd 'layout ' . $first_layout;
+ $first = open_window;
+ $second = open_window;
+ cmd 'layout ' . $second_layout;
+ @content = @{get_ws_content($tmp)};
+ is($content[0]->{layout}, $second_layout, 'layout changes to ' . $second_layout);
+
+ cmd '[id="' . $first->id . '"] kill';
+ cmd '[id="' . $second->id . '"] kill';
+ sync_with_i3;
+ }
+}
+
done_testing;
is(launch_get_border($config), 'none', 'no border');
+#####################################################################
+# test that variables with longer name than value don't crash i3 with
+# v3 to v4 conversion.
+# See: #3076
+#####################################################################
+
+$config = <<'EOT';
+set $var a
+EOT
+my $pid = launch_with_config($config);
+does_i3_live;
+exit_gracefully($pid);
done_testing;
################################################################################
clear_scratchpad;
-my $ws = fresh_workspace;
+$ws = fresh_workspace;
open_window;
my $scratch = get_focused($ws);
# 7: check floating_maximum_size with cmd_size
################################################################################
-my $config = <<EOT;
+$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
$pid = launch_with_config($config);
-my $window = open_floating_window(rect => [ 0, 0, 90, 80 ]);
+$window = open_floating_window(rect => [ 0, 0, 90, 80 ]);
cmd 'border none';
cmd 'resize set 101 91';
sync_with_i3;
-my $rect = $window->rect;
+$rect = $window->rect;
is($rect->{width}, 100, 'width did not exceed maximum width');
is($rect->{height}, 90, 'height did not exceed maximum height');
sub send_net_active_window {
my ($id, $source) = @_;
- $source = ($source eq 'pager' ? 2 : 0);
+ $source = (((defined $source) && ($source eq 'pager')) ? 2 : 0);
my $msg = pack "CCSLLLLLLL",
X11::XCB::CLIENT_MESSAGE, # response_type
# is received.
################################################################################
-my $scratch = open_window;
+$scratch = open_window;
is($x->input_focus, $scratch->id, 'to-scratchpad window has focus');
}
EOT
-my $i3 = i3(get_socket_path(0));
-$i3->connect->recv;
+my @events = events_for(
+ sub { cmd 'mode "m1"' },
+ 'mode');
-my $cv = AnyEvent->condvar;
-
-$i3->subscribe({
- mode => sub {
- my ($event) = @_;
- $cv->send($event->{change} eq 'm1');
- }
-})->recv;
-
-cmd 'mode "m1"';
-
-# Timeout after 0.5s
-my $t;
-$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); });
-
-ok($cv->recv, 'Mode event received');
+my @changes = map { $_->{change} } @events;
+is_deeply(\@changes, [ 'm1' ], 'Mode event received');
done_testing;
$config = <<'EOT';
assign [class="^Chrome"] 4
+assign [class="^Chrome"] workspace number 3
assign [class="^Chrome"] named workspace
assign [class="^Chrome"] "quoted named workspace"
assign [class="^Chrome"] → "quoted named workspace"
$expected = <<'EOT';
cfg_criteria_add(class, ^Chrome)
-cfg_assign(4)
+cfg_assign(4, 0)
cfg_criteria_add(class, ^Chrome)
-cfg_assign(named workspace)
+cfg_assign(3, 1)
cfg_criteria_add(class, ^Chrome)
-cfg_assign(quoted named workspace)
+cfg_assign(named workspace, 0)
cfg_criteria_add(class, ^Chrome)
-cfg_assign(quoted named workspace)
+cfg_assign(quoted named workspace, 0)
+cfg_criteria_add(class, ^Chrome)
+cfg_assign(quoted named workspace, 0)
EOT
is(parser_calls($config),
$config = <<'EOT';
floating_minimum_size 80x55
-floating_minimum_size 80 x 55
+floating_minimum_size 80 x 55
floating_maximum_size 73 x 10
EOT
################################################################################
$config = <<'EOT';
-workspace "3" output DP-1
-workspace "3" output VGA-1
+workspace "3" output DP-1
+workspace "3" output VGA-1
EOT
$expected = <<'EOT';
new_window 1pixel
new_window normal
new_window none
+default_border 1pixel
+default_border normal
+default_border none
new_float 1pixel
new_float normal
new_float none
+default_floating_border 1pixel
+default_floating_border normal
+default_floating_border none
EOT
$expected = <<'EOT';
-cfg_new_window(new_window, 1pixel, -1)
-cfg_new_window(new_window, normal, 2)
-cfg_new_window(new_window, none, -1)
-cfg_new_window(new_float, 1pixel, -1)
-cfg_new_window(new_float, normal, 2)
-cfg_new_window(new_float, none, -1)
-EOT
+cfg_default_border(new_window, 1pixel, -1)
+cfg_default_border(new_window, normal, 2)
+cfg_default_border(new_window, none, -1)
+cfg_default_border(default_border, 1pixel, -1)
+cfg_default_border(default_border, normal, 2)
+cfg_default_border(default_border, none, -1)
+cfg_default_border(new_float, 1pixel, -1)
+cfg_default_border(new_float, normal, 2)
+cfg_default_border(new_float, none, -1)
+cfg_default_border(default_floating_border, 1pixel, -1)
+cfg_default_border(default_floating_border, normal, 2)
+cfg_default_border(default_floating_border, none, -1)
+EOT
+
+# TODO: are there no tests for "border pixel 1" etc?
is(parser_calls($config),
$expected,
floating_modifier
default_orientation
workspace_layout
+ default_border
new_window
+ default_floating_border
new_float
hide_edge_borders
for_window
no_focus
focus_follows_mouse
mouse_warping
+ focus_wrapping
force_focus_wrapping
force_xinerama
force-xinerama
use i3test;
-SKIP: {
-
- skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
-
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-################################
-# Window event
-################################
-
-# Events
-
my $new = AnyEvent->condvar;
my $focus = AnyEvent->condvar;
-$i3->subscribe({
- window => sub {
- my ($event) = @_;
- if ($event->{change} eq 'new') {
- $new->send($event);
- } elsif ($event->{change} eq 'focus') {
- $focus->send($event);
- }
- }
-})->recv;
-
-open_window;
-
-my $t;
-$t = AnyEvent->timer(
- after => 0.5,
- cb => sub {
- $new->send(0);
- $focus->send(0);
- }
-);
-is($new->recv->{container}->{focused}, 0, 'Window "new" event received');
-is($focus->recv->{container}->{focused}, 1, 'Window "focus" event received');
+my @events = events_for(
+ sub { open_window },
+ 'window');
-}
+is(scalar @events, 2, 'Received 2 events');
+is($events[0]->{container}->{focused}, 0, 'Window "new" event received');
+is($events[1]->{container}->{focused}, 1, 'Window "focus" event received');
done_testing;
my $tmp = fresh_workspace;
-sub fullscreen_windows {
- my $ws = $tmp;
- $ws = shift if @_;
-
- my $nodes = scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)->[0]->{nodes}};
- my $cons = scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)};
- return $nodes + $cons;
-}
-
##########################################################################################
# map two windows in one container, fullscreen one of them and then move it to scratchpad
##########################################################################################
cmd 'fullscreen';
# see if the window really is in fullscreen mode
-is(fullscreen_windows(), 1, 'amount of fullscreen windows after enabling fullscreen');
+is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after enabling fullscreen');
# move window to scratchpad
cmd 'move scratchpad';
cmd 'floating toggle';
# see if no window is in fullscreen mode
-is(fullscreen_windows(), 0, 'amount of fullscreen windows after showing previously fullscreened scratchpad window');
+is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after showing previously fullscreened scratchpad window');
########################################################################################
# move a window to scratchpad, focus parent container, make it fullscreen, focus a child
cmd 'focus child';
# see if the window really is in fullscreen mode
-is(fullscreen_windows(), 1, 'amount of fullscreen windows after enabling fullscreen on parent');
+is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after enabling fullscreen on parent');
##########################################################################
# show a scratchpad window; no window should be in fullscreen mode anymore
cmd 'scratchpad show';
# see if no window is in fullscreen mode
-is(fullscreen_windows(), 0, 'amount of fullscreen windows after showing a scratchpad window while a parent container was in fullscreen mode');
+is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after showing a scratchpad window while a parent container was in fullscreen mode');
done_testing;
# 3: change size of the shared memory log buffer and verify old content is gone
################################################################################
-cmd 'shmlog ' . (23 * 1024 * 1024);
+cmd 'shmlog ' . (1 * 1024 * 1024);
run [ 'i3-dump-log' ],
'>', \$stdout,
use i3test;
-SKIP: {
-
- skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
-
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
################################
# Window focus event
################################
my $win1 = open_window;
my $win2 = open_window;
-my $focus = AnyEvent->condvar;
-
-$i3->subscribe({
- window => sub {
- my ($event) = @_;
- $focus->send($event);
- }
-})->recv;
-
-my $t;
-$t = AnyEvent->timer(
- after => 0.5,
- cb => sub {
- $focus->send(0);
- }
-);
-
# ensure the rightmost window contains input focus
-$i3->command('[id="' . $win2->id . '"] focus')->recv;
+cmd '[id="' . $win2->id . '"] focus';
is($x->input_focus, $win2->id, "Window 2 focused");
-cmd 'focus left';
-my $event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($focus->recv->{container}->{name}, 'Window 1', 'Window 1 focused');
-
-$focus = AnyEvent->condvar;
-cmd 'focus left';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 0', 'Window 0 focused');
-
-$focus = AnyEvent->condvar;
-cmd 'focus right';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 1', 'Window 1 focused');
+sub focus_subtest {
+ my ($cmd, $name) = @_;
-$focus = AnyEvent->condvar;
-cmd 'focus right';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 2', 'Window 2 focused');
+ my $focus = AnyEvent->condvar;
-$focus = AnyEvent->condvar;
-cmd 'focus right';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 0', 'Window 0 focused');
-
-$focus = AnyEvent->condvar;
-cmd 'focus left';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 2', 'Window 2 focused');
+ my @events = events_for(
+ sub { cmd $cmd },
+ 'window');
+ is(scalar @events, 1, 'Received 1 event');
+ is($events[0]->{change}, 'focus', 'Focus event received');
+ is($events[0]->{container}->{name}, $name, "$name focused");
}
+subtest 'focus left (1)', \&focus_subtest, 'focus left', $win1->name;
+subtest 'focus left (2)', \&focus_subtest, 'focus left', $win0->name;
+subtest 'focus right (1)', \&focus_subtest, 'focus right', $win1->name;
+subtest 'focus right (2)', \&focus_subtest, 'focus right', $win2->name;
+subtest 'focus right (3)', \&focus_subtest, 'focus right', $win0->name;
+subtest 'focus left', \&focus_subtest, 'focus left', $win2->name;
+
done_testing;
use i3test;
-SKIP: {
-
- skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
-
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-################################
-# Window title event
-################################
-
my $window = open_window(name => 'Window 0');
-my $title = AnyEvent->condvar;
-
-$i3->subscribe({
- window => sub {
- my ($event) = @_;
- $title->send($event);
- }
-})->recv;
-
-$window->name('New Window Title');
-
-my $t;
-$t = AnyEvent->timer(
- after => 0.5,
- cb => sub {
- $title->send(0);
- }
-);
-
-my $event = $title->recv;
-is($event->{change}, 'title', 'Window title change event received');
-is($event->{container}->{name}, 'New Window Title', 'Window title changed');
+my @events = events_for(
+ sub {
+ $window->name('New Window Title');
+ sync_with_i3;
+ },
+ 'window');
-}
+is(scalar @events, 1, 'Received 1 event');
+is($events[0]->{change}, 'title', 'Window title change event received');
+is($events[0]->{container}->{name}, 'New Window Title', 'Window title changed');
done_testing;
# Bug still in: 4.7.2-135-g7deb23c
use i3test;
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
+open_window;
-my $cv;
-my $t;
+sub fullscreen_subtest {
+ my ($want) = @_;
+ my @events = events_for(
+ sub { cmd 'fullscreen' },
+ 'window');
-sub reset_test {
- $cv = AE::cv;
- $t = AE::timer(0.5, 0, sub { $cv->send(0); });
+ is(scalar @events, 1, 'Received 1 event');
+ is($events[0]->{container}->{fullscreen_mode}, $want, "fullscreen_mode now $want");
}
-reset_test;
-
-$i3->subscribe({
- window => sub {
- my ($e) = @_;
- if ($e->{change} eq 'fullscreen_mode') {
- $cv->send($e->{container});
- }
- },
- })->recv;
-
-my $window = open_window;
-
-cmd 'fullscreen';
-my $con = $cv->recv;
-
-ok($con, 'got fullscreen window event (on)');
-is($con->{fullscreen_mode}, 1, 'window is fullscreen');
-
-reset_test;
-cmd 'fullscreen';
-$con = $cv->recv;
-
-ok($con, 'got fullscreen window event (off)');
-is($con->{fullscreen_mode}, 0, 'window is not fullscreen');
+subtest 'fullscreen on', \&fullscreen_subtest, 1;
+subtest 'fullscreen off', \&fullscreen_subtest, 0;
done_testing;
#
use i3test;
-SKIP: {
-
- skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
-
################################################################################
-# check that the workspace empty event is send upon workspace switch when the
+# check that the workspace empty event is sent upon workspace switch when the
# old workspace is empty
################################################################################
subtest 'Workspace empty event upon switch', sub {
cmd '[id="' . $w1->id . '"] kill';
my $cond = AnyEvent->condvar;
- my $client = i3(get_socket_path(0));
- $client->connect()->recv;
- $client->subscribe({
- workspace => sub {
- my ($event) = @_;
- $cond->send($event);
- }
- })->recv;
-
- cmd "workspace $ws2";
-
- sync_with_i3;
+ my @events = events_for(
+ sub { cmd "workspace $ws2" },
+ 'workspace');
- my $event = $cond->recv;
- is($event->{change}, 'empty', '"Empty" event received upon workspace switch');
- is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
+ is(scalar @events, 2, 'Received 2 event');
+ is($events[1]->{change}, 'empty', '"Empty" event received upon workspace switch');
+ is($events[1]->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
};
################################################################################
-# check that no workspace empty event is send upon workspace switch if the
+# check that no workspace empty event is sent upon workspace switch if the
# workspace is not empty
################################################################################
subtest 'No workspace empty event', sub {
my $ws1 = fresh_workspace;
my $w1 = open_window();
- my @events;
- my $cond = AnyEvent->condvar;
- my $client = i3(get_socket_path(0));
- $client->connect()->recv;
- $client->subscribe({
- workspace => sub {
- my ($event) = @_;
- push @events, $event;
- }
- })->recv;
-
- # Wait for the workspace event on a new connection. Events will be delivered
- # to older connections earlier, so by the time it arrives here, it should be
- # in @events already.
- my $ws_event_block_conn = i3(get_socket_path(0));
- $ws_event_block_conn->connect()->recv;
- $ws_event_block_conn->subscribe({ workspace => sub { $cond->send(1) }});
-
- cmd "workspace $ws2";
+ my @events = events_for(
+ sub { cmd "workspace $ws2" },
+ 'workspace');
- sync_with_i3;
-
- my @expected_events = grep { $_->{change} eq 'focus' } @events;
- my @empty_events = grep { $_->{change} eq 'empty' } @events;
- is(@expected_events, 1, '"Focus" event received');
- is(@empty_events, 0, 'No "empty" events received');
+ is(scalar @events, 1, 'Received 1 event');
+ is($events[0]->{change}, 'focus', 'Event change is "focus"');
};
################################################################################
-# check that workspace empty event is send when the last window has been closed
+# check that workspace empty event is sent when the last window has been closed
# on invisible workspace
################################################################################
subtest 'Workspace empty event upon window close', sub {
my $ws2 = fresh_workspace;
my $w2 = open_window();
- my $cond = AnyEvent->condvar;
- my $client = i3(get_socket_path(0));
- $client->connect()->recv;
- $client->subscribe({
- workspace => sub {
- my ($event) = @_;
- $cond->send($event);
- }
- })->recv;
-
- cmd '[id="' . $w1->id . '"] kill';
-
- sync_with_i3;
+ my @events = events_for(
+ sub {
+ $w1->unmap;
+ sync_with_i3;
+ },
+ 'workspace');
- my $event = $cond->recv;
- is($event->{change}, 'empty', '"Empty" event received upon window close');
- is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
+ is(scalar @events, 1, 'Received 1 event');
+ is($events[0]->{change}, 'empty', '"Empty" event received upon window close');
+ is($events[0]->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
};
-}
-
done_testing;
# Bug still in: 4.8-7-gf4a8253
use i3test;
-my $i3 = i3(get_socket_path());
-$i3->connect->recv;
+sub floating_subtest {
+ my ($win, $cmd, $want) = @_;
-my $cv = AnyEvent->condvar;
+ my @events = events_for(
+ sub { cmd $cmd },
+ 'window');
-$i3->subscribe({
- window => sub {
- my ($event) = @_;
- $cv->send($event) if $event->{change} eq 'floating';
- }
- })->recv;
-
-my $t;
-$t = AnyEvent->timer(
- after => 0.5,
- cb => sub {
- $cv->send(0);
- }
-);
+ my @floating = grep { $_->{change} eq 'floating' } @events;
+ is(scalar @floating, 1, 'Received 1 floating event');
+ is($floating[0]->{container}->{window}, $win->{id}, "window id matches");
+ is($floating[0]->{container}->{floating}, $want, "floating is $want");
+}
my $win = open_window();
-cmd '[id="' . $win->{id} . '"] floating enable';
-my $e = $cv->recv;
-
-isnt($e, 0, 'floating a container should send an ipc window event');
-is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window');
-is($e->{container}->{floating}, 'user_on', 'the container should be floating');
-
-$cv = AnyEvent->condvar;
-cmd '[id="' . $win->{id} . '"] floating disable';
-$e = $cv->recv;
-
-isnt($e, 0, 'disabling floating on a container should send an ipc window event');
-is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window');
-is($e->{container}->{floating}, 'user_off', 'the container should not be floating');
+subtest 'floating enable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating enable', 'user_on';
+subtest 'floating disable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating disable', 'user_off';
done_testing;
skip 'xdotool is required to test the binding event. `[apt-get install|pacman -S] xdotool`', 1 if $?;
- skip "AnyEvent::I3 too old (need >= 0.16)", 1 if $AnyEvent::I3::VERSION < 0.16;
-
my $pid = launch_with_config($config);
- my $i3 = i3(get_socket_path());
- $i3->connect->recv;
-
- my $cv = AE::cv;
- my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
-
- $i3->subscribe({
- binding => sub {
- $cv->send(shift);
- }
- })->recv;
-
- qx(xdotool key $binding_symbol);
-
- my $e = $cv->recv;
-
- does_i3_live;
+ my $cv = AnyEvent->condvar;
- diag "Event:\n", Dumper($e);
+ my @events = events_for(
+ sub {
+ # TODO: this is still flaky: we need to synchronize every X11
+ # connection with i3. Move to XTEST and synchronize that connection.
+ qx(xdotool key $binding_symbol);
+ },
+ 'binding');
- ok($e,
- 'the binding event should emit when user input triggers an i3 binding event');
+ is(scalar @events, 1, 'Received 1 event');
- is($e->{change}, 'run',
+ is($events[0]->{change}, 'run',
'the `change` field should indicate this binding has run');
- ok($e->{binding},
+ ok($events[0]->{binding},
'the `binding` field should be a hash that contains information about the binding');
- is($e->{binding}->{input_type}, 'keyboard',
+ is($events[0]->{binding}->{input_type}, 'keyboard',
'the input_type field should be the input type of the binding (keyboard or mouse)');
note 'the `mods` field should contain the symbols for the modifiers of the binding';
foreach (@mods) {
- ok(grep(/$_/i, @{$e->{binding}->{mods}}), "`mods` contains the modifier $_");
+ ok(grep(/$_/i, @{$events[0]->{binding}->{mods}}), "`mods` contains the modifier $_");
}
- is($e->{binding}->{command}, $command,
+ is($events[0]->{binding}->{command}, $command,
'the `command` field should contain the command the binding ran');
- is($e->{binding}->{input_code}, 0,
+ is($events[0]->{binding}->{input_code}, 0,
'the input_code should be the specified code if the key was bound with bindcode, and otherwise zero');
exit_gracefully($pid);
$pid = launch_with_config($config);
-my $ws = fresh_workspace;
+$ws = fresh_workspace;
$first = open_window;
$second = open_window;
$pid = launch_with_config($config);
-my $ws = fresh_workspace;
+$ws = fresh_workspace;
$first = open_window;
$second = open_window;
# Test behavior of "resize <width> <height>" command.
# Ticket: #1727
# Bug still in: 4.10.2-1-gc0dbc5d
-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 1333x999+0+0
+workspace ws output fake-0
+EOT
################################################################################
# Check that setting floating windows size works
cmp_ok($content[0]->{rect}->{width}, '==', 100, 'width changed to 100 px');
cmp_ok($content[0]->{rect}->{height}, '==', 250, 'height changed to 250 px');
+################################################################################
+# Same but with ppt instead of px
+################################################################################
+
+kill_all_windows;
+$tmp = 'ws';
+cmd "workspace $tmp";
+open_floating_window;
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+is(@content, 1, 'one floating node on this ws');
+
+$oldrect = $content[0]->{rect};
+
+cmd 'resize set 33 ppt 20 ppt';
+my $expected_width = int(0.33 * 1333);
+my $expected_height = int(0.2 * 999);
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
+cmp_ok($content[0]->{rect}->{width}, '!=', $oldrect->{width}, 'width changed');
+cmp_ok($content[0]->{rect}->{height}, '!=', $oldrect->{width}, 'height changed');
+cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px");
+cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px");
+
+################################################################################
+# Mix ppt and px in a single resize set command
+################################################################################
+
+cmd 'resize set 44 ppt 111 px';
+$expected_width = int(0.44 * 1333);
+$expected_height = 111;
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
+cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px");
+cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px");
+
+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");
+
done_testing;
skip "setxkbmap not found", 1 if
system(q|setxkbmap -print >/dev/null|) != 0;
-start_binding_capture;
-
system(q|setxkbmap us,ru -option grp:alt_shift_toggle|);
is(listen_for_binding(
sub {
xtest_key_press(107);
xtest_key_release(107);
+ xtest_sync_with_i3;
},
),
'Print',
xtest_key_press(36); # Return
xtest_key_release(36); # Return
xtest_key_release(133); # Super_L
+ xtest_sync_with_i3;
},
),
'Mod4+Return',
sub {
xtest_key_press(107);
xtest_key_release(107);
+ xtest_sync_with_i3;
},
),
'Print',
xtest_key_press(36); # Return
xtest_key_release(36); # Return
xtest_key_release(133); # Super_L
+ xtest_sync_with_i3;
},
),
'Mod4+Return',
'triggered the "Mod4+Return" keybinding');
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events');
-
# Disable the grp:alt_shift_toggle option, as we use Alt+Shift in other testcases.
system(q|setxkbmap us -option|);
skip "libxcb-xkb too old (need >= 1.11)", 1 unless
ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11');
-start_binding_capture;
-
is(listen_for_binding(
sub {
xtest_key_press(107); # Print
xtest_key_release(107); # Print
+ xtest_sync_with_i3;
},
),
'Print',
xtest_key_press(107); # Print
xtest_key_release(107); # Print
xtest_key_release(37); # Control_L
+ xtest_sync_with_i3;
},
),
'Control+Print',
xtest_key_press(56); # b
xtest_key_release(56); # b
xtest_key_release(64); # Alt_L
+ xtest_sync_with_i3;
},
),
'Mod1+b',
xtest_key_release(56); # b
xtest_key_release(50); # Shift_L
xtest_key_release(64); # Alt_L
+ xtest_sync_with_i3;
},
),
'Mod1+Shift+b release',
'triggered the "Mod1+Shift+b" release keybinding');
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events');
-
}
done_testing;
cmd 'mode othermode';
-my $i3 = i3(get_socket_path(0));
-$i3->connect->recv;
+my @events = events_for(
+ sub { cmd 'reload' },
+ 'mode');
-my $cv = AnyEvent->condvar;
-$i3->subscribe({
- mode => sub {
- my ($event) = @_;
- $cv->send($event->{change} eq 'default');
- }
-})->recv;
-
-cmd 'reload';
-
-# Timeout after 0.5s
-my $t;
-$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); });
-
-ok($cv->recv, 'Mode event received');
+is(scalar @events, 1, 'Received 1 event');
+is($events[0]->{change}, 'default', 'change is "default"');
done_testing;
# Ticket: #2501
use i3test;
-my ($i3, $timer, $event, $mark);
+sub mark_subtest {
+ my ($cmd) = @_;
-$i3 = i3(get_socket_path());
-$i3->connect()->recv;
+ my @events = events_for(
+ sub { cmd $cmd },
+ 'window');
-$i3->subscribe({
- window => sub {
- my ($event) = @_;
- return unless defined $mark;
- return unless $event->{change} eq 'mark';
-
- $mark->send($event);
- }
-})->recv;
-
-$timer = AnyEvent->timer(
- after => 0.5,
- cb => sub {
- $mark->send(0);
- }
-);
+ my @mark = grep { $_->{change} eq 'mark' } @events;
+ is(scalar @mark, 1, 'Received 1 window::mark event');
+}
###############################################################################
# Marking a container triggers a 'mark' event.
fresh_workspace;
open_window;
-$mark = AnyEvent->condvar;
-cmd 'mark x';
-
-$event = $mark->recv;
-ok($event, 'window::mark event has been received');
+subtest 'mark', \&mark_subtest, 'mark x';
###############################################################################
# Unmarking a container triggers a 'mark' event.
open_window;
cmd 'mark x';
-$mark = AnyEvent->condvar;
-cmd 'unmark x';
-
-$event = $mark->recv;
-ok($event, 'window::mark event has been received');
+subtest 'unmark', \&mark_subtest, 'unmark x';
###############################################################################
my $i3 = i3(get_socket_path());
$i3->connect->recv;
-my $cv = AE::cv;
-my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
+my $cv = AnyEvent->condvar;
+my $timer = AnyEvent->timer(after => 0.5, interval => 0, cb => sub { $cv->send(0); });
my $last_config = $i3->get_config()->recv;
chomp($last_config->{config});
# Bug still in: 4.8-7-gf4a8253
use i3test;
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-my $cv;
-my $t;
-
-sub reset_test {
- $cv = AE::cv;
- $t = AE::timer(0.5, 0, sub { $cv->send(0); });
-}
-
-reset_test;
-
-$i3->subscribe({
- window => sub {
- my ($e) = @_;
- if ($e->{change} eq 'close') {
- $cv->send($e->{container});
- }
- },
- })->recv;
-
my $window = open_window;
-cmd 'kill';
-my $con = $cv->recv;
+my @events = events_for(
+ sub {
+ $window->unmap;
+ sync_with_i3;
+ },
+ 'window');
-ok($con, 'closing a window should send the window::close event');
-is($con->{window}, $window->{id}, 'the event should contain information about the window');
+my @close = grep { $_->{change} eq 'close' } @events;
+is(scalar @close, 1, 'Received 1 window::close event');
+is($close[0]->{container}->{window}, $window->{id}, 'the event should contain information about the window');
done_testing;
# Bug still in: 4.8-7-gf4a8253
use i3test;
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-my $cv;
-my $t;
-
-sub reset_test {
- $cv = AE::cv;
- $t = AE::timer(0.5, 0, sub { $cv->send(0); });
-}
-
-reset_test;
-
-$i3->subscribe({
- window => sub {
- my ($e) = @_;
- if ($e->{change} eq 'move') {
- $cv->send($e->{container});
- }
- },
- })->recv;
-
my $dummy_window = open_window;
my $window = open_window;
-cmd 'move right';
-my $con = $cv->recv;
-
-ok($con, 'moving a window should emit the window::move event');
-is($con->{window}, $window->{id}, 'the event should contain info about the window');
+sub move_subtest {
+ my ($cmd) = @_;
+ my $cv = AnyEvent->condvar;
+ my @events = events_for(
+ sub { cmd $cmd },
+ 'window');
-reset_test;
-
-cmd 'move to workspace ws_new';
-$con = $cv->recv;
+ my @move = grep { $_->{change} eq 'move' } @events;
+ is(scalar @move, 1, 'Received 1 window::move event');
+ is($move[0]->{container}->{window}, $window->{id}, 'window id matches');
+}
-ok($con, 'moving a window to a different workspace should emit the window::move event');
-is($con->{window}, $window->{id}, 'the event should contain info about the window');
+subtest 'move right', \&move_subtest, 'move right';
+subtest 'move to workspace', \&move_subtest, 'move to workspace ws_new';
done_testing;
#
use i3test;
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-
-force_display_urgency_hint 0ms
-EOT
-
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-my $cv;
-$i3->subscribe({
- window => sub {
- my ($event) = @_;
- $cv->send($event) if $event->{change} eq 'urgent';
- }
-})->recv;
-
-my $t;
-$t = AnyEvent->timer(
- after => 0.5,
- cb => sub {
- $cv->send(0);
- }
-);
-
-$cv = AnyEvent->condvar;
fresh_workspace;
my $win = open_window;
my $dummy_win = open_window;
-$win->add_hint('urgency');
-my $event = $cv->recv;
-
-isnt($event, 0, 'an urgent con should emit the window::urgent event');
-is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window');
-is($event->{container}->{urgent}, 1, 'the container should be urgent');
-
-$cv = AnyEvent->condvar;
-$win->delete_hint('urgency');
-$event = $cv->recv;
-
-isnt($event, 0, 'an urgent con should emit the window::urgent event');
-is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window');
-is($event->{container}->{urgent}, 0, 'the container should not be urgent');
+sub urgency_subtest {
+ my ($subscribecb, $win, $want) = @_;
+
+ my @events = events_for(
+ $subscribecb,
+ 'window');
+
+ my @urgent = grep { $_->{change} eq 'urgent' } @events;
+ is(scalar @urgent, 1, 'Received 1 window::urgent event');
+ is($urgent[0]->{container}->{window}, $win->{id}, "window id matches");
+ is($urgent[0]->{container}->{urgent}, $want, "urgent is $want");
+}
+
+subtest "urgency set", \&urgency_subtest,
+ sub {
+ $win->add_hint('urgency');
+ sync_with_i3;
+ },
+ $win,
+ 1;
+
+subtest "urgency unset", \&urgency_subtest,
+ sub {
+ $win->delete_hint('urgency');
+ sync_with_i3;
+ },
+ $win,
+ 0;
done_testing;
xtest_button_press(4, 50, 50);
xtest_button_release(4, 50, 50);
-sync_with_i3;
+xtest_sync_with_i3;
is(focused_ws(), 'special', 'the binding was triggered');
# Bug still in: 4.12-46-g2123888
use i3test;
-SKIP: {
- skip "AnyEvent::I3 too old (need >= 0.17)", 1 if $AnyEvent::I3::VERSION < 0.17;
+# We cannot use events_for in this test as we cannot send events after
+# issuing the restart/shutdown command.
my $i3 = i3(get_socket_path());
$i3->connect->recv;
-my $cv = AE::cv;
-my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
+my $cv = AnyEvent->condvar;
+my $timer = AnyEvent->timer(after => 0.5, interval => 0, cb => sub { $cv->send(0); });
$i3->subscribe({
shutdown => sub {
$i3 = i3(get_socket_path());
$i3->connect->recv;
-$cv = AE::cv;
-$timer = AE::timer 0.5, 0, sub { $cv->send(0); };
+$cv = AnyEvent->condvar;
+$timer = AnyEvent->timer(after => 0.5, interval => 0, cb => sub { $cv->send(0); });
$i3->subscribe({
shutdown => sub {
diag "Event:\n", Dumper($e);
ok($e, 'the shutdown event should emit when the ipc is exited by command');
is($e->{change}, 'exit', 'the `change` field should tell the reason for the shutdown');
-}
done_testing;
my $pid = launch_with_config($config);
-start_binding_capture;
-
is(listen_for_binding(
sub {
xtest_key_press(87); # KP_End
xtest_key_release(87); # KP_End
+ xtest_sync_with_i3;
},
),
'KP_End',
xtest_key_release(87); # KP_1
xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock
+ xtest_sync_with_i3;
},
),
'KP_1',
xtest_key_press(38); # a
xtest_key_release(38); # a
xtest_key_release(133); # Super_L
+ xtest_sync_with_i3;
},
),
'a',
xtest_key_release(133); # Super_L
xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock
+ xtest_sync_with_i3;
},
),
'a',
sub {
xtest_key_press(9); # Escape
xtest_key_release(9); # Escape
+ xtest_sync_with_i3;
},
),
'Escape',
xtest_key_release(9); # Escape
xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock
+ xtest_sync_with_i3;
},
),
'Escape',
xtest_key_press(9); # Escape
xtest_key_release(9); # Escape
xtest_key_release(50); # Shift_L
+ xtest_sync_with_i3;
},
),
'Shift+Escape',
xtest_key_release(50); # Shift_L
xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock
+ xtest_sync_with_i3;
},
),
'Shift+Escape',
xtest_key_release(24); # q
xtest_key_release(64); # Alt_L
xtest_key_release(50); # Shift_L
+ xtest_sync_with_i3;
},
),
'Mod1+Shift+q',
xtest_key_release(50); # Shift_L
xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock
+ xtest_sync_with_i3;
},
),
'Mod1+Shift+q',
sub {
xtest_key_press(39); # s
xtest_key_release(39); # s
+ xtest_sync_with_i3;
},
),
's',
xtest_key_release(39); # s
xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock
+ xtest_sync_with_i3;
},
),
's',
'triggered the "s" keybinding with Num_Lock');
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 12, 'Received exactly 12 binding events');
-
exit_gracefully($pid);
################################################################################
$pid = launch_with_config($config);
-start_binding_capture;
-
is(listen_for_binding(
sub {
xtest_key_press(133); # Super_L
xtest_key_release(133); # Super_L
+ xtest_sync_with_i3;
},
),
'Super_L',
xtest_key_release(133); # Super_L
xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock
+ xtest_sync_with_i3;
},
),
'Super_L',
xtest_key_press(36); # Return
xtest_key_release(36); # Return
xtest_key_release(133); # Super_L
+ xtest_sync_with_i3;
},
),
'Return',
xtest_key_release(133); # Super_L
xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock
+ xtest_sync_with_i3;
},
),
'Return',
'triggered the "Return" keybinding with Num_Lock');
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 16, 'Received exactly 16 binding events');
-
exit_gracefully($pid);
################################################################################
$pid = launch_with_config($config);
-start_binding_capture;
-
is(listen_for_binding(
sub {
xtest_key_press(87); # KP_End
xtest_key_release(87); # KP_End
+ xtest_sync_with_i3;
},
),
'KP_End',
sub {
xtest_key_press(88); # KP_Down
xtest_key_release(88); # KP_Down
+ xtest_sync_with_i3;
},
),
'KP_Down',
'triggered the "KP_Down" keybinding');
-is(listen_for_binding(
+my @unexpected = events_for(
sub {
xtest_key_press(77); # enable Num_Lock
xtest_key_release(77); # enable Num_Lock
xtest_key_release(87); # KP_1
xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock
+ xtest_sync_with_i3;
},
- ),
- 'timeout',
- 'Did not trigger the KP_End keybinding with KP_1');
+ 'binding');
+is(scalar @unexpected, 0, 'Did not trigger the KP_End keybinding with KP_1');
-is(listen_for_binding(
+my @unexpected2 = events_for(
sub {
xtest_key_press(77); # enable Num_Lock
xtest_key_release(77); # enable Num_Lock
xtest_key_release(88); # KP_2
xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock
+ xtest_sync_with_i3;
},
- ),
- 'timeout',
- 'Did not trigger the KP_Down keybinding with KP_2');
+ 'binding');
-# TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2.
+is(scalar @unexpected2, 0, 'Did not trigger the KP_Down keybinding with KP_2');
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 18, 'Received exactly 18 binding events');
+# TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2.
exit_gracefully($pid);
my $win = open_window;
-start_binding_capture;
-
is(listen_for_binding(
sub {
xtest_key_press(77); # enable Num_Lock
xtest_button_release(4, 50, 50);
xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock
+ xtest_sync_with_i3;
},
),
'button4',
is(listen_for_binding(
sub {
- xtest_button_press(4, 50, 50);
- xtest_button_release(4, 50, 50);
+ xtest_button_press(4, 50, 50);
+ xtest_button_release(4, 50, 50);
+ xtest_sync_with_i3;
},
),
'button4',
EOT
my ($ws, $ws1, $ws2, $ws3);
-my ($nodes, $expected_focus, $A, $B, $F);
+my ($node, $nodes, $expected_focus, $A, $B, $F);
my ($result);
+my @fullscreen_permutations = ([], ["A"], ["B"], ["A", "B"]);
my @urgent;
###############################################################################
# | Y | B | Focus Stacks:
# +---+---+ H2: B, Y
###############################################################################
+for my $fullscreen (@fullscreen_permutations){
+ $ws1 = fresh_workspace;
+ $A = open_window(wm_class => 'mark_A');
+ $expected_focus = get_focused($ws1);
+ open_window;
+ cmd 'focus left';
+
+ $ws2 = fresh_workspace;
+ open_window;
+ $B = open_window(wm_class => 'mark_B');
+
+ my $A_fullscreen = "A" ~~ @$fullscreen || 0;
+ my $B_fullscreen = "B" ~~ @$fullscreen || 0;
+ $A->fullscreen($A_fullscreen);
+ $B->fullscreen($B_fullscreen);
+ sync_with_i3;
+
+ cmd '[con_mark=B] swap container with mark A';
+
+ $nodes = get_ws_content($ws1);
+ $node = $nodes->[0];
+ is($node->{window}, $B->{id}, 'B is on ws1:left');
+ is_num_fullscreen($ws1, $A_fullscreen, 'amount of fullscreen windows in ws1');
+ is($node->{fullscreen_mode}, $A_fullscreen, 'B got A\'s fullscreen mode');
+
+ $nodes = get_ws_content($ws2);
+ $node = $nodes->[1];
+ is($node->{window}, $A->{id}, 'A is on ws2:right');
+ is(get_focused($ws2), $expected_focus, 'A is focused');
+ is_num_fullscreen($ws2, $B_fullscreen, 'amount of fullscreen windows in ws2');
+ is($node->{fullscreen_mode}, $B_fullscreen, 'A got B\'s fullscreen mode');
+
+ kill_all_windows;
+}
+
+###############################################################################
+# Swap a non-fullscreen window with a fullscreen one in different workspaces.
+# Layout: O1[ W1[ H1 ] W2[ B ] ]
+#
+# +---+---+ Layout: H1[ A F ]
+# | A | F | Focus Stacks:
+# +---+---+ H1: F, A
+#
+# +---+---+
+# | B |
+# +---+---+
+###############################################################################
$ws1 = fresh_workspace;
+
$A = open_window(wm_class => 'mark_A');
+$F = open_window();
+$F->fullscreen(1);
$expected_focus = get_focused($ws1);
-open_window;
-cmd 'focus left';
$ws2 = fresh_workspace;
-open_window;
$B = open_window(wm_class => 'mark_B');
+$B->fullscreen(1);
+sync_with_i3;
cmd '[con_mark=B] swap container with mark A';
$nodes = get_ws_content($ws1);
is($nodes->[0]->{window}, $B->{id}, 'B is on ws1:left');
+is_num_fullscreen($ws1, 1, 'F still fullscreen in ws1');
+is(get_focused($ws1), $expected_focus, 'F is still focused');
$nodes = get_ws_content($ws2);
-is($nodes->[1]->{window}, $A->{id}, 'A is on ws1:right');
-is(get_focused($ws2), $expected_focus, 'A is focused');
+is($nodes->[0]->{window}, $A->{id}, 'A is on ws1');
-kill_all_windows;
+###############################################################################
+# Try a more exotic layout with fullscreen containers.
+# A and F are fullscreened as a stack of two vertical containers before the
+# swap is performed.
+# A is swapped with fullscreened window B which is in another workspace.
+#
+# +---+---+ Layout: H1[ X V1[ A F ] ]
+# | | A | Focus Stacks:
+# | X +---+ H1: V1, X
+# | | F | V1: F, A
+# +---+---+
+###############################################################################
+$ws1 = fresh_workspace;
+
+open_window;
+$A = open_window(wm_class => 'mark_A');
+cmd "split v";
+open_window;
+cmd "focus parent";
+cmd "fullscreen enable";
+$expected_focus = get_focused($ws1);
+
+$ws2 = fresh_workspace;
+$B = open_window(wm_class => 'mark_B');
+$B->fullscreen(1);
+sync_with_i3;
+
+cmd '[con_mark=B] swap container with mark A';
+
+sync_with_i3;
+does_i3_live;
+
+$nodes = get_ws_content($ws1);
+is($nodes->[1]->{nodes}->[0]->{window}, $B->{id}, 'B is on top right in ws1');
+is(get_focused($ws1), $expected_focus, 'The container of the stacked windows remains focused in ws1');
+is_num_fullscreen($ws1, 1, 'Same amount of fullscreen windows in ws1');
+
+$nodes = get_ws_content($ws2);
+is($nodes->[0]->{window}, $A->{id}, 'A is on ws2');
+is_num_fullscreen($ws2, 1, 'A is in fullscreen mode');
###############################################################################
# Swap two non-focused containers within the same workspace.
# | F |
# +---+
###############################################################################
-$ws1 = fresh_workspace;
-$A = open_window(wm_class => 'mark_A');
+for my $fullscreen (@fullscreen_permutations){
+ $ws1 = fresh_workspace;
+ $A = open_window(wm_class => 'mark_A');
-$ws2 = fresh_workspace;
-$B = open_window(wm_class => 'mark_B');
+ $ws2 = fresh_workspace;
+ $B = open_window(wm_class => 'mark_B');
-$ws3 = fresh_workspace;
-open_window;
-$expected_focus = get_focused($ws3);
+ $ws3 = fresh_workspace;
+ open_window;
+ $expected_focus = get_focused($ws3);
-cmd '[con_mark=B] swap container with mark A';
+ my $A_fullscreen = "A" ~~ @$fullscreen || 0;
+ my $B_fullscreen = "B" ~~ @$fullscreen || 0;
+ $A->fullscreen($A_fullscreen);
+ $B->fullscreen($B_fullscreen);
+ sync_with_i3;
-$nodes = get_ws_content($ws1);
-is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace');
+ cmd '[con_mark=B] swap container with mark A';
-$nodes = get_ws_content($ws2);
-is($nodes->[0]->{window}, $A->{id}, 'A is on the second workspace');
+ $nodes = get_ws_content($ws1);
+ $node = $nodes->[0];
+ is($node->{window}, $B->{id}, 'B is on the first workspace');
+ is_num_fullscreen($ws1, $A_fullscreen, 'amount of fullscreen windows in ws1');
+ is($node->{fullscreen_mode}, $A_fullscreen, 'B got A\'s fullscreen mode');
-is(get_focused($ws3), $expected_focus, 'F is still focused');
+ $nodes = get_ws_content($ws2);
+ $node = $nodes->[0];
+ is($node->{window}, $A->{id}, 'A is on the second workspace');
+ is_num_fullscreen($ws2, $B_fullscreen, 'amount of fullscreen windows in ws2');
+ is($node->{fullscreen_mode}, $B_fullscreen, 'A got B\'s fullscreen mode');
-kill_all_windows;
+ is(get_focused($ws3), $expected_focus, 'F is still focused');
+
+ kill_all_windows;
+}
###############################################################################
# Swap two non-focused containers with one being on a different workspace.
--- /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 the focus_follows_mouse setting.
+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 1000x1000+0+0
+EOT
+
+my ($first, $second);
+
+sub synced_warp_pointer {
+ my ($x_px, $y_px) = @_;
+ sync_with_i3;
+ $x->root->warp_pointer($x_px, $y_px);
+ sync_with_i3;
+}
+
+###################################################################
+# Test a simple case with 2 windows.
+###################################################################
+
+synced_warp_pointer(600, 600);
+$first = open_window;
+$second = open_window;
+is($x->input_focus, $second->id, 'second window focused');
+
+synced_warp_pointer(0, 0);
+is($x->input_focus, $first->id, 'first window focused');
+
+###################################################################
+# Test that focus isn't changed with tabbed windows.
+###################################################################
+
+fresh_workspace;
+synced_warp_pointer(600, 600);
+$first = open_window;
+cmd 'layout tabbed';
+$second = open_window;
+is($x->input_focus, $second->id, 'second (tabbed) window focused');
+
+synced_warp_pointer(0, 0);
+is($x->input_focus, $second->id, 'second window still focused');
+
+###################################################################
+# Test that floating windows are focused but not raised to the top.
+# See issue #2990.
+###################################################################
+
+my $ws;
+my $tmp = fresh_workspace;
+my ($first_floating, $second_floating);
+
+synced_warp_pointer(0, 0);
+$first_floating = open_floating_window;
+$first_floating->rect(X11::XCB::Rect->new(x => 1, y => 1, width => 100, height => 100));
+$second_floating = open_floating_window;
+$second_floating->rect(X11::XCB::Rect->new(x => 50, y => 50, width => 100, height => 100));
+sync_with_i3;
+$first = open_window;
+
+is($x->input_focus, $first->id, 'first (tiling) window focused');
+$ws = get_ws($tmp);
+is($ws->{floating_nodes}->[1]->{nodes}->[0]->{window}, $second_floating->id, 'second floating on top');
+is($ws->{floating_nodes}->[0]->{nodes}->[0]->{window}, $first_floating->id, 'first floating behind');
+
+synced_warp_pointer(40, 40);
+is($x->input_focus, $first_floating->id, 'first floating window focused');
+$ws = get_ws($tmp);
+is($ws->{floating_nodes}->[1]->{nodes}->[0]->{window}, $second_floating->id, 'second floating still on top');
+is($ws->{floating_nodes}->[0]->{nodes}->[0]->{window}, $first_floating->id, 'first floating still behind');
+
+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)
+#
+# Verifies that i3 does not crash when opening a floating sticky on one output
+# and then switching empty workspaces on the other output.
+# Ticket: #3075
+# Bug still in: 4.14-191-g9d2d602d
+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
+
+# A window on the left output.
+fresh_workspace(output => 0);
+open_window;
+cmd 'sticky enable, floating enable';
+
+# Switch to the right output and open a new workspace.
+my $ws = fresh_workspace(output => 1);
+does_i3_live;
+
+# Verify results.
+is(@{get_ws($ws)->{floating_nodes}}, 0, 'workspace in right output is empty');
+$ws = fresh_workspace(output => 0);
+is(@{get_ws($ws)->{floating_nodes}}, 1, 'new workspace in left output has the sticky container');
+
+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 the corrent focus stack order is preserved after various
+# operations.
+use i3test;
+
+sub kill_and_confirm_focus {
+ my $focus = shift;
+ my $msg = shift;
+ cmd "kill";
+ sync_with_i3;
+ is($x->input_focus, $focus, $msg);
+}
+
+my @windows;
+
+sub focus_windows {
+ for (my $i = $#windows; $i >= 0; $i--) {
+ cmd '[id=' . $windows[$i]->id . '] focus';
+ }
+}
+
+sub confirm_focus {
+ my $msg = shift;
+ sync_with_i3;
+ is($x->input_focus, $windows[0]->id, $msg . ': window 0 focused');
+ foreach my $i (1 .. $#windows) {
+ kill_and_confirm_focus($windows[$i]->id, "$msg: window $i focused");
+ }
+ cmd 'kill';
+ @windows = ();
+}
+
+#####################################################################
+# Open 5 windows, focus them in a custom order and then change to
+# tabbed layout. The focus order should be preserved.
+#####################################################################
+
+fresh_workspace;
+
+$windows[3] = open_window;
+$windows[1] = open_window;
+$windows[0] = open_window;
+$windows[2] = open_window;
+$windows[4] = open_window;
+focus_windows;
+
+cmd 'layout tabbed';
+confirm_focus('tabbed');
+
+#####################################################################
+# Same as above but with stacked.
+#####################################################################
+
+fresh_workspace;
+
+$windows[3] = open_window;
+$windows[1] = open_window;
+$windows[0] = open_window;
+$windows[2] = open_window;
+$windows[4] = open_window;
+focus_windows;
+
+cmd 'layout stacked';
+confirm_focus('stacked');
+
+#####################################################################
+# Open 4 windows horizontally, move the last one down. The focus
+# order should be preserved.
+#####################################################################
+
+fresh_workspace;
+$windows[3] = open_window;
+$windows[2] = open_window;
+$windows[1] = open_window;
+$windows[0] = open_window;
+
+cmd 'move down';
+confirm_focus('split-h + move');
+
+#####################################################################
+# Same as above but with a vertical split.
+#####################################################################
+
+fresh_workspace;
+$windows[3] = open_window;
+cmd 'split v';
+$windows[2] = open_window;
+$windows[1] = open_window;
+$windows[0] = open_window;
+
+cmd 'move left';
+confirm_focus('split-v + move');
+
+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)
+#
+# Verifies _NET_DESKTOP_NAMES, _NET_CURRENT_DESKTOP and _NET_CURRENT_DESKTOP
+# are updated properly when closing an inactive workspace container.
+# See github issue #3126
+
+use i3test;
+
+sub get_desktop_names {
+ sync_with_i3;
+
+ my $cookie = $x->get_property(
+ 0,
+ $x->get_root_window(),
+ $x->atom(name => '_NET_DESKTOP_NAMES')->id,
+ $x->atom(name => 'UTF8_STRING')->id,
+ 0,
+ 4096,
+ );
+
+ my $reply = $x->get_property_reply($cookie->{sequence});
+
+ return 0 if $reply->{value_len} == 0;
+
+ # the property is a null-delimited list of utf8 strings ;;
+ return split /\0/, $reply->{value};
+}
+
+sub get_num_of_desktops {
+ sync_with_i3;
+
+ my $cookie = $x->get_property(
+ 0,
+ $x->get_root_window(),
+ $x->atom(name => '_NET_NUMBER_OF_DESKTOPS')->id,
+ $x->atom(name => 'CARDINAL')->id,
+ 0,
+ 4,
+ );
+
+ my $reply = $x->get_property_reply($cookie->{sequence});
+
+ return undef if $reply->{value_len} != 1;
+ return undef if $reply->{format} != 32;
+ return undef if $reply->{type} != $x->atom(name => 'CARDINAL')->id,;
+
+ return unpack 'L', $reply->{value};
+}
+
+sub get_current_desktop {
+ sync_with_i3;
+
+ my $cookie = $x->get_property(
+ 0,
+ $x->get_root_window(),
+ $x->atom(name => '_NET_CURRENT_DESKTOP')->id,
+ $x->atom(name => 'CARDINAL')->id,
+ 0,
+ 4,
+ );
+
+ my $reply = $x->get_property_reply($cookie->{sequence});
+
+ return undef if $reply->{value_len} != 1;
+ return undef if $reply->{format} != 32;
+ return undef if $reply->{type} != $x->atom(name => 'CARDINAL')->id,;
+
+ return unpack 'L', $reply->{value};
+}
+
+cmd 'workspace 0';
+my $first = open_window;
+
+cmd 'workspace 1';
+my $second = open_window;
+
+cmd 'workspace 2';
+my $third = open_window;
+
+# Sanity check
+is(get_current_desktop, 2);
+is(get_num_of_desktops, 3);
+my @actual_names = get_desktop_names;
+my @expected_names = ('0', '1', '2');
+is_deeply(\@actual_names, \@expected_names);
+
+# Kill first window to close a workspace.
+cmd '[id="' . $second->id . '"] kill';
+
+is(get_current_desktop, 2, '_NET_CURRENT_DESKTOP should be updated');
+is(get_num_of_desktops, 2, '_NET_NUMBER_OF_DESKTOPS should be updated');
+my @actual_names = get_desktop_names;
+my @expected_names = ('0', '2');
+is_deeply(\@actual_names, \@expected_names, '_NET_DESKTOP_NAMES should be updated');
+
+
+done_testing;
($x0, $x1) = workspaces_per_screen();
ok($ws1 ~~ @$x0, 'ws1 on fake-0');
+################################################################################
+# Verify that '[class=".*"] move workspace to output' doesn't fail when
+# containers in the scratchpad are matched.
+# See issue: #3064.
+################################################################################
+my $__i3_scratch = get_ws('__i3_scratch');
+is(scalar @{$__i3_scratch->{floating_nodes}}, 0, 'scratchpad is empty');
+
+$ws0 = fresh_workspace(output => 0);
+open_window(wm_class => 'a');
+
+$ws1 = fresh_workspace(output => 1);
+open_window(wm_class => 'b');
+my $scratchpad_window = open_window(wm_class => 'c');
+cmd 'move to scratchpad';
+
+($x0, $x1) = workspaces_per_screen();
+ok($ws0 ~~ @$x0, 'ws0 on fake-0');
+ok($ws1 ~~ @$x1, 'ws1 on fake-1');
+
+my $reply = cmd '[class=".*"] move workspace to output fake-1';
+ok($reply->[0]->{success}, 'move successful');
+
+($x0, $x1) = workspaces_per_screen();
+ok($ws0 ~~ @$x1, 'ws0 on fake-1');
+ok($ws1 ~~ @$x1, 'ws1 on fake-1');
+
+$__i3_scratch = get_ws('__i3_scratch');
+is(scalar @{$__i3_scratch->{floating_nodes}}, 1, 'window still in scratchpad');
+
################################################################################
done_testing;
cmd "workspace $s2_ws";
cmd 'focus right';
-is($x->input_focus, $sixth->id, 'sixth window focused');
+is($x->input_focus, $seventh->id, 'seventh window focused');
reset_focus $s3_ws;
cmd "workspace $s2_ws";
cmd 'focus parent';
cmd 'focus parent';
cmd 'split v';
+# Focus second or else $first gets to the top of the focus stack.
+cmd '[id=' . $second->id . '] focus';
reset_focus $s0_ws;
cmd "workspace $s3_ws";
cmd 'focus parent';
cmd 'focus parent';
cmd 'split v';
+cmd '[id=' . $sixth->id . '] focus';
reset_focus $s3_ws;
cmd "workspace $s1_ws";
cmd 'focus up';
is($x->input_focus, $second->id, 'second window focused');
+###################################################################
+# Test that focus (left|down|right|up), when focusing across
+# outputs, doesn't focus the next window in the given direction but
+# the most focused window of the container in the given direction.
+# In the following layout:
+# [ WS1*[ ] WS2[ H[ A B* ] ] ]
+# (where the asterisk denotes the focused container within its
+# parent) moving right from WS1 should focus B which is focused
+# inside WS2, not A which is the next window on the right of WS1.
+# See issue #1160.
+###################################################################
+
+kill_all_windows;
+
+sync_with_i3;
+$x->root->warp_pointer(1025, 0); # Second screen.
+sync_with_i3;
+$s1_ws = fresh_workspace;
+$first = open_window;
+$second = open_window;
+
+sync_with_i3;
+$x->root->warp_pointer(0, 0); # First screen.
+sync_with_i3;
+$s0_ws = fresh_workspace;
+open_window;
+$third = open_window;
+
+cmd 'focus right';
+is($x->input_focus, $second->id, 'second window (rightmost) focused');
+cmd 'focus left';
+is($x->input_focus, $first->id, 'first window focused');
+cmd 'focus left';
+is($x->input_focus, $third->id, 'third window focused');
+
+
+###################################################################
+# Similar but with a tabbed layout.
+###################################################################
+
+cmd 'layout tabbed';
+$fourth = open_window;
+cmd 'focus left';
+is($x->input_focus, $third->id, 'third window (tabbed) focused');
+cmd "workspace $s1_ws";
+cmd 'focus left';
+is($x->input_focus, $third->id, 'third window (tabbed) focused');
+
+
+###################################################################
+# Similar but with a stacked layout on the bottom screen.
+###################################################################
+
+sync_with_i3;
+$x->root->warp_pointer(0, 769); # Third screen.
+sync_with_i3;
+$s2_ws = fresh_workspace;
+cmd 'layout stacked';
+$fifth = open_window;
+$sixth = open_window;
+
+cmd "workspace $s0_ws";
+cmd 'focus down';
+is($x->input_focus, $sixth->id, 'sixth window (stacked) focused');
+
+###################################################################
+# Similar but with a more complex layout.
+###################################################################
+
+sync_with_i3;
+$x->root->warp_pointer(1025, 769); # Fourth screen.
+sync_with_i3;
+$s3_ws = fresh_workspace;
+open_window;
+open_window;
+cmd 'split v';
+open_window;
+open_window;
+cmd 'split h';
+my $nested = open_window;
+open_window;
+cmd 'focus left';
+is($x->input_focus, $nested->id, 'nested window focused');
+
+cmd "workspace $s1_ws";
+cmd 'focus down';
+is($x->input_focus, $nested->id, 'nested window focused from workspace above');
+
+cmd "workspace $s2_ws";
+cmd 'focus right';
+is($x->input_focus, $nested->id, 'nested window focused from workspace on the left');
+
done_testing;
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
-#
+#
# Ticket: #990
# Bug still in: 4.5.1-23-g82b5978
fake-outputs 1024x768+0+0,1024x768+1024+0
EOT
-my $i3 = i3(get_socket_path());
-
-$i3->connect()->recv;
-
-################################
-# Workspaces requests and events
-################################
-
my $old_ws = get_ws(focused_ws);
-# Events
-
-# We are switching to an empty workpspace on the output to the right from an empty workspace on the output on the left, so we expect
-# to receive "init", "focus", and "empty".
my $focus = AnyEvent->condvar;
-$i3->subscribe({
- workspace => sub {
- my ($event) = @_;
- if ($event->{change} eq 'focus') {
- $focus->send($event);
- }
- }
-})->recv;
-
-my $t;
-$t = AnyEvent->timer(
- after => 0.5,
- cb => sub {
- $focus->send(0);
- }
-);
-
-cmd 'focus output right';
-
-my $event = $focus->recv;
+my @events = events_for(
+ sub { cmd 'focus output right' },
+ 'workspace');
my $current_ws = get_ws(focused_ws);
-ok($event, 'Workspace "focus" event received');
-is($event->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace');
-is($event->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace');
+is(scalar @events, 1, 'Received 1 event');
+is($events[0]->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace');
+is($events[0]->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace');
done_testing;
workspace ws-right output fake-1
EOT
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-# subscribe to the 'focus' ipc event
-my $focus = AnyEvent->condvar;
-$i3->subscribe({
- workspace => sub {
- my ($event) = @_;
- if ($event->{change} eq 'focus') {
- $focus->send($event);
- }
- }
-})->recv;
-
-# give up after 0.5 seconds
-my $timer = AnyEvent->timer(
- after => 0.5,
- cb => sub {
- $focus->send(0);
- }
-);
-
# open two windows on the left output
cmd 'workspace ws-left';
open_window;
open_window;
-# move a window over to the right output
-cmd 'move right';
-my $event = $focus->recv;
+sub focus_subtest {
+ my ($cmd, $want) = @_;
-ok($event, 'moving from workspace with two windows triggered focus ipc event');
-is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
-is(@{$event->{current}->{nodes}}, 1, 'focus event gave the right number of windows on the workspace');
+ my @events = events_for(
+ sub { cmd $cmd },
+ 'workspace');
-# reset and try again
-$focus = AnyEvent->condvar;
-cmd 'workspace ws-left';
-$focus->recv;
+ my @focus = grep { $_->{change} eq 'focus' } @events;
+ is(scalar @focus, 1, 'Received 1 workspace::focus event');
+ is($focus[0]->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
+ is(@{$focus[0]->{current}->{nodes}}, $want, 'focus event gave the right number of windows on the workspace');
+}
+
+# move a window over to the right output
+subtest 'move right (1)', \&focus_subtest, 'move right', 1;
-$focus = AnyEvent->condvar;
-cmd 'move right';
-$event = $focus->recv;
-ok($event, 'moving from workspace with one window triggered focus ipc event');
-is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
-is(@{$event->{current}->{nodes}}, 2, 'focus event gave the right number of windows on the workspace');
+# move another window
+cmd 'workspace ws-left';
+subtest 'move right (2)', \&focus_subtest, 'move right', 2;
done_testing;
bindsym button3 focus left
bindsym button4 focus right
bindsym button5 focus left
+ bindsym --release button6 focus right
+ bindsym button7 focus left
+ bindsym button7 --release focus right
}
EOT
use i3test::XTEST;
-my ($cv, $timer);
-sub reset_test {
- $cv = AE::cv;
- $timer = AE::timer(1, 0, sub { $cv->send(0); });
-}
-
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
my $ws = fresh_workspace;
-reset_test;
+my $cv = AnyEvent->condvar;
+my $timer = AnyEvent->timer(after => 1, interval => 0, cb => sub { $cv->send(0) });
$i3->subscribe({
window => sub {
my ($event) = @_;
},
})->recv;
-my $con;
-
sub i3bar_present {
my ($nodes) = @_;
for my $node (@{$nodes}) {
my $props = $node->{window_properties};
if (defined($props) && $props->{class} eq 'i3bar') {
- return 1;
+ return $node->{window};
}
}
return i3bar_present(\@children);
}
-if (i3bar_present($i3->get_tree->recv->{nodes})) {
+my $i3bar_window = i3bar_present($i3->get_tree->recv->{nodes});
+if ($i3bar_window) {
ok(1, 'i3bar present');
} else {
- $con = $cv->recv;
+ my $con = $cv->recv;
ok($con, 'i3bar appeared');
+ $i3bar_window = $con->{window};
}
+diag('i3bar window = ' . $i3bar_window);
+
my $left = open_window;
my $right = open_window;
sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $right->{id}, 'focus is initially on the right container');
-reset_test;
-xtest_button_press(1, 3, 3);
-xtest_button_release(1, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $left->{id}, 'button 1 moves focus left');
-reset_test;
-
-xtest_button_press(2, 3, 3);
-xtest_button_release(2, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $right->{id}, 'button 2 moves focus right');
-reset_test;
-
-xtest_button_press(3, 3, 3);
-xtest_button_release(3, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $left->{id}, 'button 3 moves focus left');
-reset_test;
-
-xtest_button_press(4, 3, 3);
-xtest_button_release(4, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $right->{id}, 'button 4 moves focus right');
-reset_test;
+sub focus_subtest {
+ my ($subscribecb, $want, $msg) = @_;
+ my @events = events_for(
+ $subscribecb,
+ 'window');
+ my @focus = map { $_->{container}->{window} } grep { $_->{change} eq 'focus' } @events;
+ is_deeply(\@focus, $want, $msg);
+}
-xtest_button_press(5, 3, 3);
-xtest_button_release(5, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $left->{id}, 'button 5 moves focus left');
-reset_test;
+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);
+ },
+ [ $left->{id} ],
+ 'button 1 moves focus left';
+
+subtest 'button 2 moves focus right', \&focus_subtest,
+ sub {
+ xtest_button_press(2, 3, 3);
+ xtest_button_release(2, 3, 3);
+ xtest_sync_with($i3bar_window);
+ },
+ [ $right->{id} ],
+ 'button 2 moves focus right';
+
+subtest 'button 3 moves focus left', \&focus_subtest,
+ sub {
+ xtest_button_press(3, 3, 3);
+ xtest_button_release(3, 3, 3);
+ xtest_sync_with($i3bar_window);
+ },
+ [ $left->{id} ],
+ 'button 3 moves focus left';
+
+subtest 'button 4 moves focus right', \&focus_subtest,
+ sub {
+ xtest_button_press(4, 3, 3);
+ xtest_button_release(4, 3, 3);
+ xtest_sync_with($i3bar_window);
+ },
+ [ $right->{id} ],
+ 'button 4 moves focus right';
+
+subtest 'button 5 moves focus left', \&focus_subtest,
+ sub {
+ xtest_button_press(5, 3, 3);
+ xtest_button_release(5, 3, 3);
+ xtest_sync_with($i3bar_window);
+ },
+ [ $left->{id} ],
+ 'button 5 moves focus left';
+
+# Test --release flag with bar bindsym.
+# See issue: #3068.
+
+my $old_focus = get_focused($ws);
+subtest 'button 6 does not move focus while pressed', \&focus_subtest,
+ sub {
+ xtest_button_press(6, 3, 3);
+ xtest_sync_with($i3bar_window);
+ },
+ [],
+ 'button 6 does not move focus while pressed';
+is(get_focused($ws), $old_focus, 'focus unchanged');
+
+subtest 'button 6 release moves focus right', \&focus_subtest,
+ sub {
+ xtest_button_release(6, 3, 3);
+ xtest_sync_with($i3bar_window);
+ },
+ [ $right->{id} ],
+ 'button 6 release moves focus right';
+
+# Test same bindsym button with and without --release.
+
+subtest 'button 7 press moves focus left', \&focus_subtest,
+ sub {
+ xtest_button_press(7, 3, 3);
+ xtest_sync_with($i3bar_window);
+ },
+ [ $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);
+ },
+ [ $right->{id} ],
+ 'button 7 release moves focus right';
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 that focus does not wrap when focus_wrapping is disabled in
+# the configuration.
+# Ticket: #2352
+# Bug still in: 4.14-72-g6411130c
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_wrapping no
+EOT
+
+sub test_orientation {
+ my ($orientation, $prev, $next) = @_;
+ my $tmp = fresh_workspace;
+
+ cmd "split $orientation";
+
+ my $win1 = open_window;
+ my $win2 = open_window;
+
+ is($x->input_focus, $win2->id, "Second window focused initially");
+ cmd "focus $prev";
+ is($x->input_focus, $win1->id, "First window focused");
+ cmd "focus $prev";
+ is($x->input_focus, $win1->id, "First window still focused");
+ cmd "focus $next";
+ is($x->input_focus, $win2->id, "Second window focused");
+ cmd "focus $next";
+ is($x->input_focus, $win2->id, "Second window still focused");
+}
+
+test_orientation('v', 'up', 'down');
+test_orientation('h', 'left', 'right');
+
+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 that the socket file is cleaned up properly after gracefully
+# shutting down i3 via SIGTERM.
+# Ticket: #3049
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config, dont_add_socket_path => 1);
+my $socket = get_socket_path();
+ok(-S $socket, "socket $socket exists");
+
+exit_forcefully($pid, 'TERM');
+
+ok(!-e $socket, "socket $socket no longer exists");
+
+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 resizing tiling containers
+use i3test;
+
+############################################################
+# resize horizontally
+############################################################
+
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+
+my $left = open_window;
+my $right = open_window;
+
+diag("left = " . $left->id . ", right = " . $right->id);
+
+is($x->input_focus, $right->id, 'Right window focused');
+
+cmd 'resize set 75 ppt 0 ppt';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.25, 'left window got only 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'right window got 75%');
+
+############################################################
+# resize vertically
+############################################################
+
+my $tmp = fresh_workspace;
+
+cmd 'split v';
+
+my $top = open_window;
+my $bottom = open_window;
+
+diag("top = " . $top->id . ", bottom = " . $bottom->id);
+
+is($x->input_focus, $bottom->id, 'Bottom window focused');
+
+cmd 'resize set 0 ppt 75 ppt';
+
+my ($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%');
+
+
+############################################################
+# resize horizontally and vertically
+############################################################
+
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+my $left = open_window;
+my $top_right = open_window;
+cmd 'split v';
+my $bottom_right = open_window;
+
+diag("left = " . $left->id . ", top-right = " . $top_right->id . ", bottom-right = " . $bottom_right->id);
+
+is($x->input_focus, $bottom_right->id, 'Bottom-right window focused');
+
+cmd 'resize set 75 ppt 75 ppt';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+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%');
+
+
+############################################################
+# resize from inside a tabbed container
+############################################################
+
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+
+my $left = open_window;
+my $right1 = open_window;
+
+cmd 'split h';
+cmd 'layout tabbed';
+
+my $right2 = open_window;
+
+diag("left = " . $left->id . ", right1 = " . $right1->id . ", right2 = " . $right2->id);
+
+is($x->input_focus, $right2->id, '2nd right window focused');
+
+cmd 'resize set 75 ppt 0 ppt';
+
+my ($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%');
+
+
+############################################################
+# resize from inside a stacked container
+############################################################
+
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+
+my $left = open_window;
+my $right1 = open_window;
+
+cmd 'split h';
+cmd 'layout stacked';
+
+my $right2 = open_window;
+
+diag("left = " . $left->id . ", right1 = " . $right1->id . ", right2 = " . $right2->id);
+
+is($x->input_focus, $right2->id, '2nd right window focused');
+
+cmd 'resize set 75 ppt 0 ppt';
+
+my ($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%');
+
+
+done_testing;
+++ /dev/null
-#!/bin/sh
-
-set -e
-set -x
-
-mkdir -p deb/DIST-clang/build
-tar xf build/*.tar.bz2 -C deb/DIST-clang --strip-components=1
-(cd deb/DIST-clang/build && scan-build -o ../../CLANG ../configure && scan-build -o ../../CLANG --html-title="Analysis of i3 v$(git describe --tags)" make -j8)
-mv deb/CLANG/*/* deb/CLANG
GITVERSION=$(git describe --tags)
mkdir build.i3wm.org
-cp -r deb/CLANG build.i3wm.org/clang-analyze
cp -r deb/COPY-DOCS build.i3wm.org/docs
cd build.i3wm.org
echo build.i3wm.org > CNAME
# Try running the tests in parallel so that the common case (tests pass) is
# quick, but fall back to running them in sequence to make debugging easier.
-if ! xvfb-run make check
+if ! make check
then
- xvfb-run ./testcases/complete-run.pl --parallel=1 || (cat latest/complete-run.log; false)
+ ./testcases/complete-run.pl --parallel=1 || (cat latest/complete-run.log; false)
fi
dpkg-dev devscripts git equivs \
clang clang-format-3.8 \
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 && \
+ 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.