Merge branch 'next' into master

This commit is contained in:
Michael Stapelberg 2018-03-10 18:29:21 +01:00
commit cdf9a8f77e
141 changed files with 3483 additions and 1572 deletions

11
.editorconfig Normal file
View File

@ -0,0 +1,11 @@
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

View File

@ -42,7 +42,6 @@ script:
- ./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_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_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_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 || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/docs.sh
- ./travis/skip-pkg.sh || travis/prep-bintray.sh - ./travis/skip-pkg.sh || travis/prep-bintray.sh

View File

@ -9,6 +9,7 @@ use AnyEvent::Socket;
use AnyEvent; use AnyEvent;
use Encode; use Encode;
use Scalar::Util qw(tainted); use Scalar::Util qw(tainted);
use Carp;
=head1 NAME =head1 NAME
@ -98,11 +99,12 @@ use constant TYPE_GET_BAR_CONFIG => 6;
use constant TYPE_GET_VERSION => 7; use constant TYPE_GET_VERSION => 7;
use constant TYPE_GET_BINDING_MODES => 8; use constant TYPE_GET_BINDING_MODES => 8;
use constant TYPE_GET_CONFIG => 9; use constant TYPE_GET_CONFIG => 9;
use constant TYPE_SEND_TICK => 10;
our %EXPORT_TAGS = ( 'all' => [ our %EXPORT_TAGS = ( 'all' => [
qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS 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_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} } ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
@ -119,6 +121,7 @@ my %events = (
barconfig_update => ($event_mask | 4), barconfig_update => ($event_mask | 4),
binding => ($event_mask | 5), binding => ($event_mask | 5),
shutdown => ($event_mask | 6), shutdown => ($event_mask | 6),
tick => ($event_mask | 7),
_error => 0xFFFFFFFF, _error => 0xFFFFFFFF,
); );
@ -187,7 +190,7 @@ sub new {
# We use getpwuid() instead of $ENV{HOME} because the latter is tainted # We use getpwuid() instead of $ENV{HOME} because the latter is tainted
# and thus produces warnings when running tests with perl -T # and thus produces warnings when running tests with perl -T
my $home = (getpwuid($<))[7]; 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; $path =~ s/~/$home/g;
} }
@ -331,9 +334,9 @@ scalar), if specified.
sub message { sub message {
my ($self, $type, $content) = @_; 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 = ""; my $payload = "";
if ($content) { if ($content) {
@ -374,7 +377,7 @@ sub _ensure_connection {
return if defined($self->{ipchdl}); 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 =head2 get_workspaces
@ -518,6 +521,18 @@ sub get_config {
$self->message(TYPE_GET_CONFIG); $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) =head2 command($content)

View File

@ -1 +1 @@
4.14.1-non-git 4.15-non-git

View File

@ -78,6 +78,11 @@ EXTRA_DIST = \
AnyEvent-I3/t/manifest.t \ AnyEvent-I3/t/manifest.t \
AnyEvent-I3/t/pod-coverage.t \ AnyEvent-I3/t/pod-coverage.t \
AnyEvent-I3/t/pod.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/asciidoc-git.conf \
docs/bigpicture.png \ docs/bigpicture.png \
docs/i3-pod2html \ docs/i3-pod2html \
@ -113,7 +118,7 @@ EXTRA_DIST = \
I3_VERSION \ I3_VERSION \
LICENSE \ LICENSE \
PACKAGE-MAINTAINER \ PACKAGE-MAINTAINER \
RELEASE-NOTES-4.14.1 \ RELEASE-NOTES-4.15 \
generate-command-parser.pl \ generate-command-parser.pl \
parser-specs/commands.spec \ parser-specs/commands.spec \
parser-specs/config.spec \ parser-specs/config.spec \

12
README.md Normal file
View File

@ -0,0 +1,12 @@
![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).

113
RELEASE-NOTES-4.15 Normal file
View File

@ -0,0 +1,113 @@
┌────────────────────────────┐
│ 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 i3s 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 didnt 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
• dont 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
• dont raise floating windows when focused because of focus_follows_mouse
• correctly set EWMH atoms when closing a workspace
• dont 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

View File

@ -2,7 +2,7 @@
# Run autoreconf -fi to generate a configure script from this file. # Run autoreconf -fi to generate a configure script from this file.
AC_PREREQ([2.69]) 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 # For AX_EXTEND_SRCDIR
AX_ENABLE_BUILDDIR AX_ENABLE_BUILDDIR
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2]) AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])

View File

@ -13,7 +13,14 @@ use warnings;
use Data::Dumper; use Data::Dumper;
use AnyEvent::I3; use AnyEvent::I3;
use File::Temp; use File::Temp;
use File::Basename;
use v5.10; 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 $i3 = i3();
@ -30,7 +37,7 @@ sub dump_node {
my $o = ($n->{orientation} eq 'none' ? "u" : ($n->{orientation} eq 'horizontal' ? "h" : "v")); my $o = ($n->{orientation} eq 'none' ? "u" : ($n->{orientation} eq 'horizontal' ? "h" : "v"));
my $w = (defined($n->{window}) ? $n->{window} : "N"); 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/\$/\\\$/g;
$na =~ s/&/\\&/g; $na =~ s/&/\\&/g;
@ -38,7 +45,7 @@ sub dump_node {
$na =~ s/~/\\textasciitilde{}/g; $na =~ s/~/\\textasciitilde{}/g;
my $type = 'leaf'; my $type = 'leaf';
if (!defined($n->{window})) { if (!defined($n->{window})) {
$type = $n->{orientation} . '-split'; $type = $n->{layout};
} }
my $name = qq|``$na'' ($type)|; my $name = qq|``$na'' ($type)|;
@ -75,4 +82,5 @@ say $tmp "draw(n" . $root->{id} . ", (0, 0));";
close($tmp); close($tmp);
my $rep = "$tmp"; my $rep = "$tmp";
$rep =~ s/asy$/eps/; $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");

View File

@ -14,6 +14,7 @@ use warnings;
use AnyEvent; use AnyEvent;
use AnyEvent::I3; use AnyEvent::I3;
use v5.10; use v5.10;
use utf8;
my %layouts = ( my %layouts = (
'4' => 'tabbed', '4' => 'tabbed',

14
debian/changelog vendored
View File

@ -1,3 +1,15 @@
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 i3-wm (4.14-1) unstable; urgency=medium
* New upstream release. * New upstream release.
@ -308,7 +320,7 @@ i3-wm (4.0.2-1) unstable; urgency=low
* Bugfix: Use correct format string in load_layout (fixes crash in restart) * Bugfix: Use correct format string in load_layout (fixes crash in restart)
* Bugfix: Fix border rendering (border lines were "cutting" through) * Bugfix: Fix border rendering (border lines were "cutting" through)
* Bugfix: Raise floating windows immediately when dragging/resizing * 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: migration-script: handle resize top/bottom correctly
* Bugfix: Fix focus issue when moving containers to workspaces * Bugfix: Fix focus issue when moving containers to workspaces
* Bugfix: Warp cursor when changing outputs again * Bugfix: Warp cursor when changing outputs again

View File

@ -1,2 +1,7 @@
debian/tmp/etc debian/tmp/etc
debian/tmp/usr 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/

4
debian/rules vendored
View File

@ -17,5 +17,9 @@ override_dh_auto_configure:
# The default is /usr/share/doc/i3 # The default is /usr/share/doc/i3
dh_auto_configure -- --docdir=/usr/share/doc/i3-wm 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 dh $@ --parallel --builddirectory=build --with=autoreconf

View File

@ -153,7 +153,7 @@ When sending bug reports, please attach the *whole* log file. Even if you think
you found the section which clearly highlights the problem, additional you found the section which clearly highlights the problem, additional
information might be necessary to completely diagnose the problem. 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 such as https://pastebin.com because pasting large amounts of text in IRC
sometimes leads to incomplete lines (servers have line length limitations) or sometimes leads to incomplete lines (servers have line length limitations) or
flood kicks. flood kicks.

View File

@ -177,7 +177,8 @@ separator_block_width::
markup:: markup::
A string that indicates how the text of the block should be parsed. Set to A string that indicates how the text of the block should be parsed. Set to
+"pango"+ to use https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup]. +"pango"+ to use https://developer.gnome.org/pango/stable/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 If you want to put in your own entries into a block, prefix the key with an
underscore (_). i3bar will ignore all keys it doesnt understand, and prefixing underscore (_). i3bar will ignore all keys it doesnt understand, and prefixing
@ -236,6 +237,11 @@ x, y::
X11 root window coordinates where the click occurred X11 root window coordinates where the click occurred
button:: button::
X11 button ID (for example 1 to 3 for left/middle/right mouse 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*: *Example*:
------------------------------------------ ------------------------------------------
@ -244,6 +250,10 @@ button::
"instance": "eth0", "instance": "eth0",
"button": 1, "button": 1,
"x": 1320, "x": 1320,
"y": 1400 "y": 1400,
"relative_x": 12,
"relative_y": 8,
"width": 50,
"height": 22
} }
------------------------------------------ ------------------------------------------

View File

@ -64,6 +64,7 @@ to do that).
| 7 | +GET_VERSION+ | <<_version_reply,VERSION>> | Gets the i3 version. | 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. | 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. | 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: So, a typical message could look like this:
@ -126,6 +127,8 @@ BINDING_MODES (8)::
Reply to the GET_BINDING_MODES message. Reply to the GET_BINDING_MODES message.
GET_CONFIG (9):: GET_CONFIG (9)::
Reply to the GET_CONFIG message. Reply to the GET_CONFIG message.
TICK (10)::
Reply to the SEND_TICK message.
[[_command_reply]] [[_command_reply]]
=== COMMAND reply === COMMAND reply
@ -637,6 +640,19 @@ which is a string containing the config file as loaded by i3 most recently.
{ "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" } { "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 == Events
@ -694,6 +710,10 @@ binding (5)::
mouse mouse
shutdown (6):: shutdown (6)::
Sent when the ipc shuts down because of a restart or exit by user command 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:* *Example:*
-------------------------------------------------------------------- --------------------------------------------------------------------
@ -866,6 +886,27 @@ because of a user action such as a +restart+ or +exit+ command. The +change
} }
--------------------------- ---------------------------
=== 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) == See also (existing libraries)
[[libraries]] [[libraries]]
@ -881,6 +922,7 @@ C++::
* https://github.com/drmgc/i3ipcpp * https://github.com/drmgc/i3ipcpp
Go:: Go::
* https://github.com/mdirkse/i3ipc-go * https://github.com/mdirkse/i3ipc-go
* https://github.com/i3/go-i3
JavaScript:: JavaScript::
* https://github.com/acrisci/i3ipc-gjs * https://github.com/acrisci/i3ipc-gjs
Lua:: Lua::
@ -958,3 +1000,6 @@ detect the byte order i3 is using:
payload. Then, receive the pending +COMMAND+ message reply in big endian. 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. 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

View File

@ -113,10 +113,8 @@ containing the appropriate i3 logfile for each testcase. The latest folder can
always be found under the symlink +latest/+. Unless told differently, it will always be found under the symlink +latest/+. Unless told differently, it will
run the tests on a separate X server instance (using Xephyr). 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 Xephyr will open a window where you can inspect the running test. By default,
the tests without an X session with Xvfb, such as with +xvfb-run tests are run under Xvfb.
./complete-run+. This will also speed up the tests significantly especially on
machines without a powerful video card.
.Example invocation of +complete-run.pl+ .Example invocation of +complete-run.pl+
--------------------------------------- ---------------------------------------

View File

@ -1,7 +1,6 @@
i3 Users Guide i3 Users Guide
=============== ===============
Michael Stapelberg <michael@i3wm.org> Michael Stapelberg <michael@i3wm.org>
March 2013
This document contains all the information you need to configure and use the i3 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/ window manager. If it does not, please check https://www.reddit.com/r/i3wm/
@ -11,7 +10,7 @@ mailing list.
== Default keybindings == Default keybindings
For the "too long; didnt read" people, here is an overview of the default For the "too long; didnt 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):* *Keys to use with $mod (Alt):*
@ -35,7 +34,8 @@ above, just decline i3-config-wizards offer and base your config on
Throughout this guide, the keyword +$mod+ will be used to refer to the 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 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 === Opening terminals and moving around
@ -196,7 +196,7 @@ out to be complicated to use (snapping), understand and implement.
=== The tree consists of Containers === 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, 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 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 simple example is the workspace: When you start i3 with a single monitor, a
@ -509,7 +509,7 @@ mode "$mode_launcher" {
=== The floating modifier === The floating modifier
To move floating windows with your mouse, you can either grab their titlebar 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 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 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 you can press Mod1, click into a window using your left mouse button, and drag
@ -585,23 +585,26 @@ workspace_layout default|stacking|tabbed
workspace_layout tabbed 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 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. floating windows, e.g., dialog windows, but not windows that are floated later on.
*Syntax*: *Syntax*:
--------------------------------------------- ---------------------------------------------
new_window normal|none|pixel default_border normal|none|pixel
new_window normal|pixel <px> default_border normal|pixel <px>
new_float normal|none|pixel default_floating_border normal|none|pixel
new_float normal|pixel <px> 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*: *Example*:
--------------------- ---------------------
new_window pixel default_border pixel
--------------------- ---------------------
The "normal" and "pixel" border styles support an optional border width in The "normal" and "pixel" border styles support an optional border width in
@ -609,11 +612,11 @@ pixels:
*Example*: *Example*:
--------------------- ---------------------
# The same as new_window none # The same as default_border none
new_window pixel 0 default_border pixel 0
# A 3 px border # A 3 px border
new_window pixel 3 default_border pixel 3
--------------------- ---------------------
@ -760,13 +763,18 @@ title change. As i3 will get the title as soon as the application maps the
window (mapping means actually displaying it on the screen), youd need to have window (mapping means actually displaying it on the screen), youd need to have
to match on 'Firefox' in this case. 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 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 file. The first one which matches the window wins and later assignments are not
considered. considered.
*Syntax*: *Syntax*:
------------------------------------------------------------ ------------------------------------------------------------
assign <criteria> [→] [workspace] <workspace> assign <criteria> [→] [workspace] [number] <workspace>
assign <criteria> [→] output left|right|up|down|primary|<output>
------------------------------------------------------------ ------------------------------------------------------------
*Examples*: *Examples*:
@ -783,11 +791,28 @@ assign [class="^URxvt$"] → 2
# Assignment to a named workspace # Assignment to a named workspace
assign [class="^URxvt$"] → work 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 # Start urxvt -name irssi
assign [class="^URxvt$" instance="^irssi$"] → 3 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. 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 To get the class and instance, you can use +xprop+. After clicking on the
@ -1033,26 +1058,39 @@ popup_during_fullscreen smart
=== Focus wrapping === Focus wrapping
When being in a tabbed or stacked container, the first container will be By default, when in a container with several windows or child containers, the
focused when you use +focus down+ on the last container -- the focus wraps. If opposite window will be focused when trying to move the focus over the edge of
however there is another stacked/tabbed container in that direction, focus will a container (and there are no other containers in that direction) -- the focus
be set on that container. This is the default behavior so you can navigate to wraps.
all your windows without having to use +focus parent+.
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 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 parent+ to switch to different containers, you can instead set +focus_wrapping+
+force_focus_wrapping+ configuration directive. After enabling it, the focus to the value +force+.
will always wrap.
*Syntax*: *Syntax*:
--------------------------- ---------------------------
force_focus_wrapping yes|no focus_wrapping yes|no|force
# Legacy syntax, equivalent to "focus_wrapping force"
force_focus_wrapping yes
--------------------------- ---------------------------
*Example*: *Examples*:
------------------------ -----------------
force_focus_wrapping yes # Disable focus wrapping
------------------------ focus_wrapping no
# Force focus wrapping
focus_wrapping force
-----------------
=== Forcing Xinerama === Forcing Xinerama
@ -1341,7 +1379,7 @@ and will be removed in a future release. We strongly recommend using the more ge
*Syntax*: *Syntax*:
---------------------------- ----------------------------
bindsym button<n> <command> bindsym [--release] button<n> <command>
---------------------------- ----------------------------
*Example*: *Example*:
@ -1349,6 +1387,8 @@ bindsym button<n> <command>
bar { bar {
# disable clicking on workspace buttons # disable clicking on workspace buttons
bindsym button1 nop 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 # execute custom script when scrolling downwards
bindsym button5 exec ~/.i3/scripts/custom_wheel_down bindsym button5 exec ~/.i3/scripts/custom_wheel_down
} }
@ -1913,6 +1953,9 @@ bindsym $mod+t floating toggle
To change focus, you can use the +focus+ command. The following options are To change focus, you can use the +focus+ command. The following options are
available: available:
<criteria>::
Sets focus to the container that matches the specified criteria.
See <<command_criteria>>.
left|right|up|down:: left|right|up|down::
Sets focus to the nearest container in the given direction. Sets focus to the nearest container in the given direction.
parent:: parent::
@ -1932,6 +1975,7 @@ output::
*Syntax*: *Syntax*:
---------------------------------------------- ----------------------------------------------
<criteria> focus
focus left|right|down|up focus left|right|down|up
focus parent|child|floating|tiling|mode_toggle focus parent|child|floating|tiling|mode_toggle
focus output left|right|up|down|primary|<output> focus output left|right|up|down|primary|<output>
@ -1939,6 +1983,9 @@ focus output left|right|up|down|primary|<output>
*Examples*: *Examples*:
------------------------------------------------- -------------------------------------------------
# Focus firefox
bindsym $mod+F1 [class="Firefox"] focus
# Focus container on the left, bottom, top, right # Focus container on the left, bottom, top, right
bindsym $mod+j focus left bindsym $mod+j focus left
bindsym $mod+k focus down bindsym $mod+k focus down
@ -2232,7 +2279,6 @@ bindsym $mod+x move container to output VGA1
bindsym $mod+x move container to output primary bindsym $mod+x move container to output primary
-------------------------------------------------------- --------------------------------------------------------
-------------------------------
Note that you might not have a primary output configured yet. To do so, run: Note that you might not have a primary output configured yet. To do so, run:
------------------------- -------------------------
xrandr --output <output> --primary xrandr --output <output> --primary
@ -2267,7 +2313,7 @@ If you want to resize containers/windows using your keyboard, you can use the
*Syntax*: *Syntax*:
------------------------------------------------------- -------------------------------------------------------
resize grow|shrink <direction> [<px> px [or <ppt> ppt]] 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 Direction can either be one of +up+, +down+, +left+ or +right+. Or you can be
@ -2276,8 +2322,11 @@ space from all the other containers. The optional pixel argument specifies by
how many pixels a *floating container* should be grown or shrunk (the default 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 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 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 default is 10 percentage points).
floating containers.
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. It is recommended to define bindings for resizing in a dedicated binding mode.
See <<binding_modes>> and the example in the i3 See <<binding_modes>> and the example in the i3
@ -2363,10 +2412,10 @@ TODO: make i3-input replace %s
*Examples*: *Examples*:
--------------------------------------- ---------------------------------------
# Read 1 character and mark the current window with this character # 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 # 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 Alternatively, if you do not want to mess with +i3-input+, you could create

View File

@ -22,7 +22,7 @@ font pango:monospace 8
# The font above is very space-efficient, that is, it looks good, sharp and # 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 # 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 # X core fonts rendering does not support right-to-left and this being a bitmap
# font, it doesnt 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 # use these keys for focus, movement, and resize directions when reaching for
# the arrows is not convenient # the arrows is not convenient
@ -104,29 +104,43 @@ bindsym Mod1+Shift+minus move scratchpad
# If there are multiple scratchpad windows, this command cycles through them. # If there are multiple scratchpad windows, this command cycles through them.
bindsym Mod1+minus scratchpad show 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 # switch to workspace
bindsym Mod1+1 workspace 1 bindsym Mod1+1 workspace $ws1
bindsym Mod1+2 workspace 2 bindsym Mod1+2 workspace $ws2
bindsym Mod1+3 workspace 3 bindsym Mod1+3 workspace $ws3
bindsym Mod1+4 workspace 4 bindsym Mod1+4 workspace $ws4
bindsym Mod1+5 workspace 5 bindsym Mod1+5 workspace $ws5
bindsym Mod1+6 workspace 6 bindsym Mod1+6 workspace $ws6
bindsym Mod1+7 workspace 7 bindsym Mod1+7 workspace $ws7
bindsym Mod1+8 workspace 8 bindsym Mod1+8 workspace $ws8
bindsym Mod1+9 workspace 9 bindsym Mod1+9 workspace $ws9
bindsym Mod1+0 workspace 10 bindsym Mod1+0 workspace $ws10
# move focused container to workspace # move focused container to workspace
bindsym Mod1+Shift+1 move container to workspace 1 bindsym Mod1+Shift+1 move container to workspace $ws1
bindsym Mod1+Shift+2 move container to workspace 2 bindsym Mod1+Shift+2 move container to workspace $ws2
bindsym Mod1+Shift+3 move container to workspace 3 bindsym Mod1+Shift+3 move container to workspace $ws3
bindsym Mod1+Shift+4 move container to workspace 4 bindsym Mod1+Shift+4 move container to workspace $ws4
bindsym Mod1+Shift+5 move container to workspace 5 bindsym Mod1+Shift+5 move container to workspace $ws5
bindsym Mod1+Shift+6 move container to workspace 6 bindsym Mod1+Shift+6 move container to workspace $ws6
bindsym Mod1+Shift+7 move container to workspace 7 bindsym Mod1+Shift+7 move container to workspace $ws7
bindsym Mod1+Shift+8 move container to workspace 8 bindsym Mod1+Shift+8 move container to workspace $ws8
bindsym Mod1+Shift+9 move container to workspace 9 bindsym Mod1+Shift+9 move container to workspace $ws9
bindsym Mod1+Shift+0 move container to workspace 10 bindsym Mod1+Shift+0 move container to workspace $ws10
# reload the configuration file # reload the configuration file
bindsym Mod1+Shift+c reload bindsym Mod1+Shift+c reload
@ -154,9 +168,10 @@ mode "resize" {
bindsym Up resize shrink height 10 px or 10 ppt bindsym Up resize shrink height 10 px or 10 ppt
bindsym Right resize grow width 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 Return mode "default"
bindsym Escape mode "default" bindsym Escape mode "default"
bindsym Mod1+r mode "default"
} }
bindsym Mod1+r mode "resize" bindsym Mod1+r mode "resize"

View File

@ -91,29 +91,42 @@ bindcode $mod+38 focus parent
# focus the child container # focus the child container
#bindsym $mod+d focus child #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 # switch to workspace
bindcode $mod+10 workspace 1 bindcode $mod+10 workspace $ws1
bindcode $mod+11 workspace 2 bindcode $mod+11 workspace $ws2
bindcode $mod+12 workspace 3 bindcode $mod+12 workspace $ws3
bindcode $mod+13 workspace 4 bindcode $mod+13 workspace $ws4
bindcode $mod+14 workspace 5 bindcode $mod+14 workspace $ws5
bindcode $mod+15 workspace 6 bindcode $mod+15 workspace $ws6
bindcode $mod+16 workspace 7 bindcode $mod+16 workspace $ws7
bindcode $mod+17 workspace 8 bindcode $mod+17 workspace $ws8
bindcode $mod+18 workspace 9 bindcode $mod+18 workspace $ws9
bindcode $mod+19 workspace 10 bindcode $mod+19 workspace $ws10
# move focused container to workspace # move focused container to workspace
bindcode $mod+Shift+10 move container to workspace 1 bindcode $mod+Shift+10 move container to workspace $ws1
bindcode $mod+Shift+11 move container to workspace 2 bindcode $mod+Shift+11 move container to workspace $ws2
bindcode $mod+Shift+12 move container to workspace 3 bindcode $mod+Shift+12 move container to workspace $ws3
bindcode $mod+Shift+13 move container to workspace 4 bindcode $mod+Shift+13 move container to workspace $ws4
bindcode $mod+Shift+14 move container to workspace 5 bindcode $mod+Shift+14 move container to workspace $ws5
bindcode $mod+Shift+15 move container to workspace 6 bindcode $mod+Shift+15 move container to workspace $ws6
bindcode $mod+Shift+16 move container to workspace 7 bindcode $mod+Shift+16 move container to workspace $ws7
bindcode $mod+Shift+17 move container to workspace 8 bindcode $mod+Shift+17 move container to workspace $ws8
bindcode $mod+Shift+18 move container to workspace 9 bindcode $mod+Shift+18 move container to workspace $ws9
bindcode $mod+Shift+19 move container to workspace 10 bindcode $mod+Shift+19 move container to workspace $ws10
# reload the configuration file # reload the configuration file
bindcode $mod+Shift+54 reload bindcode $mod+Shift+54 reload
@ -141,9 +154,10 @@ mode "resize" {
bindcode 111 resize shrink height 10 px or 10 ppt bindcode 111 resize shrink height 10 px or 10 ppt
bindcode 114 resize grow width 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 36 mode "default"
bindcode 9 mode "default" bindcode 9 mode "default"
bindcode $mod+27 mode "default"
} }
bindcode $mod+27 mode "resize" bindcode $mod+27 mode "resize"

View File

@ -116,17 +116,16 @@ my @keys = sort { (length($b) <=> length($a)) or ($a cmp $b) } keys %states;
open(my $enumfh, '>', "GENERATED_${prefix}_enums.h"); 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; my %statenum;
say $enumfh 'typedef enum {'; say $enumfh 'typedef enum {';
my $cnt = 0; my $cnt = 0;
for my $state (@keys, '__CALL') { for my $state (@keys, '__CALL') {
say $enumfh " $state = $cnt,"; say $enumfh ',' if $cnt > 0;
print $enumfh " $state = $cnt";
$statenum{$state} = $cnt; $statenum{$state} = $cnt;
$cnt++; $cnt++;
} }
say $enumfh '} cmdp_state;'; say $enumfh "\n} cmdp_state;";
close($enumfh); close($enumfh);
# Third step: Generate the call function. # Third step: Generate the call function.
@ -225,7 +224,7 @@ for my $state (@keys) {
$next_state = '__CALL'; $next_state = '__CALL';
} }
my $identifier = $token->{identifier}; 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 '};'; say $tokfh '};';
} }

View File

@ -58,12 +58,10 @@
#error "SYSCONFDIR not defined" #error "SYSCONFDIR not defined"
#endif #endif
#define FREE(pointer) \ #define FREE(pointer) \
do { \ do { \
if (pointer != NULL) { \ free(pointer); \
free(pointer); \ pointer = NULL; \
pointer = NULL; \
} \
} while (0) } while (0)
#include "xcb.h" #include "xcb.h"
@ -94,7 +92,7 @@ static xcb_get_modifier_mapping_reply_t *modmap_reply;
static i3Font font; static i3Font font;
static i3Font bold_font; static i3Font bold_font;
static int char_width; static int char_width;
static char *socket_path; static char *socket_path = NULL;
static xcb_window_t win; static xcb_window_t win;
static surface_t surface; static surface_t surface;
static xcb_key_symbols_t *symbols; static xcb_key_symbols_t *symbols;
@ -744,7 +742,6 @@ static void finish() {
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
char *xdg_config_home; char *xdg_config_home;
socket_path = getenv("I3SOCK");
char *pattern = "pango:monospace 8"; char *pattern = "pango:monospace 8";
char *patternbold = "pango:monospace bold 8"; char *patternbold = "pango:monospace bold 8";
int o, option_index = 0; int o, option_index = 0;
@ -824,12 +821,6 @@ int main(int argc, char *argv[]) {
&xkb_base_error) != 1) &xkb_base_error) != 1)
errx(EXIT_FAILURE, "Could not setup XKB extension."); 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); keysyms = xcb_key_symbols_alloc(conn);
xcb_get_modifier_mapping_cookie_t modmap_cookie; xcb_get_modifier_mapping_cookie_t modmap_cookie;
modmap_cookie = xcb_get_modifier_mapping(conn); modmap_cookie = xcb_get_modifier_mapping(conn);

View File

@ -25,6 +25,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <signal.h>
#include "libi3.h" #include "libi3.h"
#include "shmlog.h" #include "shmlog.h"
@ -38,6 +39,29 @@ static uint32_t wrap_count;
static i3_shmlog_header *header; static i3_shmlog_header *header;
static char *logbuffer, static char *logbuffer,
*walk; *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) { static int check_for_wrap(void) {
if (wrap_count == header->wrap_count) if (wrap_count == header->wrap_count)
@ -59,6 +83,14 @@ static void print_till_end(void) {
walk += len; 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 main(int argc, char *argv[]) {
int o, option_index = 0; int o, option_index = 0;
bool verbose = false; bool verbose = false;
@ -123,15 +155,35 @@ int main(int argc, char *argv[]) {
exit(1); exit(1);
} }
if (root_atom_contents("I3_CONFIG_PATH", conn, screen) != NULL) { 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"); fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled. Enabling SHM log until cancelled\n\n");
if (!is_debug_build()) { 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, "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, "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"); fprintf(stderr, "Please follow this guide instead:\nhttps://i3wm.org/docs/debugging-release-version.html\n");
exit(1); 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') if (*shmname == '\0')
@ -182,22 +234,32 @@ int main(int argc, char *argv[]) {
print_till_end(); print_till_end();
#if !defined(__OpenBSD__) #if !defined(__OpenBSD__)
if (follow) { if (!follow) {
/* Since pthread_cond_wait() expects a mutex, we need to provide one. 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 (thats bad, mhkay?) we just define one outside of * To not lock i3 (thats bad, mhkay?) we just define one outside of
* the shared memory. */ * the shared memory. */
pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&dummy_mutex); pthread_mutex_lock(&dummy_mutex);
while (1) { while (!interrupted) {
pthread_cond_wait(&(header->condvar), &dummy_mutex); pthread_cond_wait(&(header->condvar), &dummy_mutex);
/* If this was not a spurious wakeup, print the new lines. */ /* If this was not a spurious wakeup, print the new lines. */
if (header->offset_next_write != offset_next_write) { if (header->offset_next_write != offset_next_write) {
offset_next_write = header->offset_next_write; offset_next_write = header->offset_next_write;
print_till_end(); print_till_end();
}
} }
} }
#endif
#endif
exit(0);
return 0; return 0;
} }

View File

@ -5,12 +5,10 @@
#include <err.h> #include <err.h>
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__); #define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
#define FREE(pointer) \ #define FREE(pointer) \
do { \ do { \
if (pointer != NULL) { \ free(pointer); \
free(pointer); \ pointer = NULL; \
pointer = NULL; \
} \
} while (0) } while (0)
extern xcb_window_t root; extern xcb_window_t root;

View File

@ -41,7 +41,6 @@
* the command will be sent to i3 */ * the command will be sent to i3 */
static char *format; static char *format;
static char *socket_path;
static int sockfd; static int sockfd;
static xcb_key_symbols_t *symbols; static xcb_key_symbols_t *symbols;
static bool modeswitch_active = false; static bool modeswitch_active = false;
@ -374,7 +373,7 @@ free_resources:
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
format = sstrdup("%s"); format = sstrdup("%s");
socket_path = getenv("I3SOCK"); char *socket_path = NULL;
char *pattern = sstrdup("pango:monospace 8"); char *pattern = sstrdup("pango:monospace 8");
int o, option_index = 0; int o, option_index = 0;
@ -438,12 +437,6 @@ int main(int argc, char *argv[]) {
if (!conn || xcb_connection_has_error(conn)) if (!conn || xcb_connection_has_error(conn))
die("Cannot open display\n"); 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); sockfd = ipc_connect(socket_path);
root_screen = xcb_aux_get_screen(conn, screen); root_screen = xcb_aux_get_screen(conn, screen);

View File

@ -38,8 +38,6 @@
#include <i3/ipc.h> #include <i3/ipc.h>
static char *socket_path;
/* /*
* Having verboselog() and errorlog() is necessary when using libi3. * Having verboselog() and errorlog() is necessary when using libi3.
* *
@ -161,11 +159,7 @@ int main(int argc, char *argv[]) {
if (pledge("stdio rpath unix", NULL) == -1) if (pledge("stdio rpath unix", NULL) == -1)
err(EXIT_FAILURE, "pledge"); err(EXIT_FAILURE, "pledge");
#endif #endif
char *env_socket_path = getenv("I3SOCK"); char *socket_path = NULL;
if (env_socket_path)
socket_path = sstrdup(env_socket_path);
else
socket_path = NULL;
int o, option_index = 0; int o, option_index = 0;
uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND; uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND;
char *payload = NULL; char *payload = NULL;
@ -183,8 +177,7 @@ int main(int argc, char *argv[]) {
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
if (o == 's') { if (o == 's') {
if (socket_path != NULL) free(socket_path);
free(socket_path);
socket_path = sstrdup(optarg); socket_path = sstrdup(optarg);
} else if (o == 't') { } else if (o == 't') {
if (strcasecmp(optarg, "command") == 0) { if (strcasecmp(optarg, "command") == 0) {
@ -207,9 +200,11 @@ int main(int argc, char *argv[]) {
message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION; message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION;
} else if (strcasecmp(optarg, "get_config") == 0) { } else if (strcasecmp(optarg, "get_config") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG; message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
} else if (strcasecmp(optarg, "send_tick") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_SEND_TICK;
} else { } else {
printf("Unknown message type\n"); 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); exit(EXIT_FAILURE);
} }
} else if (o == 'q') { } else if (o == 'q') {
@ -226,13 +221,6 @@ int main(int argc, char *argv[]) {
} }
} }
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. /* Use all arguments, separated by whitespace, as payload.
* This way, you dont have to do i3-msg 'mark foo', you can use * This way, you dont have to do i3-msg 'mark foo', you can use
* i3-msg mark foo */ * i3-msg mark foo */
@ -251,17 +239,7 @@ int main(int argc, char *argv[]) {
if (!payload) if (!payload)
payload = sstrdup(""); payload = sstrdup("");
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); int sockfd = ipc_connect(socket_path);
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);
if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1) if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1)
err(EXIT_FAILURE, "IPC: write()"); err(EXIT_FAILURE, "IPC: write()");
free(payload); free(payload);

View File

@ -5,12 +5,10 @@
#include <err.h> #include <err.h>
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__); #define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
#define FREE(pointer) \ #define FREE(pointer) \
do { \ do { \
if (pointer != NULL) { \ free(pointer); \
free(pointer); \ pointer = NULL; \
pointer = NULL; \
} \
} while (0) } while (0)
#define xmacro(atom) xcb_atom_t A_##atom; #define xmacro(atom) xcb_atom_t A_##atom;

View File

@ -575,7 +575,9 @@ int main(int argc, char *argv[]) {
case XCB_CONFIGURE_NOTIFY: { case XCB_CONFIGURE_NOTIFY: {
xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event; 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; break;
} }
} }

View File

@ -9,7 +9,7 @@
# mechanism to find the preferred editor # mechanism to find the preferred editor
# Hopefully one of these is installed (no flamewars about preference please!): # 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 if command -v "$editor" > /dev/null 2>&1; then
exec "$editor" "$@" exec "$editor" "$@"
fi fi

View File

@ -8,7 +8,7 @@
# We welcome patches that add distribution-specific mechanisms to find the # We welcome patches that add distribution-specific mechanisms to find the
# preferred terminal emulator. On Debian, there is the x-terminal-emulator # preferred terminal emulator. On Debian, there is the x-terminal-emulator
# symlink for example. # 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 if command -v "$terminal" > /dev/null 2>&1; then
exec "$terminal" "$@" exec "$terminal" "$@"
fi fi

View File

@ -85,4 +85,4 @@ bool child_want_click_events(void);
* Generates a click event, if enabled. * 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);

View File

@ -27,6 +27,7 @@ typedef enum { M_DOCK = 0,
typedef struct binding_t { typedef struct binding_t {
int input_code; int input_code;
char *command; char *command;
bool release;
TAILQ_ENTRY(binding_t) TAILQ_ENTRY(binding_t)
bindings; bindings;

View File

@ -33,6 +33,12 @@ void parse_outputs_json(char* json);
*/ */
void init_outputs(void); void init_outputs(void);
/*
* free() all outputs data structures.
*
*/
void free_outputs(void);
/* /*
* Returns the output with the given name * Returns the output with the given name
* *

View File

@ -20,12 +20,10 @@
#define STARTS_WITH(string, len, needle) (((len) >= strlen((needle))) && strncasecmp((string), (needle), strlen((needle))) == 0) #define STARTS_WITH(string, len, needle) (((len) >= strlen((needle))) && strncasecmp((string), (needle), strlen((needle))) == 0)
/* Securely free p */ /* Securely free p */
#define FREE(p) \ #define FREE(p) \
do { \ do { \
if (p != NULL) { \ free(p); \
free(p); \ p = NULL; \
p = NULL; \
} \
} while (0) } while (0)
/* Securely free single-linked list */ /* Securely free single-linked list */

View File

@ -9,4 +9,5 @@ ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
ATOM_DO(_NET_SYSTEM_TRAY_COLORS) ATOM_DO(_NET_SYSTEM_TRAY_COLORS)
ATOM_DO(_XEMBED_INFO) ATOM_DO(_XEMBED_INFO)
ATOM_DO(_XEMBED) ATOM_DO(_XEMBED)
ATOM_DO(I3_SYNC)
#undef ATOM_DO #undef ATOM_DO

View File

@ -106,7 +106,7 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
va_list args; va_list args;
va_start(args, format); va_start(args, format);
if (vasprintf(&message, format, args) == -1) { if (vasprintf(&message, format, args) == -1) {
return; goto finish;
} }
struct status_block *err_block = scalloc(1, sizeof(struct status_block)); struct status_block *err_block = scalloc(1, sizeof(struct status_block));
@ -124,6 +124,7 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks); TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks);
TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks); TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
finish:
FREE(message); FREE(message);
va_end(args); va_end(args);
} }
@ -595,7 +596,7 @@ void child_click_events_key(const char *key) {
* Generates a click event, if enabled. * 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) { if (!child.click_events) {
return; return;
} }
@ -623,6 +624,18 @@ void send_block_clicked(int button, const char *name, const char *instance, int
child_click_events_key("y"); child_click_events_key("y");
yajl_gen_integer(gen, 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); yajl_gen_map_close(gen);
child_write_output(); child_write_output();
} }

View File

@ -107,34 +107,34 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
if (!strcmp(cur_key, "mode")) { if (!strcmp(cur_key, "mode")) {
DLOG("mode = %.*s, len = %d\n", len, val, len); DLOG("mode = %.*s, len = %d\n", len, val, len);
config.hide_on_modifier = (len == 4 && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK config.hide_on_modifier = (len == strlen("dock") && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK
: (len == 4 && !strncmp((const char *)val, "hide", strlen("hide")) ? M_HIDE : (len == strlen("hide") && !strncmp((const char *)val, "hide", strlen("hide")) ? M_HIDE
: M_INVISIBLE)); : M_INVISIBLE));
return 1; return 1;
} }
if (!strcmp(cur_key, "hidden_state")) { if (!strcmp(cur_key, "hidden_state")) {
DLOG("hidden_state = %.*s, len = %d\n", len, val, len); 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; return 1;
} }
if (!strcmp(cur_key, "modifier")) { if (!strcmp(cur_key, "modifier")) {
DLOG("modifier = %.*s\n", len, val); 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; config.modifier = XCB_NONE;
return 1; 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; config.modifier = ShiftMask;
return 1; 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; config.modifier = ControlMask;
return 1; 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]) { switch (val[3]) {
case '1': case '1':
config.modifier = Mod1Mask; config.modifier = Mod1Mask;
@ -179,7 +179,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
if (!strcmp(cur_key, "position")) { if (!strcmp(cur_key, "position")) {
DLOG("position = %.*s\n", len, val); 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; return 1;
} }
@ -192,6 +192,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
if (!strcmp(cur_key, "font")) { if (!strcmp(cur_key, "font")) {
DLOG("font = %.*s\n", len, val); DLOG("font = %.*s\n", len, val);
FREE(config.fontname);
sasprintf(&config.fontname, "%.*s", len, val); sasprintf(&config.fontname, "%.*s", len, val);
return 1; return 1;
} }
@ -263,6 +264,21 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
* *
*/ */
static int config_boolean_cb(void *params_, int val) { 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")) { if (!strcmp(cur_key, "binding_mode_indicator")) {
DLOG("binding_mode_indicator = %d\n", val); DLOG("binding_mode_indicator = %d\n", val);
config.disable_binding_mode_indicator = !val; config.disable_binding_mode_indicator = !val;

View File

@ -64,17 +64,14 @@ void got_subscribe_reply(char *reply) {
*/ */
void got_output_reply(char *reply) { void got_output_reply(char *reply) {
DLOG("Clearing old output configuration...\n"); DLOG("Clearing old output configuration...\n");
i3_output *o_walk; free_outputs();
SLIST_FOREACH(o_walk, outputs, slist) {
destroy_window(o_walk);
}
FREE_SLIST(outputs, i3_output);
DLOG("Parsing outputs JSON...\n"); DLOG("Parsing outputs JSON...\n");
parse_outputs_json(reply); parse_outputs_json(reply);
DLOG("Reconfiguring windows...\n"); DLOG("Reconfiguring windows...\n");
reconfig_windows(false); reconfig_windows(false);
i3_output *o_walk;
SLIST_FOREACH(o_walk, outputs, slist) { SLIST_FOREACH(o_walk, outputs, slist) {
kick_tray_clients(o_walk); kick_tray_clients(o_walk);
} }
@ -177,7 +174,7 @@ void got_bar_config_update(char *event) {
/* update the configuration with the received settings */ /* update the configuration with the received settings */
DLOG("Received bar config update \"%s\"\n", event); 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; bar_display_mode_t old_mode = config.hide_on_modifier;
parse_config_json(event); parse_config_json(event);
if (old_mode != config.hide_on_modifier) { if (old_mode != config.hide_on_modifier) {
@ -189,7 +186,7 @@ void got_bar_config_update(char *event) {
init_colors(&(config.colors)); init_colors(&(config.colors));
/* restart status command process */ /* restart status command process */
if (strcmp(old_command, config.command) != 0) { if (old_command && strcmp(old_command, config.command) != 0) {
kill_child(); kill_child();
start_child(config.command); start_child(config.command);
} }

View File

@ -182,7 +182,5 @@ int main(int argc, char **argv) {
clean_xcb(); clean_xcb();
ev_default_destroy(); ev_default_destroy();
free_workspaces();
return 0; return 0;
} }

View File

@ -173,6 +173,12 @@ static int outputs_start_map_cb(void *params_) {
return 1; 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) * We hit the end of a map (rect or a new output)
* *
@ -199,9 +205,7 @@ static int outputs_end_map_cb(void *params_) {
if (!handle_output) { if (!handle_output) {
DLOG("Ignoring output \"%s\", not configured to handle it.\n", DLOG("Ignoring output \"%s\", not configured to handle it.\n",
params->outputs_walk->name); params->outputs_walk->name);
FREE(params->outputs_walk->name); clear_output(params->outputs_walk);
FREE(params->outputs_walk->workspaces);
FREE(params->outputs_walk->trayclients);
FREE(params->outputs_walk); FREE(params->outputs_walk);
FREE(params->cur_key); FREE(params->cur_key);
return 1; return 1;
@ -217,6 +221,9 @@ static int outputs_end_map_cb(void *params_) {
target->primary = params->outputs_walk->primary; target->primary = params->outputs_walk->primary;
target->ws = params->outputs_walk->ws; target->ws = params->outputs_walk->ws;
target->rect = params->outputs_walk->rect; target->rect = params->outputs_walk->rect;
clear_output(params->outputs_walk);
FREE(params->outputs_walk);
} }
return 1; return 1;
} }
@ -260,7 +267,6 @@ void init_outputs(void) {
*/ */
void parse_outputs_json(char *json) { void parse_outputs_json(char *json) {
struct outputs_json_params params; struct outputs_json_params params;
params.outputs_walk = NULL; params.outputs_walk = NULL;
params.cur_key = NULL; params.cur_key = NULL;
params.json = json; params.json = json;
@ -286,6 +292,27 @@ void parse_outputs_json(char *json) {
yajl_free(handle); 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 * Returns the output with the given name
* *

View File

@ -83,7 +83,6 @@ int mod_pressed = 0;
/* Event watchers, to interact with the user */ /* Event watchers, to interact with the user */
ev_prepare *xcb_prep; ev_prepare *xcb_prep;
ev_check *xcb_chk;
ev_io *xcb_io; ev_io *xcb_io;
ev_io *xkb_io; ev_io *xkb_io;
@ -440,6 +439,18 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
xcb_flush(xcb_connection); 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). * 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- * We determine, whether the click occurred on a workspace button or if the scroll-
@ -461,10 +472,16 @@ void handle_button(xcb_button_press_event_t *event) {
return; return;
} }
int32_t x = event->event_x >= 0 ? event->event_x : 0;
DLOG("Got button %d\n", event->detail); 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; int workspace_width = 0;
i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk; i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
@ -506,7 +523,8 @@ void handle_button(xcb_button_press_event_t *event) {
block_x += render->width + render->x_offset + render->x_append + get_sep_offset(block) + sep_offset_remainder; 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) { 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; return;
} }
@ -517,12 +535,7 @@ void handle_button(xcb_button_press_event_t *event) {
/* If a custom command was specified for this mouse button, it overrides /* If a custom command was specified for this mouse button, it overrides
* the default behavior. */ * the default behavior. */
binding_t *binding; if (execute_custom_command(event->detail, event_is_release)) {
TAILQ_FOREACH(binding, &(config.bindings), bindings) {
if (binding->input_code != event->detail)
continue;
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
return; return;
} }
@ -678,8 +691,26 @@ static void configure_trayclients(void) {
* *
*/ */
static void handle_client_message(xcb_client_message_event_t *event) { static void handle_client_message(xcb_client_message_event_t *event) {
if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] && if (event->type == atoms[I3_SYNC]) {
event->format == 32) { 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"); DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
/* event->data.data32[0] is the timestamp */ /* event->data.data32[0] is the timestamp */
uint32_t op = event->data.data32[1]; uint32_t op = event->data.data32[1];
@ -1057,21 +1088,11 @@ static void handle_resize_request(xcb_resize_request_event_t *event) {
} }
/* /*
* This function is called immediately before the main loop locks. We flush xcb * This function is called immediately before the main loop locks. We check for
* then (and only then) * events from X11, handle them, then flush our outgoing queue.
* *
*/ */
void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) { 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; xcb_generic_event_t *event;
if (xcb_connection_has_error(xcb_connection)) { if (xcb_connection_has_error(xcb_connection)) {
@ -1157,6 +1178,7 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
} }
break; break;
case XCB_BUTTON_RELEASE:
case XCB_BUTTON_PRESS: case XCB_BUTTON_PRESS:
/* Button press events are mouse buttons clicked on one of our bars */ /* Button press events are mouse buttons clicked on one of our bars */
handle_button((xcb_button_press_event_t *)event); handle_button((xcb_button_press_event_t *)event);
@ -1192,6 +1214,8 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
} }
free(event); free(event);
} }
xcb_flush(xcb_connection);
} }
/* /*
@ -1249,21 +1273,12 @@ char *init_xcb_early() {
/* The various watchers to communicate with xcb */ /* The various watchers to communicate with xcb */
xcb_io = smalloc(sizeof(ev_io)); xcb_io = smalloc(sizeof(ev_io));
xcb_prep = smalloc(sizeof(ev_prepare)); 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_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
ev_prepare_init(xcb_prep, &xcb_prep_cb); 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 xcbs
* queue last, otherwise we risk dead-locking. */
ev_set_priority(xcb_chk, EV_MINPRI);
ev_io_start(main_loop, xcb_io); ev_io_start(main_loop, xcb_io);
ev_prepare_start(main_loop, xcb_prep); 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 */ /* Now we get the atoms and save them in a nice data structure */
get_atoms(); get_atoms();
@ -1499,16 +1514,7 @@ void init_tray_colors(void) {
* *
*/ */
void clean_xcb(void) { void clean_xcb(void) {
i3_output *o_walk; free_outputs();
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_font(); free_font();
@ -1517,11 +1523,9 @@ void clean_xcb(void) {
xcb_aux_sync(xcb_connection); xcb_aux_sync(xcb_connection);
xcb_disconnect(xcb_connection); xcb_disconnect(xcb_connection);
ev_check_stop(main_loop, xcb_chk);
ev_prepare_stop(main_loop, xcb_prep); ev_prepare_stop(main_loop, xcb_prep);
ev_io_stop(main_loop, xcb_io); ev_io_stop(main_loop, xcb_io);
FREE(xcb_chk);
FREE(xcb_prep); FREE(xcb_prep);
FREE(xcb_io); FREE(xcb_io);
} }
@ -1689,7 +1693,8 @@ void reconfig_windows(bool redraw_bars) {
* */ * */
values[3] = XCB_EVENT_MASK_EXPOSURE | values[3] = XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | 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 (config.hide_on_modifier == M_DOCK) {
/* If the bar is normally visible, catch visibility change events to suspend /* If the bar is normally visible, catch visibility change events to suspend
* the status process when the bar is obscured by full-screened windows. */ * the status process when the bar is obscured by full-screened windows. */

View File

@ -28,6 +28,7 @@
#include <errno.h> #include <errno.h>
#include <err.h> #include <err.h>
#include <stdint.h> #include <stdint.h>
#include <inttypes.h>
#include <math.h> #include <math.h>
#include <limits.h> #include <limits.h>

View File

@ -63,10 +63,10 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *no_aut
void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth); 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]'. * Implementation of 'resize grow|shrink <direction> [<px> px] [or <ppt> ppt]'.

View File

@ -38,6 +38,12 @@ void con_free(Con *con);
*/ */
void con_focus(Con *con); 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. * Closes the given container.
* *
@ -222,6 +228,22 @@ void con_unmark(Con *con, const char *name);
*/ */
Con *con_for_window(Con *con, i3Window *window, Match **store_match); 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. * Returns the number of children of this container.
* *
@ -314,7 +336,16 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates,
* visible workspace on the given output. * 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. * Moves the given container to the given mark.

View File

@ -49,6 +49,7 @@ CFGFUN(workspace_layout, const char *layout);
CFGFUN(workspace_back_and_forth, const char *value); CFGFUN(workspace_back_and_forth, const char *value);
CFGFUN(focus_follows_mouse, const char *value); CFGFUN(focus_follows_mouse, const char *value);
CFGFUN(mouse_warping, const char *value); CFGFUN(mouse_warping, const char *value);
CFGFUN(focus_wrapping, const char *value);
CFGFUN(force_focus_wrapping, const char *value); CFGFUN(force_focus_wrapping, const char *value);
CFGFUN(force_xinerama, const char *value); CFGFUN(force_xinerama, const char *value);
CFGFUN(disable_randr15, const char *value); CFGFUN(disable_randr15, const char *value);
@ -57,7 +58,8 @@ CFGFUN(force_display_urgency_hint, const long duration_ms);
CFGFUN(focus_on_window_activation, const char *mode); CFGFUN(focus_on_window_activation, const char *mode);
CFGFUN(show_marks, const char *value); CFGFUN(show_marks, const char *value);
CFGFUN(hide_edge_borders, const char *borders); 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(no_focus);
CFGFUN(ipc_socket, const char *path); CFGFUN(ipc_socket, const char *path);
CFGFUN(restart_state, const char *path); CFGFUN(restart_state, const char *path);
@ -65,7 +67,7 @@ CFGFUN(popup_during_fullscreen, const char *value);
CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border); CFGFUN(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(color_single, const char *colorclass, const char *color);
CFGFUN(floating_modifier, const char *modifiers); 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(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(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);
@ -82,7 +84,7 @@ CFGFUN(bar_verbose, const char *verbose);
CFGFUN(bar_modifier, const char *modifier); CFGFUN(bar_modifier, const char *modifier);
CFGFUN(bar_wheel_up_cmd, const char *command); CFGFUN(bar_wheel_up_cmd, const char *command);
CFGFUN(bar_wheel_down_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_position, const char *position);
CFGFUN(bar_i3bar_command, const char *i3bar_command); CFGFUN(bar_i3bar_command, const char *i3bar_command);
CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text); CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);

View File

@ -137,15 +137,24 @@ struct Config {
* comes with i3. Thus, you can turn it off entirely. */ * comes with i3. Thus, you can turn it off entirely. */
bool disable_workspace_bar; bool disable_workspace_bar;
/** Think of the following layout: Horizontal workspace with a tabbed /** When focus wrapping is enabled (the default), attempting to
* con on the left of the screen and a terminal on the right of the * move focus past the edge of the screen (in other words, in a
* screen. You are in the second container in the tabbed container and * direction in which there are no more containers to focus) will
* focus to the right. By default, i3 will set focus to the terminal on * cause the focus to wrap to the opposite edge of the current
* the right. If you are in the first container in the tabbed container * container. When it is disabled, nothing happens; the current
* however, focusing to the left will wrap. This option forces i3 to * focus is preserved.
* always wrap, which will result in you having to use "focus parent" *
* more often. */ * Additionally, focus wrapping may be forced. Think of the
bool force_focus_wrapping; * 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. /** By default, use the RandR API for multi-monitor setups.
* Unfortunately, the nVidia binary graphics driver doesn't support * Unfortunately, the nVidia binary graphics driver doesn't support
@ -375,6 +384,9 @@ struct Barbinding {
/** The command which is to be executed for this button. */ /** The command which is to be executed for this button. */
char *command; char *command;
/** If true, the command will be executed after the button is released. */
bool release;
TAILQ_ENTRY(Barbinding) TAILQ_ENTRY(Barbinding)
bindings; bindings;
}; };

View File

@ -133,6 +133,15 @@ typedef enum {
POINTER_WARPING_NONE = 1 POINTER_WARPING_NONE = 1
} warping_t; } 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. * Stores a rectangle, for example the size of a window, the child window etc.
* It needs to be packed so that the compiler will not add any padding bytes. * It needs to be packed so that the compiler will not add any padding bytes.
@ -556,7 +565,9 @@ struct Assignment {
A_ANY = 0, A_ANY = 0,
A_COMMAND = (1 << 0), A_COMMAND = (1 << 0),
A_TO_WORKSPACE = (1 << 1), 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; } type;
/** the criteria to check if a window matches */ /** the criteria to check if a window matches */
@ -566,6 +577,7 @@ struct Assignment {
union { union {
char *command; char *command;
char *workspace; char *workspace;
char *output;
} dest; } dest;
TAILQ_ENTRY(Assignment) TAILQ_ENTRY(Assignment)

View File

@ -74,3 +74,4 @@ extern bool xcursor_supported, xkb_supported;
extern xcb_window_t root; extern xcb_window_t root;
extern struct ev_loop *main_loop; extern struct ev_loop *main_loop;
extern bool only_check_config; extern bool only_check_config;
extern bool force_xinerama;

View File

@ -60,6 +60,9 @@ typedef struct i3_ipc_header {
/** Request the raw last loaded i3 config. */ /** Request the raw last loaded i3 config. */
#define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9 #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 * Messages from i3 to clients
* *
@ -74,6 +77,7 @@ typedef struct i3_ipc_header {
#define I3_IPC_REPLY_TYPE_VERSION 7 #define I3_IPC_REPLY_TYPE_VERSION 7
#define I3_IPC_REPLY_TYPE_BINDING_MODES 8 #define I3_IPC_REPLY_TYPE_BINDING_MODES 8
#define I3_IPC_REPLY_TYPE_CONFIG 9 #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. * Events from i3 to clients. Events have the first bit set high.
@ -101,3 +105,6 @@ typedef struct i3_ipc_header {
/** The shutdown event will be triggered when the ipc shuts down */ /** The shutdown event will be triggered when the ipc shuts down */
#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6) #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)

View File

@ -31,6 +31,10 @@ typedef struct ipc_client {
int num_events; int num_events;
char **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) TAILQ_ENTRY(ipc_client)
clients; clients;
} ipc_client; } ipc_client;

View File

@ -11,6 +11,6 @@
#include <config.h> #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); int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);

View File

@ -47,16 +47,21 @@
break; \ break; \
} }
#define FREE(pointer) \ #define FREE(pointer) \
do { \ do { \
if (pointer != NULL) { \ free(pointer); \
free(pointer); \ pointer = NULL; \
pointer = NULL; \
} \
} while (0) } while (0)
#define CALL(obj, member, ...) obj->member(obj, ##__VA_ARGS__) #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 min(int a, int b);
int max(int a, int b); int max(int a, int b);
bool rect_contains(Rect rect, uint32_t x, uint32_t y); bool rect_contains(Rect rect, uint32_t x, uint32_t y);

View File

@ -43,12 +43,13 @@ void init_dpi(void) {
} }
char *endptr; char *endptr;
dpi = strtol(resource, &endptr, 10); double in_dpi = strtod(resource, &endptr);
if (dpi == LONG_MAX || dpi == LONG_MIN || dpi < 0 || *endptr != '\0' || endptr == resource) { 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); ELOG("Xft.dpi = %s is an invalid number and couldn't be parsed.\n", resource);
dpi = 0; dpi = 0;
goto init_dpi_end; goto init_dpi_end;
} }
dpi = (long)round(in_dpi);
DLOG("Found Xft.dpi = %ld.\n", dpi); DLOG("Found Xft.dpi = %ld.\n", dpi);

View File

@ -22,6 +22,25 @@
* *
*/ */
int ipc_connect(const char *socket_path) { 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); int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd == -1) if (sockfd == -1)
err(EXIT_FAILURE, "Could not create socket"); err(EXIT_FAILURE, "Could not create socket");
@ -31,9 +50,9 @@ int ipc_connect(const char *socket_path) {
struct sockaddr_un addr; struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un)); memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL; 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) 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; return sockfd;
} }

View File

@ -13,6 +13,7 @@
#include <stdint.h> #include <stdint.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h>
#include <i3/ipc.h> #include <i3/ipc.h>
@ -41,14 +42,21 @@ int ipc_recv_message(int sockfd, uint32_t *message_type,
if (n == -1) if (n == -1)
return -1; return -1;
if (n == 0) { 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; read_bytes += n;
} }
if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) { 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; return -3;
} }
@ -61,13 +69,18 @@ int ipc_recv_message(int sockfd, uint32_t *message_type,
*reply = smalloc(*reply_length); *reply = smalloc(*reply_length);
read_bytes = 0; read_bytes = 0;
int n;
while (read_bytes < *reply_length) { 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) if (errno == EINTR || errno == EAGAIN)
continue; continue;
return -1; 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; read_bytes += n;
} }

View File

@ -30,6 +30,7 @@ It tries to start one of the following (in that order):
* jed * jed
* gedit * gedit
* mcedit * mcedit
* gvim
Please dont complain about the order: If the user has any preference, they will Please dont complain about the order: If the user has any preference, they will
have $VISUAL or $EDITOR set. have $VISUAL or $EDITOR set.

View File

@ -44,6 +44,9 @@ It tries to start one of the following (in that order):
* tilix * tilix
* terminix * terminix
* konsole * konsole
* kitty
* guake
* tilda
Please dont complain about the order: If the user has any preference, they will Please dont complain about the order: If the user has any preference, they will
have $TERMINAL set or modified their i3 configuration file. have $TERMINAL set or modified their i3 configuration file.

View File

@ -258,14 +258,16 @@ state RESIZE_SET:
-> RESIZE_WIDTH -> RESIZE_WIDTH
state RESIZE_WIDTH: state RESIZE_WIDTH:
'px' mode_width = 'px', 'ppt'
-> ->
height = number height = number
-> RESIZE_HEIGHT -> RESIZE_HEIGHT
state RESIZE_HEIGHT: state RESIZE_HEIGHT:
'px', end mode_height = 'px', 'ppt'
-> call cmd_resize_set(&width, &height) ->
end
-> call cmd_resize_set(&width, $mode_width, &height, $mode_height)
# rename workspace <name> to <name> # rename workspace <name> to <name>
# rename workspace to <name> # rename workspace to <name>

View File

@ -29,13 +29,15 @@ state INITIAL:
'floating_modifier' -> FLOATING_MODIFIER 'floating_modifier' -> FLOATING_MODIFIER
'default_orientation' -> DEFAULT_ORIENTATION 'default_orientation' -> DEFAULT_ORIENTATION
'workspace_layout' -> WORKSPACE_LAYOUT '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 'hide_edge_borders' -> HIDE_EDGE_BORDERS
'for_window' -> FOR_WINDOW 'for_window' -> FOR_WINDOW
'assign' -> ASSIGN 'assign' -> ASSIGN
'no_focus' -> NO_FOCUS 'no_focus' -> NO_FOCUS
'focus_follows_mouse' -> FOCUS_FOLLOWS_MOUSE 'focus_follows_mouse' -> FOCUS_FOLLOWS_MOUSE
'mouse_warping' -> MOUSE_WARPING 'mouse_warping' -> MOUSE_WARPING
'focus_wrapping' -> FOCUS_WRAPPING
'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING 'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING
'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA 'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA
'disable_randr15', 'disable-randr15' -> DISABLE_RANDR15 'disable_randr15', 'disable-randr15' -> DISABLE_RANDR15
@ -104,25 +106,25 @@ state WORKSPACE_LAYOUT:
layout = 'default', 'stacking', 'stacked', 'tabbed' layout = 'default', 'stacking', 'stacked', 'tabbed'
-> call cfg_workspace_layout($layout) -> call cfg_workspace_layout($layout)
# new_window <normal|1pixel|none> # <default_border|new_window> <normal|1pixel|none>
# new_float <normal|1pixel|none> # <default_floating_border|new_float> <normal|1pixel|none>
state NEW_WINDOW: state DEFAULT_BORDER:
border = 'normal', 'pixel' border = 'normal', 'pixel'
-> NEW_WINDOW_PIXELS -> DEFAULT_BORDER_PIXELS
border = '1pixel', 'none' 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 end
-> call cfg_new_window($windowtype, $border, 2) -> call cfg_default_border($windowtype, $border, 2)
width = number width = number
-> NEW_WINDOW_PIXELS_PX -> DEFAULT_BORDER_PIXELS_PX
state NEW_WINDOW_PIXELS_PX: state DEFAULT_BORDER_PIXELS_PX:
'px' 'px'
-> ->
end end
-> call cfg_new_window($windowtype, $border, &width) -> call cfg_default_border($windowtype, $border, &width)
# hide_edge_borders <none|vertical|horizontal|both|smart> # hide_edge_borders <none|vertical|horizontal|both|smart>
# also hide_edge_borders <bool> for compatibility # also hide_edge_borders <bool> for compatibility
@ -141,7 +143,7 @@ state FOR_WINDOW_COMMAND:
command = string command = string
-> call cfg_for_window($command) -> call cfg_for_window($command)
# assign <criteria> [→] workspace # assign <criteria> [→] [workspace | output] <name>
state ASSIGN: state ASSIGN:
'[' '['
-> call cfg_criteria_init(ASSIGN_WORKSPACE); CRITERIA -> call cfg_criteria_init(ASSIGN_WORKSPACE); CRITERIA
@ -149,10 +151,22 @@ state ASSIGN:
state ASSIGN_WORKSPACE: state ASSIGN_WORKSPACE:
'→' '→'
-> ->
'output'
-> ASSIGN_OUTPUT
'workspace' 'workspace'
-> ->
'number'
-> ASSIGN_WORKSPACE_NUMBER
workspace = string 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> # no_focus <criteria>
state NO_FOCUS: state NO_FOCUS:
@ -197,6 +211,11 @@ state MOUSE_WARPING:
value = 'none', 'output' value = 'none', 'output'
-> call cfg_mouse_warping($value) -> 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 # force_focus_wrapping
state FORCE_FOCUS_WRAPPING: state FORCE_FOCUS_WRAPPING:
value = word value = word
@ -483,12 +502,16 @@ state BAR_WHEEL_DOWN_CMD:
-> call cfg_bar_wheel_down_cmd($command); BAR -> call cfg_bar_wheel_down_cmd($command); BAR
state BAR_BINDSYM: state BAR_BINDSYM:
release = '--release'
->
button = word button = word
-> BAR_BINDSYM_COMMAND -> BAR_BINDSYM_COMMAND
state BAR_BINDSYM_COMMAND: state BAR_BINDSYM_COMMAND:
release = '--release'
->
command = string command = string
-> call cfg_bar_bindsym($button, $command); BAR -> call cfg_bar_bindsym($button, $release, $command); BAR
state BAR_POSITION: state BAR_POSITION:
position = 'top', 'bottom' position = 'top', 'bottom'

View File

@ -1,9 +1,9 @@
#!/bin/zsh #!/bin/zsh
# This script is used to prepare a new release of i3. # This script is used to prepare a new release of i3.
export RELEASE_VERSION="4.13" export RELEASE_VERSION="4.14.1"
export PREVIOUS_VERSION="4.12" export PREVIOUS_VERSION="4.14"
export RELEASE_BRANCH="next" export RELEASE_BRANCH="master"
if [ ! -e "../i3.github.io" ] if [ ! -e "../i3.github.io" ]
then then
@ -232,9 +232,9 @@ echo ""
echo " cd ${TMPDIR}" echo " cd ${TMPDIR}"
echo " sendmail -t < email.txt" echo " sendmail -t < email.txt"
echo "" echo ""
echo "Update milestones on GitHub:" 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 " 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 " Create milestone for the next major version with unset due date"
echo "" echo ""
echo "Announce on:" echo "Announce on:"
echo " twitter" echo " twitter"

View File

@ -910,7 +910,6 @@ static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) {
int remaining = xcb_get_property_value_length(prop_reply); int remaining = xcb_get_property_value_length(prop_reply);
for (int i = 0; i < 5 && remaining > 0; i++) { for (int i = 0; i < 5 && remaining > 0; i++) {
const int len = strnlen(walk, remaining); const int len = strnlen(walk, remaining);
remaining -= len;
switch (i) { switch (i) {
case 0: case 0:
sasprintf((char **)&(xkb_names->rules), "%.*s", len, walk); sasprintf((char **)&(xkb_names->rules), "%.*s", len, walk);
@ -930,6 +929,7 @@ static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) {
} }
DLOG("component %d of _XKB_RULES_NAMES is \"%.*s\"\n", i, len, walk); DLOG("component %d of _XKB_RULES_NAMES is \"%.*s\"\n", i, len, walk);
walk += (len + 1); walk += (len + 1);
remaining -= (len + 1);
} }
free(atom_reply); free(atom_reply);

View File

@ -49,7 +49,7 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
break; break;
} }
bool res = resize_find_tiling_participants(&first, &second, search_direction); bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
if (!res) { if (!res) {
LOG("No second container in this direction found.\n"); LOG("No second container in this direction found.\n");
return false; return false;
@ -241,7 +241,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
* The splitv container will be focused. */ * The splitv container will be focused. */
Con *focused = con->parent; Con *focused = con->parent;
focused = TAILQ_FIRST(&(focused->focus_head)); focused = TAILQ_FIRST(&(focused->focus_head));
con_focus(focused); con_activate(focused);
/* To prevent scrolling from going outside the container (see ticket /* To prevent scrolling from going outside the container (see ticket
* #557), we first check if scrolling is possible at all. */ * #557), we first check if scrolling is possible at all. */
bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL); bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
@ -256,7 +256,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
} }
/* 2: focus this con. */ /* 2: focus this con. */
con_focus(con); con_activate(con);
/* 3: For floating containers, we also want to raise them on click. /* 3: For floating containers, we also want to raise them on click.
* We will skip handling events on floating cons in fullscreen mode */ * We will skip handling events on floating cons in fullscreen mode */

View File

@ -78,14 +78,6 @@
} \ } \
} while (0) } 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, * 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 * signaling that further workspace switching should be done by the calling function
@ -269,14 +261,20 @@ void cmd_criteria_add(I3_CMD, const char *ctype, const char *cvalue) {
match_parse_property(current_match, ctype, cvalue); 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 * Implementation of 'move [window|container] [to] workspace
* next|prev|next_on_output|prev_on_output|current'. * next|prev|next_on_output|prev_on_output|current'.
* *
*/ */
void cmd_move_con_to_workspace(I3_CMD, const char *which) { void cmd_move_con_to_workspace(I3_CMD, const char *which) {
owindow *current;
DLOG("which=%s\n", which); DLOG("which=%s\n", which);
/* We have nothing to move: /* We have nothing to move:
@ -309,10 +307,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) {
return; return;
} }
TAILQ_FOREACH(current, &owindows, owindows) { move_matches_to_workspace(ws);
DLOG("matching: %p / %s\n", current->con, current->con->name);
con_move_to_workspace(current->con, ws, true, false, false);
}
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
@ -324,11 +319,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) {
* *
*/ */
void cmd_move_con_to_workspace_back_and_forth(I3_CMD) { void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
owindow *current; Con *ws = workspace_back_and_forth_get();
Con *ws;
ws = workspace_back_and_forth_get();
if (ws == NULL) { if (ws == NULL) {
yerror("No workspace was previously active."); yerror("No workspace was previously active.");
return; return;
@ -336,10 +327,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) { move_matches_to_workspace(ws);
DLOG("matching: %p / %s\n", current->con, current->con->name);
con_move_to_workspace(current->con, ws, true, false, false);
}
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
@ -358,7 +346,6 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_au
} }
const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
owindow *current;
/* We have nothing to move: /* We have nothing to move:
* when criteria was specified but didn't match any window or * when criteria was specified but didn't match any window or
@ -382,10 +369,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_au
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) { move_matches_to_workspace(ws);
DLOG("matching: %p / %s\n", current->con, current->con->name);
con_move_to_workspace(current->con, ws, true, false, false);
}
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
@ -398,7 +382,6 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_au
*/ */
void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) { 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); const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
owindow *current;
/* We have nothing to move: /* We have nothing to move:
* when criteria was specified but didn't match any window or * when criteria was specified but didn't match any window or
@ -412,7 +395,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no
LOG("should move window to workspace %s\n", which); LOG("should move window to workspace %s\n", which);
/* get the workspace */ /* get the workspace */
Con *output, *workspace = NULL; Con *output, *ws = NULL;
long parsed_num = ws_name_to_number(which); long parsed_num = ws_name_to_number(which);
@ -423,22 +406,19 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no
} }
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) 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); child->num == parsed_num);
if (!workspace) { if (!ws) {
workspace = workspace_get(which, NULL); ws = workspace_get(which, NULL);
} }
if (!no_auto_back_and_forth) 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; HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) { move_matches_to_workspace(ws);
DLOG("matching: %p / %s\n", current->con, current->con->name);
con_move_to_workspace(current->con, workspace, true, false, false);
}
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
@ -511,7 +491,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c
else else
search_direction = D_DOWN; 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) { if (!res) {
LOG("No second container in this direction found.\n"); LOG("No second container in this direction found.\n");
ysuccess(false); ysuccess(false);
@ -525,8 +505,8 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c
LOG("default percentage = %f\n", percentage); LOG("default percentage = %f\n", percentage);
/* resize */ /* resize */
LOG("second->percent = %f\n", second->percent);
LOG("first->percent before = %f\n", first->percent); LOG("first->percent before = %f\n", first->percent);
LOG("second->percent before = %f\n", second->percent);
if (first->percent == 0.0) if (first->percent == 0.0)
first->percent = percentage; first->percent = percentage;
if (second->percent == 0.0) if (second->percent == 0.0)
@ -535,12 +515,10 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c
double new_second_percent = second->percent - ((double)ppt / 100.0); double new_second_percent = second->percent - ((double)ppt / 100.0);
LOG("new_first_percent = %f\n", new_first_percent); LOG("new_first_percent = %f\n", new_first_percent);
LOG("new_second_percent = %f\n", new_second_percent); LOG("new_second_percent = %f\n", new_second_percent);
/* Ensure that the new percentages are positive and greater than /* Ensure that the new percentages are positive. */
* 0.05 to have a reasonable minimum size. */ if (new_first_percent > 0.0 && new_second_percent > 0.0) {
if (definitelyGreaterThan(new_first_percent, 0.05, DBL_EPSILON) && first->percent = new_first_percent;
definitelyGreaterThan(new_second_percent, 0.05, DBL_EPSILON)) { second->percent = new_second_percent;
first->percent += ((double)ppt / 100.0);
second->percent -= ((double)ppt / 100.0);
LOG("first->percent after = %f\n", first->percent); LOG("first->percent after = %f\n", first->percent);
LOG("second->percent after = %f\n", second->percent); LOG("second->percent after = %f\n", second->percent);
} else { } else {
@ -552,19 +530,15 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c
static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int ppt) { static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
LOG("width/height resize\n"); LOG("width/height resize\n");
/* get the appropriate current container (skip stacked/tabbed cons) */ /* get the appropriate current container (skip stacked/tabbed cons) */
while (current->parent->layout == L_STACKED || Con *dummy = NULL;
current->parent->layout == L_TABBED) direction_t search_direction = (strcmp(direction, "width") == 0 ? D_LEFT : D_DOWN);
current = current->parent; bool search_result = resize_find_tiling_participants(&current, &dummy, search_direction, true);
if (search_result == false) {
/* Then further go up until we find one with the matching orientation. */ ysuccess(false);
orientation_t search_orientation = return false;
(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 default percentage */ /* get the default percentage */
int children = con_num_children(current->parent); int children = con_num_children(current->parent);
@ -572,24 +546,6 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way
double percentage = 1.0 / children; double percentage = 1.0 / children;
LOG("default percentage = %f\n", percentage); 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. */ /* Ensure all the other children have a percentage set. */
Con *child; Con *child;
TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
@ -602,24 +558,23 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way
double subtract_percent = ((double)ppt / 100.0) / (children - 1); double subtract_percent = ((double)ppt / 100.0) / (children - 1);
LOG("new_current_percent = %f\n", new_current_percent); LOG("new_current_percent = %f\n", new_current_percent);
LOG("subtract_percent = %f\n", subtract_percent); LOG("subtract_percent = %f\n", subtract_percent);
/* Ensure that the new percentages are positive and greater than /* Ensure that the new percentages are positive. */
* 0.05 to have a reasonable minimum size. */
TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
if (child == current) if (child == current)
continue; 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); 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); ysuccess(false);
return 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"); LOG("Not resizing, already at minimum size\n");
ysuccess(false); ysuccess(false);
return false; return false;
} }
current->percent += ((double)ppt / 100.0); current->percent = new_current_percent;
LOG("current->percent after = %f\n", current->percent); LOG("current->percent after = %f\n", current->percent);
TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
@ -676,31 +631,97 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
} }
/* /*
* 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) {
DLOG("resizing to %ldx%ld px\n", cwidth, cheight); DLOG("resizing to %ld %s x %ld %s\n", cwidth, mode_width, cheight, mode_height);
if (cwidth <= 0 || cheight <= 0) { if (cwidth < 0 || cheight < 0) {
ELOG("Resize failed: dimensions cannot be negative (was %ldx%ld)\n", cwidth, cheight); ELOG("Resize failed: dimensions cannot be negative (was %ld %s x %ld %s)\n", cwidth, mode_width, cheight, mode_height);
return; return;
} }
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
owindow *current; owindow *current;
bool success = true;
TAILQ_FOREACH(current, &owindows, owindows) { TAILQ_FOREACH(current, &owindows, owindows) {
Con *floating_con; Con *floating_con;
if ((floating_con = con_inside_floating(current->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); floating_resize(floating_con, cwidth, cheight);
} else { } 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; cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply ysuccess(success);
ysuccess(true);
} }
/* /*
@ -760,6 +781,7 @@ void cmd_nop(I3_CMD, const char *comment) {
LOG("-------------------------------------------------\n"); LOG("-------------------------------------------------\n");
LOG(" NOP: %s\n", comment); LOG(" NOP: %s\n", comment);
LOG("-------------------------------------------------\n"); LOG("-------------------------------------------------\n");
ysuccess(true);
} }
/* /*
@ -1044,25 +1066,7 @@ void cmd_move_con_to_output(I3_CMD, const char *name) {
TAILQ_FOREACH(current, &owindows, owindows) { TAILQ_FOREACH(current, &owindows, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name); DLOG("matching: %p / %s\n", current->con, current->con->name);
Output *current_output = get_output_for_con(current->con); had_error |= !con_move_to_output_name(current->con, name, true);
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);
} }
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
@ -1132,6 +1136,10 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) {
owindow *current; owindow *current;
TAILQ_FOREACH(current, &owindows, owindows) { TAILQ_FOREACH(current, &owindows, owindows) {
Con *ws = con_get_workspace(current->con); Con *ws = con_get_workspace(current->con);
if (con_is_internal(ws)) {
continue;
}
bool success = workspace_move_to_output(ws, name); bool success = workspace_move_to_output(ws, name);
if (!success) { if (!success) {
ELOG("Failed to move workspace to output.\n"); ELOG("Failed to move workspace to output.\n");
@ -1257,6 +1265,20 @@ void cmd_focus_direction(I3_CMD, const char *direction) {
ysuccess(true); 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'. * Implementation of 'focus tiling|floating|mode_toggle'.
* *
@ -1264,28 +1286,34 @@ void cmd_focus_direction(I3_CMD, const char *direction) {
void cmd_focus_window_mode(I3_CMD, const char *window_mode) { void cmd_focus_window_mode(I3_CMD, const char *window_mode) {
DLOG("window_mode = %s\n", window_mode); DLOG("window_mode = %s\n", window_mode);
Con *ws = con_get_workspace(focused); bool to_floating = false;
if (ws != NULL) { if (strcmp(window_mode, "mode_toggle") == 0) {
if (strcmp(window_mode, "mode_toggle") == 0) { to_floating = !con_inside_floating(focused);
if (con_inside_floating(focused)) } else if (strcmp(window_mode, "floating") == 0) {
window_mode = "tiling"; to_floating = true;
else } else if (strcmp(window_mode, "tiling") == 0) {
window_mode = "floating"; to_floating = false;
}
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_focus(con_descend_focused(current));
break;
}
} }
cmd_output->needs_tree_render = true; Con *ws = con_get_workspace(focused);
// XXX: default reply for now, make this a better reply Con *current;
ysuccess(true); 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;
cmd_focus_force_focus(con_descend_focused(current));
success = true;
break;
}
if (success) {
cmd_output->needs_tree_render = true;
ysuccess(true);
} else {
yerror("Failed to find a %s container in workspace.", to_floating ? "floating" : "tiling");
}
} }
/* /*
@ -1342,13 +1370,6 @@ void cmd_focus(I3_CMD) {
if (!ws) if (!ws)
continue; 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(). */ /* In case this is a scratchpad window, call scratchpad_show(). */
if (ws == __i3_scratch) { if (ws == __i3_scratch) {
scratchpad_show(current->con); scratchpad_show(current->con);
@ -1372,13 +1393,13 @@ void cmd_focus(I3_CMD) {
* So we focus 'current' to make it the currently focused window of * So we focus 'current' to make it the currently focused window of
* the target workspace, then revert focus. */ * the target workspace, then revert focus. */
Con *currently_focused = focused; Con *currently_focused = focused;
con_focus(current->con); cmd_focus_force_focus(current->con);
con_focus(currently_focused); con_activate(currently_focused);
/* Now switch to the workspace, then focus */ /* Now switch to the workspace, then focus */
workspace_show(ws); workspace_show(ws);
LOG("focusing %p / %s\n", current->con, current->con->name); LOG("focusing %p / %s\n", current->con, current->con->name);
con_focus(current->con); con_activate(current->con);
count++; count++;
} }
@ -1490,7 +1511,7 @@ void cmd_move_direction(I3_CMD, const char *direction, long move_px) {
/* the move command should not disturb focus */ /* the move command should not disturb focus */
if (focused != initially_focused) if (focused != initially_focused)
con_focus(initially_focused); con_activate(initially_focused);
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
ysuccess(true); ysuccess(true);
@ -1615,7 +1636,7 @@ void cmd_open(I3_CMD) {
LOG("opening new container\n"); LOG("opening new container\n");
Con *con = tree_open_con(NULL, NULL); Con *con = tree_open_con(NULL, NULL);
con->layout = L_SPLITH; con->layout = L_SPLITH;
con_focus(con); con_activate(con);
y(map_open); y(map_open);
ystr("success"); ystr("success");
@ -2004,7 +2025,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
} }
/* Restore the previous focus since con_attach messes with the focus. */ /* 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; cmd_output->needs_tree_render = true;
ysuccess(true); ysuccess(true);

178
src/con.c
View File

@ -252,6 +252,27 @@ void con_focus(Con *con) {
} }
} }
/*
* 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. * Closes the given container.
* *
@ -795,6 +816,62 @@ Con *con_for_window(Con *con, i3Window *window, Match **store_match) {
return NULL; 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. * Returns the number of children of this container.
* *
@ -994,9 +1071,9 @@ void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode) {
Con *old_focused = focused; Con *old_focused = focused;
if (fullscreen_mode == CF_GLOBAL && cur_ws != con_ws) if (fullscreen_mode == CF_GLOBAL && cur_ws != con_ws)
workspace_show(con_ws); workspace_show(con_ws);
con_focus(con); con_activate(con);
if (fullscreen_mode != CF_GLOBAL && cur_ws != con_ws) if (fullscreen_mode != CF_GLOBAL && cur_ws != con_ws)
con_focus(old_focused); con_activate(old_focused);
con_set_fullscreen_mode(con, fullscreen_mode); con_set_fullscreen_mode(con, fullscreen_mode);
} }
@ -1148,11 +1225,11 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
* new workspace is hidden and it's necessary to immediately switch * new workspace is hidden and it's necessary to immediately switch
* back to the originally-focused workspace. */ * back to the originally-focused workspace. */
Con *old_focus = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head)); 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. */ /* Restore focus if the output's focused workspace has changed. */
if (con_get_workspace(focused) != old_focus) 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 /* 7: when moving to another workspace, we leave the focus on the current
@ -1172,7 +1249,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
/* Set focus only if con was on current workspace before moving. /* Set focus only if con was on current workspace before moving.
* Otherwise we would give focus to some window on different workspace. */ * Otherwise we would give focus to some window on different workspace. */
if (!ignore_focus && source_ws == current_ws) 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, /* 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. */ * delete it so child windows won't be created on the old workspace. */
@ -1300,12 +1377,33 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
* visible workspace on the given output. * 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; Con *ws = NULL;
GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
assert(ws != NULL); assert(ws != NULL);
DLOG("Moving con %p to output %s\n", con, output_primary_name(output)); 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;
} }
/* /*
@ -1735,7 +1833,7 @@ void con_set_layout(Con *con, layout_t layout) {
con->workspace_layout = ws_layout; con->workspace_layout = ws_layout;
DLOG("Setting layout to %d\n", layout); DLOG("Setting layout to %d\n", layout);
con->layout = 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"); DLOG("Creating new split container\n");
/* 1: create a new split container */ /* 1: create a new split container */
Con *new = con_new(NULL, NULL); Con *new = con_new(NULL, NULL);
@ -1746,17 +1844,9 @@ void con_set_layout(Con *con, layout_t layout) {
new->layout = layout; new->layout = layout;
new->last_split_layout = con->last_split_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 */ /* 3: move the existing cons of this workspace below the new con */
Con **focus_order = get_focus_order(con);
DLOG("Moving cons\n"); DLOG("Moving cons\n");
Con *child; Con *child;
while (!TAILQ_EMPTY(&(con->nodes_head))) { while (!TAILQ_EMPTY(&(con->nodes_head))) {
@ -1765,13 +1855,13 @@ void con_set_layout(Con *con, layout_t layout) {
con_attach(child, new, true); con_attach(child, new, true);
} }
set_focus_order(new, focus_order);
free(focus_order);
/* 4: attach the new split container to the workspace */ /* 4: attach the new split container to the workspace */
DLOG("Attaching new split to ws\n"); DLOG("Attaching new split to ws\n");
con_attach(new, con, false); con_attach(new, con, false);
if (old_focused)
con_focus(old_focused);
tree_flatten(croot); tree_flatten(croot);
} }
con_force_split_parents_redraw(con); con_force_split_parents_redraw(con);
@ -1827,6 +1917,10 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
* change to the opposite split layout. */ * change to the opposite split layout. */
if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) { if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) {
layout = parent->last_split_layout; layout = parent->last_split_layout;
/* In case last_split_layout was not initialized… */
if (layout == L_DEFAULT) {
layout = L_SPLITH;
}
} else { } else {
layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH; layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH;
} }
@ -2045,14 +2139,7 @@ bool con_fullscreen_permits_focusing(Con *con) {
/* Allow it only if the container to be focused is contained within the /* Allow it only if the container to be focused is contained within the
* current fullscreen container. */ * current fullscreen container. */
do { return con_has_parent(con, fs);
if (con->parent == fs)
return true;
con = con->parent;
} while (con);
/* Focusing con would hide it behind a fullscreen window, disallow it. */
return false;
} }
/* /*
@ -2294,15 +2381,14 @@ bool con_swap(Con *first, Con *second) {
Con *current_ws = con_get_workspace(old_focus); 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_first = (first == old_focus || con_has_parent(old_focus, first));
const bool focused_within_second = (second == old_focus || con_has_parent(old_focus, second)); 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)) { if (first_fullscreen_mode != CF_NONE) {
DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", first_ws->name); con_disable_fullscreen(first);
return false;
} }
if (second_fullscreen_mode != CF_NONE) {
if (!con_fullscreen_permits_focusing(second_ws)) { con_disable_fullscreen(second);
DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", second_ws->name);
return false;
} }
double first_percent = first->percent; double first_percent = first->percent;
@ -2341,7 +2427,7 @@ bool con_swap(Con *first, Con *second) {
* We don't need to check this for the second container because we've only * We don't need to check this for the second container because we've only
* moved the first one at this point.*/ * moved the first one at this point.*/
if (first_ws != second_ws && focused_within_first) { 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. */ /* Move second to where first has been originally. */
@ -2384,15 +2470,15 @@ bool con_swap(Con *first, Con *second) {
*/ */
if (focused_within_first) { if (focused_within_first) {
if (first_ws == second_ws) { if (first_ws == second_ws) {
con_focus(old_focus); con_activate(old_focus);
} else { } else {
con_focus(con_descend_focused(second)); con_activate(con_descend_focused(second));
} }
} else if (focused_within_second) { } else if (focused_within_second) {
if (first_ws == second_ws) { if (first_ws == second_ws) {
con_focus(old_focus); con_activate(old_focus);
} else { } else {
con_focus(con_descend_focused(first)); con_activate(con_descend_focused(first));
} }
} }
@ -2403,7 +2489,17 @@ bool con_swap(Con *first, Con *second) {
second->percent = first_percent; second->percent = first_percent;
fake->percent = 0.0; fake->percent = 0.0;
SWAP(first_fullscreen_mode, second_fullscreen_mode, fullscreen_mode_t);
swap_end: 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 /* 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 * anything, but we'll better be safe than sorry and just make sure as we'd
* otherwise crash i3. */ * otherwise crash i3. */

View File

@ -227,6 +227,8 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
if (config.workspace_urgency_timer == 0) if (config.workspace_urgency_timer == 0)
config.workspace_urgency_timer = 0.5; config.workspace_urgency_timer = 0.5;
config.focus_wrapping = FOCUS_WRAPPING_ON;
parse_configuration(override_configpath, true); parse_configuration(override_configpath, true);
if (reload) { if (reload) {

View File

@ -197,7 +197,7 @@ CFGFUN(workspace_layout, const char *layout) {
config.default_layout = L_TABBED; 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_style;
int border_width; int border_width;
@ -215,7 +215,8 @@ CFGFUN(new_window, const char *windowtype, const char *border, const long width)
border_width = 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", DLOG("default tiled border style = %d and border width = %d (%d physical px)\n",
border_style, border_width, logical_px(border_width)); border_style, border_width, logical_px(border_width));
config.default_border = border_style; config.default_border = border_style;
@ -264,8 +265,27 @@ CFGFUN(disable_randr15, const char *value) {
config.disable_randr15 = eval_boolstr(value); 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) { 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) { CFGFUN(workspace_back_and_forth, const char *value) {
@ -377,15 +397,35 @@ CFGFUN(color, const char *colorclass, const char *border, const char *background
#undef APPLY_COLORS #undef APPLY_COLORS
} }
CFGFUN(assign, const char *workspace) { CFGFUN(assign_output, const char *output) {
if (match_is_empty(current_match)) { if (match_is_empty(current_match)) {
ELOG("Match is empty, ignoring this assignment\n"); ELOG("Match is empty, ignoring this assignment\n");
return; 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); DLOG("New assignment, using above criteria, to workspace \"%s\".\n", workspace);
Assignment *assignment = scalloc(1, sizeof(Assignment)); Assignment *assignment = scalloc(1, sizeof(Assignment));
match_copy(&(assignment->match), current_match); 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); assignment->dest.workspace = sstrdup(workspace);
TAILQ_INSERT_TAIL(&assignments, assignment, assignments); TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
} }
@ -463,7 +503,7 @@ CFGFUN(bar_modifier, const char *modifier) {
current_bar->modifier = M_NONE; 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) { if (strncasecmp(button, "button", strlen("button")) != 0) {
ELOG("Bindings for a bar can only be mouse bindings, not \"%s\", ignoring.\n", button); ELOG("Bindings for a bar can only be mouse bindings, not \"%s\", ignoring.\n", button);
return; return;
@ -474,16 +514,18 @@ static void bar_configure_binding(const char *button, const char *command) {
ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button); ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button);
return; return;
} }
const bool release_bool = release != NULL;
struct Barbinding *current; struct Barbinding *current;
TAILQ_FOREACH(current, &(current_bar->bar_bindings), bindings) { 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); ELOG("command for button %s was already specified, ignoring.\n", button);
return; return;
} }
} }
struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding)); struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding));
new_binding->release = release_bool;
new_binding->input_code = input_code; new_binding->input_code = input_code;
new_binding->command = sstrdup(command); new_binding->command = sstrdup(command);
TAILQ_INSERT_TAIL(&(current_bar->bar_bindings), new_binding, bindings); TAILQ_INSERT_TAIL(&(current_bar->bar_bindings), new_binding, bindings);
@ -491,16 +533,16 @@ static void bar_configure_binding(const char *button, const char *command) {
CFGFUN(bar_wheel_up_cmd, const char *command) { CFGFUN(bar_wheel_up_cmd, const char *command) {
ELOG("'wheel_up_cmd' is deprecated. Please us 'bindsym button4 %s' instead.\n", 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) { CFGFUN(bar_wheel_down_cmd, const char *command) {
ELOG("'wheel_down_cmd' is deprecated. Please us 'bindsym button5 %s' instead.\n", 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) { CFGFUN(bar_bindsym, const char *button, const char *release, const char *command) {
bar_configure_binding(button, command); bar_configure_binding(button, release, command);
} }
CFGFUN(bar_position, const char *position) { CFGFUN(bar_position, const char *position) {

View File

@ -743,7 +743,7 @@ static char *migrate_config(char *input, off_t size) {
/* read the scripts output */ /* read the scripts output */
int conv_size = 65535; int conv_size = 65535;
char *converted = smalloc(conv_size); char *converted = scalloc(conv_size, 1);
int read_bytes = 0, ret; int read_bytes = 0, ret;
do { do {
if (read_bytes == conv_size) { if (read_bytes == conv_size) {
@ -764,6 +764,7 @@ static char *migrate_config(char *input, off_t size) {
wait(&status); wait(&status);
if (!WIFEXITED(status)) { if (!WIFEXITED(status)) {
fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n"); fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
FREE(converted);
return NULL; return NULL;
} }
@ -778,6 +779,7 @@ static char *migrate_config(char *input, off_t size) {
fprintf(stderr, "# i3 config file (v4)\n"); fprintf(stderr, "# i3 config file (v4)\n");
/* TODO: nag the user with a message to include a hint for i3 in their config file */ /* TODO: nag the user with a message to include a hint for i3 in their config file */
} }
FREE(converted);
return NULL; return NULL;
} }
@ -900,7 +902,9 @@ bool parse_file(const char *f, bool use_nagbar) {
FREE(current_config); FREE(current_config);
current_config = scalloc(stbuf.st_size + 1, 1); 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); rewind(fstr);
bool invalid_sets = false; bool invalid_sets = false;
@ -1061,7 +1065,7 @@ bool parse_file(const char *f, bool use_nagbar) {
int version = detect_version(buf); int version = detect_version(buf);
if (version == 3) { if (version == 3) {
/* We need to convert this v3 configuration */ /* 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) { if (converted != NULL) {
ELOG("\n"); ELOG("\n");
ELOG("****************************************************************\n"); ELOG("****************************************************************\n");

View File

@ -55,10 +55,6 @@ static yajl_callbacks version_callbacks = {
* *
*/ */
void display_running_version(void) { 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); char *pid_from_atom = root_atom_contents("I3_PID", conn, conn_screen);
if (pid_from_atom == NULL) { if (pid_from_atom == NULL) {
/* If I3_PID is not set, the running version is older than 4.2-200. */ /* If I3_PID is not set, the running version is older than 4.2-200. */
@ -71,18 +67,7 @@ void display_running_version(void) {
printf("(Getting version from running i3, press ctrl-c to abort…)"); printf("(Getting version from running i3, press ctrl-c to abort…)");
fflush(stdout); fflush(stdout);
/* TODO: refactor this with the code for sending commands */ int sockfd = ipc_connect(NULL);
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");
if (ipc_send_message(sockfd, 0, I3_IPC_MESSAGE_TYPE_GET_VERSION, if (ipc_send_message(sockfd, 0, I3_IPC_MESSAGE_TYPE_GET_VERSION,
(uint8_t *)"") == -1) (uint8_t *)"") == -1)
err(EXIT_FAILURE, "IPC: write()"); err(EXIT_FAILURE, "IPC: write()");
@ -184,5 +169,4 @@ void display_running_version(void) {
yajl_free(handle); yajl_free(handle);
free(reply); free(reply);
free(pid_from_atom); free(pid_from_atom);
free(socket_path);
} }

View File

@ -318,7 +318,7 @@ void floating_enable(Con *con, bool automatic) {
render_con(con, false); render_con(con, false);
if (set_focus) if (set_focus)
con_focus(con); con_activate(con);
/* Check if we need to re-assign it to a different workspace because of its /* Check if we need to re-assign it to a different workspace because of its
* coordinates and exit if that was done successfully. */ * coordinates and exit if that was done successfully. */
@ -382,7 +382,7 @@ void floating_disable(Con *con, bool automatic) {
con_fix_percent(con->parent); con_fix_percent(con->parent);
if (set_focus) if (set_focus)
con_focus(con); con_activate(con);
floating_set_hint_atom(con, false); floating_set_hint_atom(con, false);
ipc_send_window_event("floating", con); ipc_send_window_event("floating", con);
@ -449,7 +449,8 @@ bool floating_maybe_reassign_ws(Con *con) {
Con *ws = TAILQ_FIRST(&(content->focus_head)); Con *ws = TAILQ_FIRST(&(content->focus_head));
DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name); 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_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; return true;
} }
@ -667,7 +668,7 @@ void floating_resize_window(Con *con, const bool proportional,
/* Custom data structure used to track dragging-related events. */ /* Custom data structure used to track dragging-related events. */
struct drag_x11_cb { struct drag_x11_cb {
ev_check check; ev_prepare prepare;
/* Whether this modal event loop should be exited and with which result. */ /* Whether this modal event loop should be exited and with which result. */
drag_result_t result; drag_result_t result;
@ -686,7 +687,7 @@ struct drag_x11_cb {
const void *extra; 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; struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
xcb_motion_notify_event_t *last_motion_notify = NULL; xcb_motion_notify_event_t *last_motion_notify = NULL;
xcb_generic_event_t *event; xcb_generic_event_t *event;
@ -746,8 +747,10 @@ static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
if (last_motion_notify != (xcb_motion_notify_event_t *)event) if (last_motion_notify != (xcb_motion_notify_event_t *)event)
free(event); free(event);
if (dragloop->result != DRAGGING) if (dragloop->result != DRAGGING) {
free(last_motion_notify);
return; return;
}
} }
if (last_motion_notify == NULL) if (last_motion_notify == NULL)
@ -765,6 +768,8 @@ static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
dragloop->extra); dragloop->extra);
} }
free(last_motion_notify); free(last_motion_notify);
xcb_flush(conn);
} }
/* /*
@ -831,18 +836,18 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
.callback = callback, .callback = callback,
.extra = extra, .extra = extra,
}; };
ev_check *check = &loop.check; ev_prepare *prepare = &loop.prepare;
if (con) if (con)
loop.old_rect = con->rect; loop.old_rect = con->rect;
ev_check_init(check, xcb_drag_check_cb); ev_prepare_init(prepare, xcb_drag_prepare_cb);
check->data = &loop; prepare->data = &loop;
main_set_x11_cb(false); main_set_x11_cb(false);
ev_check_start(main_loop, check); ev_prepare_start(main_loop, prepare);
while (loop.result == DRAGGING) while (loop.result == DRAGGING)
ev_run(main_loop, EVRUN_ONCE); ev_run(main_loop, EVRUN_ONCE);
ev_check_stop(main_loop, check); ev_prepare_stop(main_loop, prepare);
main_set_x11_cb(true); main_set_x11_cb(true);
xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME); xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);

View File

@ -433,7 +433,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) { 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); DLOG("Focusing con = %p\n", con);
workspace_show(ws); workspace_show(ws);
con_focus(con); con_activate(con);
tree_render(); tree_render();
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) { } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
DLOG("Marking con = %p urgent\n", con); DLOG("Marking con = %p urgent\n", con);
@ -774,7 +774,9 @@ static void handle_client_message(xcb_client_message_event_t *event) {
scratchpad_show(con); scratchpad_show(con);
} else { } else {
workspace_show(ws); workspace_show(ws);
con_focus(con); /* Re-set focus, even if unchanged from i3s perspective. */
focused_id = XCB_NONE;
con_activate(con);
} }
} else { } else {
/* Request is from an application. */ /* Request is from an application. */
@ -786,7 +788,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) { 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); DLOG("Focusing con = %p\n", con);
workspace_show(ws); 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))) { } 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); DLOG("Marking con = %p urgent\n", con);
con_set_urgency(con, true); con_set_urgency(con, true);
@ -1243,7 +1245,7 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
if (ws != con_get_workspace(focused)) if (ws != con_get_workspace(focused))
workspace_show(ws); workspace_show(ws);
con_focus(con); con_activate(con);
/* We update focused_id because we dont need to set focus again */ /* We update focused_id because we dont need to set focus again */
focused_id = event->event; focused_id = event->event;
tree_render(); tree_render();
@ -1262,6 +1264,9 @@ static void handle_configure_notify(xcb_configure_notify_event_t *event) {
} }
DLOG("ConfigureNotify for root window 0x%08x\n", event->event); DLOG("ConfigureNotify for root window 0x%08x\n", event->event);
if (force_xinerama) {
return;
}
randr_query_outputs(); randr_query_outputs();
} }

View File

@ -572,6 +572,8 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) {
y(integer, current->input_code); y(integer, current->input_code);
ystr("command"); ystr("command");
ystr(current->command); ystr(current->command);
ystr("release");
y(bool, current->release == B_UPON_KEYRELEASE);
y(map_close); y(map_close);
} }
@ -1046,8 +1048,9 @@ static int add_subscription(void *extra, const unsigned char *s,
memcpy(client->events[event], s, len); memcpy(client->events[event], s, len);
DLOG("client is now subscribed to:\n"); 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("event %s\n", client->events[i]);
}
DLOG("(done)\n"); DLOG("(done)\n");
return 1; return 1;
@ -1099,6 +1102,25 @@ IPC_HANDLER(subscribe) {
yajl_free(p); yajl_free(p);
const char *reply = "{\"success\":true}"; const char *reply = "{\"success\":true}";
ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply); 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);
} }
/* /*
@ -1122,9 +1144,35 @@ IPC_HANDLER(get_config) {
y(free); 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 /* The index of each callback function corresponds to the numeric
* value of the message type (see include/i3/ipc.h) */ * value of the message type (see include/i3/ipc.h) */
handler_t handlers[10] = { handler_t handlers[11] = {
handle_run_command, handle_run_command,
handle_get_workspaces, handle_get_workspaces,
handle_subscribe, handle_subscribe,
@ -1135,6 +1183,7 @@ handler_t handlers[10] = {
handle_get_version, handle_get_version,
handle_get_binding_modes, handle_get_binding_modes,
handle_get_config, handle_get_config,
handle_send_tick,
}; };
/* /*

View File

@ -654,6 +654,6 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
yajl_free(hand); yajl_free(hand);
if (to_focus) { if (to_focus) {
con_focus(to_focus); con_activate(to_focus);
} }
} }

View File

@ -35,9 +35,9 @@ struct rlimit original_rlimit_core;
/** The number of file descriptors passed via socket activation. */ /** The number of file descriptors passed via socket activation. */
int listen_fds; 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(). */ * temporarily for drag_pointer(). */
static struct ev_check *xcb_check; static struct ev_prepare *xcb_prepare;
extern Con *focused; extern Con *focused;
@ -92,29 +92,26 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment
bool xcursor_supported = true; bool xcursor_supported = true;
bool xkb_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 * 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) { 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 xcbs 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) { static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
xcb_flush(conn); /* Process all queued (and possibly new) events before the event loop
} sleeps. */
/*
* 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) {
xcb_generic_event_t *event; xcb_generic_event_t *event;
while ((event = xcb_poll_for_event(conn)) != NULL) { while ((event = xcb_poll_for_event(conn)) != NULL) {
@ -137,6 +134,9 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
free(event); free(event);
} }
/* Flush all queued events to X11. */
xcb_flush(conn);
} }
/* /*
@ -148,12 +148,12 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
void main_set_x11_cb(bool enable) { void main_set_x11_cb(bool enable) {
DLOG("Setting main X11 callback to enabled=%d\n", enable); DLOG("Setting main X11 callback to enabled=%d\n", enable);
if (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. /* Trigger the watcher explicitly to handle all remaining X11 events.
* drag_pointer()s event handler exits in the middle of the loop. */ * 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 { } else {
ev_check_stop(main_loop, xcb_check); ev_prepare_stop(main_loop, xcb_prepare);
} }
} }
@ -174,19 +174,62 @@ static void i3_exit(void) {
fflush(stderr); fflush(stderr);
shm_unlink(shmlogname); shm_unlink(shmlogname);
} }
ipc_shutdown(SHUTDOWN_REASON_EXIT);
unlink(config.ipc_socket_path);
}
/*
* (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_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) * (One-shot) Handler for all signals with default action "Term", see signal(7)
* *
* Unlinks the SHM log and re-raises the signal. * Exits the program gracefully.
* *
*/ */
static void handle_signal(int sig, siginfo_t *info, void *data) { static void handle_term_signal(struct ev_loop *loop, ev_signal *signal, int revents) {
if (*shmlogname != '\0') { /* We exit gracefully here in the sense that cleanup handlers
shm_unlink(shmlogname); * 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);
} }
raise(sig);
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
@ -197,7 +240,6 @@ int main(int argc, char *argv[]) {
bool autostart = true; bool autostart = true;
char *layout_path = NULL; char *layout_path = NULL;
bool delete_layout_path = false; bool delete_layout_path = false;
bool force_xinerama = false;
bool disable_randr15 = false; bool disable_randr15 = false;
char *fake_outputs = NULL; char *fake_outputs = NULL;
bool disable_signalhandler = false; bool disable_signalhandler = false;
@ -550,6 +592,10 @@ int main(int argc, char *argv[]) {
config.ipc_socket_path = sstrdup(config.ipc_socket_path); config.ipc_socket_path = sstrdup(config.ipc_socket_path);
} }
if (config.force_xinerama) {
force_xinerama = true;
}
xcb_void_cookie_t cookie; xcb_void_cookie_t cookie;
cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK}); 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); xcb_generic_error_t *error = xcb_request_check(conn, cookie);
@ -668,7 +714,7 @@ int main(int argc, char *argv[]) {
fake_outputs_init(fake_outputs); fake_outputs_init(fake_outputs);
FREE(fake_outputs); FREE(fake_outputs);
config.fake_outputs = NULL; 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 /* Force Xinerama (for drivers which don't support RandR yet, esp. the
* nVidia binary graphics driver), when specified either in the config * nVidia binary graphics driver), when specified either in the config
* file or on command-line */ * file or on command-line */
@ -720,7 +766,7 @@ int main(int argc, char *argv[]) {
output = get_first_output(); 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); free(pointerreply);
} }
@ -776,15 +822,11 @@ int main(int argc, char *argv[]) {
ewmh_update_desktop_viewport(); ewmh_update_desktop_viewport();
struct ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io)); struct ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io));
xcb_check = scalloc(1, sizeof(struct ev_check)); xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
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_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
ev_io_start(main_loop, xcb_watcher); 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_init(xcb_prepare, xcb_prepare_cb);
ev_prepare_start(main_loop, xcb_prepare); ev_prepare_start(main_loop, xcb_prepare);
@ -854,15 +896,15 @@ int main(int argc, char *argv[]) {
err(EXIT_FAILURE, "pledge"); err(EXIT_FAILURE, "pledge");
#endif #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) if (!disable_signalhandler)
setup_signal_handler(); setup_signal_handler();
else { 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) */ /* Catch all signals with default action "Core", see signal(7) */
if (sigaction(SIGQUIT, &action, NULL) == -1 || if (sigaction(SIGQUIT, &action, NULL) == -1 ||
sigaction(SIGILL, &action, NULL) == -1 || sigaction(SIGILL, &action, NULL) == -1 ||
@ -872,14 +914,7 @@ int main(int argc, char *argv[]) {
ELOG("Could not setup signal handler.\n"); ELOG("Could not setup signal handler.\n");
} }
/* Catch all signals with default action "Term", see signal(7) */ setup_term_handlers();
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");
/* Ignore SIGPIPE to survive errors when an IPC client disconnects /* Ignore SIGPIPE to survive errors when an IPC client disconnects
* while we are sending them a message */ * while we are sending them a message */
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
@ -922,7 +957,7 @@ int main(int argc, char *argv[]) {
free(command); 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() */ * when calling exit() */
atexit(i3_exit); atexit(i3_exit);

View File

@ -259,9 +259,26 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
Con *wm_desktop_ws = NULL; Con *wm_desktop_ws = NULL;
/* If not, check if it is assigned to a specific workspace */ /* 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); 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); nc = con_descend_tiling_focused(assigned_ws);
DLOG("focused on ws %s: %p / %s\n", assigned_ws->name, nc, nc->name); DLOG("focused on ws %s: %p / %s\n", assigned_ws->name, nc, nc->name);
if (nc->type == CT_WORKSPACE) if (nc->type == CT_WORKSPACE)
@ -305,6 +322,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
} else } else
nc = tree_open_con(NULL, cwindow); 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 { } else {
/* M_BELOW inserts the new window as a child of the one which was /* M_BELOW inserts the new window as a child of the one which was
* matched (e.g. dock areas) */ * matched (e.g. dock areas) */
@ -367,7 +388,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
* needed e.g. for LibreOffice Impress multi-monitor * needed e.g. for LibreOffice Impress multi-monitor
* presentations to work out of the box. */ * presentations to work out of the box. */
if (output != NULL) if (output != NULL)
con_move_to_output(nc, output); con_move_to_output(nc, output, false);
con_toggle_fullscreen(nc, CF_OUTPUT); con_toggle_fullscreen(nc, CF_OUTPUT);
} }
fs = NULL; fs = NULL;
@ -625,7 +646,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
* proper window event sequence. */ * proper window event sequence. */
if (set_focus && nc->mapped) { if (set_focus && nc->mapped) {
DLOG("Now setting focus.\n"); DLOG("Now setting focus.\n");
con_focus(nc); con_activate(nc);
} }
tree_render(); tree_render();

View File

@ -118,7 +118,7 @@ static void move_to_output_directed(Con *con, direction_t direction) {
attach_to_workspace(con, ws, direction); attach_to_workspace(con, ws, direction);
/* fix the focus stack */ /* fix the focus stack */
con_focus(con); con_activate(con);
/* force re-painting the indicators */ /* force re-painting the indicators */
FREE(con->deco_render_params); FREE(con->deco_render_params);

View File

@ -99,7 +99,8 @@ void output_push_sticky_windows(Con *to_focus) {
continue; continue;
if (con_is_sticky(current)) { 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);
} }
} }
} }

View File

@ -496,7 +496,7 @@ void init_ws_for_output(Output *output, Con *content) {
Con *ws = create_workspace_on_output(output, content); Con *ws = create_workspace_on_output(output, content);
/* TODO: Set focus in main.c */ /* TODO: Set focus in main.c */
con_focus(ws); con_activate(ws);
} }
/* /*
@ -924,7 +924,7 @@ void randr_query_outputs(void) {
continue; continue;
DLOG("Focusing primary output %s\n", output_primary_name(output)); 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 */ /* render_layout flushes */
@ -987,7 +987,7 @@ void randr_disable_output(Output *output) {
if (next) { if (next) {
DLOG("now focusing next = %p\n", next); DLOG("now focusing next = %p\n", next);
con_focus(next); con_activate(next);
workspace_show(con_get_workspace(next)); workspace_show(con_get_workspace(next));
} }

View File

@ -47,7 +47,7 @@ DRAGGING_CB(resize_callback) {
xcb_flush(conn); 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); DLOG("Find two participants for resizing container=%p in direction=%i\n", other, direction);
Con *first = *current; Con *first = *current;
Con *second = NULL; Con *second = NULL;
@ -74,8 +74,14 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
/* get the counterpart for this resizement */ /* get the counterpart for this resizement */
if (dir_backwards) { if (dir_backwards) {
second = TAILQ_PREV(first, nodes_head, nodes); second = TAILQ_PREV(first, nodes_head, nodes);
if (second == NULL && both_sides == true) {
second = TAILQ_NEXT(first, nodes);
}
} else { } else {
second = TAILQ_NEXT(first, nodes); second = TAILQ_NEXT(first, nodes);
if (second == NULL && both_sides == true) {
second = TAILQ_PREV(first, nodes_head, nodes);
}
} }
if (second == NULL) { if (second == NULL) {

View File

@ -39,7 +39,6 @@ static TAILQ_HEAD(state_head, placeholder_state) state_head =
static xcb_connection_t *restore_conn; static xcb_connection_t *restore_conn;
static struct ev_io *xcb_watcher; static struct ev_io *xcb_watcher;
static struct ev_check *xcb_check;
static struct ev_prepare *xcb_prepare; static struct ev_prepare *xcb_prepare;
static void restore_handle_event(int type, xcb_generic_event_t *event); static void restore_handle_event(int type, xcb_generic_event_t *event);
@ -49,10 +48,6 @@ static void restore_xcb_got_event(EV_P_ struct ev_io *w, int revents) {
} }
static void restore_xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) { 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; xcb_generic_event_t *event;
if (xcb_connection_has_error(restore_conn)) { if (xcb_connection_has_error(restore_conn)) {
@ -77,6 +72,8 @@ static void restore_xcb_check_cb(EV_P_ ev_check *w, int revents) {
free(event); free(event);
} }
xcb_flush(restore_conn);
} }
/* /*
@ -91,7 +88,6 @@ void restore_connect(void) {
/* This is not the initial connect, but a reconnect, most likely /* This is not the initial connect, but a reconnect, most likely
* because our X11 connection was killed (e.g. by a user with xkill. */ * because our X11 connection was killed (e.g. by a user with xkill. */
ev_io_stop(main_loop, xcb_watcher); ev_io_stop(main_loop, xcb_watcher);
ev_check_stop(main_loop, xcb_check);
ev_prepare_stop(main_loop, xcb_prepare); ev_prepare_stop(main_loop, xcb_prepare);
placeholder_state *state; placeholder_state *state;
@ -107,7 +103,6 @@ void restore_connect(void) {
*/ */
xcb_disconnect(restore_conn); xcb_disconnect(restore_conn);
free(xcb_watcher); free(xcb_watcher);
free(xcb_check);
free(xcb_prepare); free(xcb_prepare);
} }
@ -124,15 +119,11 @@ void restore_connect(void) {
} }
xcb_watcher = scalloc(1, sizeof(struct ev_io)); xcb_watcher = scalloc(1, sizeof(struct ev_io));
xcb_check = scalloc(1, sizeof(struct ev_check));
xcb_prepare = scalloc(1, sizeof(struct ev_prepare)); 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_init(xcb_watcher, restore_xcb_got_event, xcb_get_file_descriptor(restore_conn), EV_READ);
ev_io_start(main_loop, xcb_watcher); 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_init(xcb_prepare, restore_xcb_prepare_cb);
ev_prepare_start(main_loop, xcb_prepare); ev_prepare_start(main_loop, xcb_prepare);
} }

View File

@ -123,7 +123,7 @@ void scratchpad_show(Con *con) {
/* use con_descend_tiling_focused to get the last focused /* use con_descend_tiling_focused to get the last focused
* window inside this scratch container in order to * window inside this scratch container in order to
* keep the focus the same within this container */ * 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; return;
} }
} }
@ -205,7 +205,7 @@ void scratchpad_show(Con *con) {
workspace_show(active); workspace_show(active);
} }
con_focus(con_descend_focused(con)); con_activate(con_descend_focused(con));
} }
/* /*

View File

@ -330,6 +330,13 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
DLOG("parent container killed\n"); 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); con_free(con);
/* in the case of floating windows, we already focused another container /* in the case of floating windows, we already focused another container
@ -344,12 +351,12 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
DLOG("focusing %p / %s\n", next, next->name); DLOG("focusing %p / %s\n", next, next->name);
if (next->type == CT_DOCKAREA) { if (next->type == CT_DOCKAREA) {
/* Instead of focusing the dockarea, we need to restore focus to the workspace */ /* 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 { } else {
if (!force_set_focus && con != focused) if (!force_set_focus && con != focused)
DLOG("not changing focus, the container was not focused before\n"); DLOG("not changing focus, the container was not focused before\n");
else else
con_focus(next); con_activate(next);
} }
} else { } else {
DLOG("not focusing because we're not killing anybody\n"); DLOG("not focusing because we're not killing anybody\n");
@ -433,7 +440,7 @@ bool level_up(void) {
/* Skip over floating containers and go directly to the grandparent /* Skip over floating containers and go directly to the grandparent
* (which should always be a workspace) */ * (which should always be a workspace) */
if (focused->parent->type == CT_FLOATING_CON) { if (focused->parent->type == CT_FLOATING_CON) {
con_focus(focused->parent->parent); con_activate(focused->parent->parent);
return true; return true;
} }
@ -444,7 +451,7 @@ bool level_up(void) {
ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n"); ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n");
return false; return false;
} }
con_focus(focused->parent); con_activate(focused->parent);
return true; return true;
} }
@ -469,7 +476,7 @@ bool level_down(void) {
next = TAILQ_FIRST(&(next->focus_head)); next = TAILQ_FIRST(&(next->focus_head));
} }
con_focus(next); con_activate(next);
return true; return true;
} }
@ -560,26 +567,14 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
if (!workspace) if (!workspace)
return false; return false;
workspace_show(workspace); Con *focus = con_descend_tiling_focused(workspace);
if (focus == 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)
focus = con_descend_focused(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; return true;
} }
@ -616,7 +611,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows); TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows);
} }
con_focus(con_descend_focused(next)); con_activate(con_descend_focused(next));
return true; return true;
} }
@ -641,7 +636,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
next = TAILQ_PREV(current, nodes_head, nodes); next = TAILQ_PREV(current, nodes_head, nodes);
if (!next) { 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 /* 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 * when going higher (without wrapping, though). If so, we are done, if
* not, we wrap */ * not, we wrap */
@ -665,7 +660,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
/* 3: focus choice comes in here. at the moment we will go down /* 3: focus choice comes in here. at the moment we will go down
* until we find a window */ * until we find a window */
/* TODO: check for window, atm we only go down as far as possible */ /* 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; return true;
} }
@ -675,7 +670,8 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
* *
*/ */
void tree_next(char way, orientation_t orientation) { 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);
} }
/* /*

View File

@ -500,7 +500,7 @@ ssize_t slurp(const char *path, char **buf) {
size_t n = fread(*buf, 1, stbuf.st_size, f); size_t n = fread(*buf, 1, stbuf.st_size, f);
fclose(f); fclose(f);
if ((ssize_t)n != stbuf.st_size) { 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); free(*buf);
*buf = NULL; *buf = NULL;
return -1; return -1;

View File

@ -459,6 +459,11 @@ static void _workspace_show(Con *workspace) {
y(free); 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_number_of_desktops();
ewmh_update_desktop_names(); ewmh_update_desktop_names();
ewmh_update_desktop_viewport(); ewmh_update_desktop_viewport();
@ -810,9 +815,9 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
/* 2: copy layout from workspace */ /* 2: copy layout from workspace */
split->layout = ws->layout; split->layout = ws->layout;
Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
/* 3: move the existing cons of this workspace below the new con */ /* 3: move the existing cons of this workspace below the new con */
Con **focus_order = get_focus_order(ws);
DLOG("Moving cons\n"); DLOG("Moving cons\n");
while (!TAILQ_EMPTY(&(ws->nodes_head))) { while (!TAILQ_EMPTY(&(ws->nodes_head))) {
Con *child = TAILQ_FIRST(&(ws->nodes_head)); Con *child = TAILQ_FIRST(&(ws->nodes_head));
@ -820,6 +825,9 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
con_attach(child, split, true); con_attach(child, split, true);
} }
set_focus_order(split, focus_order);
free(focus_order);
/* 4: switch workspace layout */ /* 4: switch workspace layout */
ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV; ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
DLOG("split->layout = %d, ws->layout = %d\n", split->layout, ws->layout); DLOG("split->layout = %d, ws->layout = %d\n", split->layout, ws->layout);
@ -830,9 +838,6 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
/* 6: fix the percentages */ /* 6: fix the percentages */
con_fix_percent(ws); con_fix_percent(ws);
if (old_focused)
con_focus(old_focused);
} }
/* /*
@ -887,9 +892,10 @@ Con *workspace_encapsulate(Con *ws) {
new->parent = ws; new->parent = ws;
new->layout = ws->layout; new->layout = ws->layout;
Con **focus_order = get_focus_order(ws);
DLOG("Moving children of workspace %p / %s into container %p\n", DLOG("Moving children of workspace %p / %s into container %p\n",
ws, ws->name, new); ws, ws->name, new);
Con *child; Con *child;
while (!TAILQ_EMPTY(&(ws->nodes_head))) { while (!TAILQ_EMPTY(&(ws->nodes_head))) {
child = TAILQ_FIRST(&(ws->nodes_head)); child = TAILQ_FIRST(&(ws->nodes_head));
@ -897,6 +903,9 @@ Con *workspace_encapsulate(Con *ws) {
con_attach(child, new, true); con_attach(child, new, true);
} }
set_focus_order(new, focus_order);
free(focus_order);
con_attach(new, ws, true); con_attach(new, ws, true);
return new; return new;

10
src/x.c
View File

@ -1227,9 +1227,13 @@ void x_set_name(Con *con, const char *name) {
* *
*/ */
void update_shmlog_atom() { void update_shmlog_atom() {
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, if (*shmlogname == '\0') {
A_I3_SHMLOG_PATH, A_UTF8_STRING, 8, xcb_delete_property(conn, root, A_I3_SHMLOG_PATH);
strlen(shmlogname), shmlogname); } else {
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
strlen(shmlogname), shmlogname);
}
} }
/* /*

View File

@ -38,6 +38,8 @@ binmode STDERR, ':utf8';
# subshell or situations like that. # subshell or situations like that.
AnyEvent::Util::close_all_fds_except(0, 1, 2); AnyEvent::Util::close_all_fds_except(0, 1, 2);
our @CLEANUP;
# convenience wrapper to write to the log file # convenience wrapper to write to the log file
my $log; my $log;
sub Log { say $log "@_" } sub Log { say $log "@_" }
@ -55,6 +57,7 @@ my %options = (
xtrace => 0, xtrace => 0,
coverage => 0, coverage => 0,
restart => 0, restart => 0,
xvfb => 1,
); );
my $keep_xserver_output = 0; my $keep_xserver_output = 0;
@ -64,6 +67,7 @@ my $result = GetOptions(
"valgrind" => \$options{valgrind}, "valgrind" => \$options{valgrind},
"strace" => \$options{strace}, "strace" => \$options{strace},
"xtrace" => \$options{xtrace}, "xtrace" => \$options{xtrace},
"xvfb!" => \$options{xvfb},
"display=s" => \@displays, "display=s" => \@displays,
"parallel=i" => \$parallel, "parallel=i" => \$parallel,
"help|?" => \$help, "help|?" => \$help,
@ -112,6 +116,44 @@ $ENV{PATH} = join(':',
qx(Xephyr -help 2>&1); qx(Xephyr -help 2>&1);
die "Xephyr was not found in your path. Please install Xephyr (xserver-xephyr on Debian)." if $?; 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
# cant implement this in Perl, as Perls 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 = split(/,/, join(',', @displays));
@displays = map { s/ //g; $_ } @displays; @displays = map { s/ //g; $_ } @displays;
@ -379,7 +421,7 @@ sub take_job {
sub cleanup { sub cleanup {
my $exitcode = $?; my $exitcode = $?;
$_->() for our @CLEANUP; $_->() for @CLEANUP;
exit $exitcode; exit $exitcode;
} }
@ -443,6 +485,12 @@ C<latest/strace-for-$test.log>.
Runs i3 under xtrace to trace X11 requests/replies. The output will be Runs i3 under xtrace to trace X11 requests/replies. The output will be
available in C<latest/xtrace-for-$test.log>. 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> =item B<--coverage-testing>
Generates a test coverage report at C<latest/i3-coverage>. Exits i3 cleanly Generates a test coverage report at C<latest/i3-coverage>. Exits i3 cleanly

View File

@ -23,6 +23,7 @@
#include <sys/resource.h> #include <sys/resource.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/wait.h>
#include <libgen.h> #include <libgen.h>
static void uds_connection_cb(EV_P_ ev_io *w, int revents); static void uds_connection_cb(EV_P_ ev_io *w, int revents);

View File

@ -87,7 +87,7 @@ sub start_xserver {
# First get the last used display number, then increment it by one. # First get the last used display number, then increment it by one.
# Effectively falls back to 1 if no X server is running. # 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++; $displaynum++;
say "Starting $parallel Xephyr instances, starting at :$displaynum..."; say "Starting $parallel Xephyr instances, starting at :$displaynum...";
@ -105,7 +105,7 @@ sub start_xserver {
for (1 .. $parallel) { for (1 .. $parallel) {
my $socket = fork_xserver($keep_xserver_output, $displaynum, my $socket = fork_xserver($keep_xserver_output, $displaynum,
'Xephyr', ":$displaynum", '-screen', '1280x800', 'Xephyr', ":$displaynum", '-screen', '1280x800',
'-nolisten', 'tcp'); '-nolisten', 'tcp', '-name', "i3test");
push(@displays, ":$displaynum"); push(@displays, ":$displaynum");
push(@sockets_waiting, $socket); push(@sockets_waiting, $socket);
$displaynum++; $displaynum++;

View File

@ -12,6 +12,7 @@ use AnyEvent::I3;
use List::Util qw(first); use List::Util qw(first);
use Time::HiRes qw(sleep); use Time::HiRes qw(sleep);
use Cwd qw(abs_path); use Cwd qw(abs_path);
use POSIX ':sys_wait_h';
use Scalar::Util qw(blessed); use Scalar::Util qw(blessed);
use SocketActivation; use SocketActivation;
use i3test::Util qw(slurp); use i3test::Util qw(slurp);
@ -37,6 +38,7 @@ our @EXPORT = qw(
cmd cmd
sync_with_i3 sync_with_i3
exit_gracefully exit_gracefully
exit_forcefully
workspace_exists workspace_exists
focused_ws focused_ws
get_socket_path get_socket_path
@ -47,6 +49,8 @@ our @EXPORT = qw(
wait_for_unmap wait_for_unmap
$x $x
kill_all_windows kill_all_windows
events_for
listen_for_binding
); );
=head1 NAME =head1 NAME
@ -121,7 +125,7 @@ END {
} else { } else {
kill(-9, $i3_pid) kill(-9, $i3_pid)
or $tester->BAIL_OUT("could not kill i3"); or $tester->BAIL_OUT("could not kill i3: $!");
waitpid $i3_pid, 0; waitpid $i3_pid, 0;
} }
@ -131,6 +135,22 @@ sub import {
my ($class, %args) = @_; my ($class, %args) = @_;
my $pkg = caller; 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; $i3_autostart = delete($args{i3_autostart}) // 1;
my $i3_config = delete($args{i3_config}) // '-default'; my $i3_config = delete($args{i3_config}) // '-default';
@ -153,10 +173,6 @@ __
strict->import; strict->import;
warnings->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; $cv->recv if $i3_autostart;
@_ = ($class); @_ = ($class);
@ -179,29 +195,11 @@ received, etc.
sub wait_for_event { sub wait_for_event {
my ($timeout, $cb) = @_; my ($timeout, $cb) = @_;
my $cv = AE::cv;
$x->flush; $x->flush;
# unfortunately, there is no constant for this while (defined(my $event = $x->wait_for_event)) {
my $ae_read = 0; return 1 if $cb->($event);
}
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;
} }
=head2 wait_for_map($window) =head2 wait_for_map($window)
@ -348,6 +346,12 @@ sub open_window {
$window->map; $window->map;
wait_for_map($window); 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; return $window;
} }
@ -686,6 +690,7 @@ sub sync_with_i3 {
$_sync_window = open_window( $_sync_window = open_window(
rect => [ -15, -15, 10, 10 ], rect => [ -15, -15, 10, 10 ],
override_redirect => 1, override_redirect => 1,
dont_map => 1,
); );
} }
@ -756,7 +761,7 @@ sub exit_gracefully {
if (!$exited) { if (!$exited) {
kill(9, $pid) 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-,) { if ($socketpath =~ m,^/tmp/i3-test-socket-,) {
@ -767,6 +772,47 @@ sub exit_gracefully {
undef $i3_pid; 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 ]) =head2 get_socket_path([ $cache ])
Gets the socket path from the C<I3_SOCKET_PATH> atom stored on the X11 root Gets the socket path from the C<I3_SOCKET_PATH> atom stored on the X11 root
@ -900,6 +946,86 @@ sub kill_all_windows {
cmd '[title=".*"] kill'; 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 well 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 =head1 AUTHOR
Michael Stapelberg <michael@i3wm.org> Michael Stapelberg <michael@i3wm.org>

View File

@ -5,6 +5,7 @@ use base 'Test::Builder::Module';
our @EXPORT = qw( our @EXPORT = qw(
is_num_children is_num_children
is_num_fullscreen
cmp_float cmp_float
does_i3_live does_i3_live
); );
@ -59,6 +60,25 @@ sub is_num_children {
$tb->is_num($got_num_children, $num_children, $name); $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) =head2 cmp_float($a, $b)
Compares floating point numbers C<$a> and C<$b> and returns true if they differ Compares floating point numbers C<$a> and C<$b> and returns true if they differ

View File

@ -14,13 +14,13 @@ use ExtUtils::PkgConfig;
use Exporter (); use Exporter ();
our @EXPORT = qw( our @EXPORT = qw(
inlinec_connect inlinec_connect
xtest_sync_with
xtest_sync_with_i3
set_xkb_group set_xkb_group
xtest_key_press xtest_key_press
xtest_key_release xtest_key_release
xtest_button_press xtest_button_press
xtest_button_release xtest_button_release
listen_for_binding
start_binding_capture
binding_events binding_events
); );
@ -38,7 +38,7 @@ i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb
# ineffective. # ineffective.
my %sn_config; my %sn_config;
BEGIN { 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}; use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
@ -53,8 +53,12 @@ use Inline C => <<'END_OF_C_CODE';
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xkb.h> #include <xcb/xkb.h>
#include <xcb/xtest.h> #include <xcb/xtest.h>
#include <xcb/xcb_aux.h>
static xcb_connection_t *conn = NULL; 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() { bool inlinec_connect() {
int screen; int screen;
@ -89,9 +93,94 @@ bool inlinec_connect() {
} }
free(usereply); 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; 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 // NOTE: while |group| should be a uint8_t, Inline::C will not define the
// function unless we use an int. // function unless we use an int.
bool set_xkb_group(int group) { bool set_xkb_group(int group) {
@ -170,86 +259,6 @@ sub import {
=cut =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) =head2 set_xkb_group($group)
Changes the current XKB group from the default of 1 to C<$group>, which must be Changes the current XKB group from the default of 1 to C<$group>, which must be
@ -283,6 +292,15 @@ Sends a ButtonRelease event via XTEST, with the specified C<$button>.
Returns false when there was an X11 error, true otherwise. 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 =head1 AUTHOR
Michael Stapelberg <michael@i3wm.org> Michael Stapelberg <michael@i3wm.org>

View File

@ -21,13 +21,6 @@ my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace; 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 # get the output of this workspace
my $tree = $i3->get_tree->recv; my $tree = $i3->get_tree->recv;
my @outputs = @{$tree->{nodes}}; my @outputs = @{$tree->{nodes}};
@ -143,11 +136,11 @@ ok(!eq_hash($new_rect, $original_rect), "Window got repositioned");
$swindow->fullscreen(1); $swindow->fullscreen(1);
sync_with_i3; sync_with_i3;
is(fullscreen_windows(), 1, 'amount of fullscreen windows'); is_num_fullscreen($tmp, 1, 'amount of fullscreen windows');
$window->fullscreen(0); $window->fullscreen(0);
sync_with_i3; 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'); ok($swindow->mapped, 'window mapped after other fullscreen ended');
@ -160,15 +153,15 @@ ok($swindow->mapped, 'window mapped after other fullscreen ended');
$swindow->fullscreen(0); $swindow->fullscreen(0);
sync_with_i3; 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'; 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'; 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 # clean up the workspace so that it will be cleaned when switching away
cmd 'kill' for (@{get_ws_content($tmp)}); cmd 'kill' for (@{get_ws_content($tmp)});
@ -221,18 +214,18 @@ $swindow = open_window;
cmd 'fullscreen'; 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"; cmd "move workspace $tmp";
is(fullscreen_windows($tmp2), 0, 'no fullscreen windows on second ws'); is_num_fullscreen($tmp2, 0, 'no fullscreen windows on second ws');
is(fullscreen_windows($tmp), 1, 'one fullscreen window on first ws'); is_num_fullscreen($tmp, 1, 'one fullscreen window on first ws');
$swindow->fullscreen(0); $swindow->fullscreen(0);
sync_with_i3; sync_with_i3;
# Verify that $swindow was the one that initially remained fullscreen. # 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 # Verify that opening a window with _NET_WM_STATE_FULLSCREEN unfullscreens any
@ -245,14 +238,14 @@ $window = open_window();
cmd "fullscreen"; 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'); is($x->input_focus, $window->id, 'fullscreen window focused');
$swindow = open_window({ $swindow = open_window({
fullscreen => 1 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'); is($x->input_focus, $swindow->id, 'fullscreen window focused');
################################################################################ ################################################################################
@ -263,19 +256,19 @@ $tmp = fresh_workspace;
$window = open_window; $window = open_window;
is($x->input_focus, $window->id, 'window focused'); 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'; cmd 'fullscreen enable';
is($x->input_focus, $window->id, 'window still focused'); 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'; cmd 'fullscreen enable';
is($x->input_focus, $window->id, 'window still focused'); 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); $window->fullscreen(0);
sync_with_i3; 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. # Verify that command fullscreen enable global works and is idempotent.
@ -285,19 +278,19 @@ $tmp = fresh_workspace;
$window = open_window; $window = open_window;
is($x->input_focus, $window->id, 'window focused'); 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'; cmd 'fullscreen enable global';
is($x->input_focus, $window->id, 'window still focused'); 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'; cmd 'fullscreen enable global';
is($x->input_focus, $window->id, 'window still focused'); 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); $window->fullscreen(0);
sync_with_i3; 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. # Verify that command fullscreen disable works and is idempotent.
@ -307,19 +300,19 @@ $tmp = fresh_workspace;
$window = open_window; $window = open_window;
is($x->input_focus, $window->id, 'window focused'); 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); $window->fullscreen(1);
sync_with_i3; 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'; cmd 'fullscreen disable';
is($x->input_focus, $window->id, 'window still focused'); 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'; cmd 'fullscreen disable';
is($x->input_focus, $window->id, 'window still focused'); 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. # Verify that command fullscreen toggle works.
@ -328,15 +321,15 @@ is(fullscreen_windows($tmp), 0, 'still no fullscreen window on workspace');
$tmp = fresh_workspace; $tmp = fresh_workspace;
$window = open_window; $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'; cmd 'fullscreen toggle';
is($x->input_focus, $window->id, 'window still focused'); 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'; cmd 'fullscreen toggle';
is($x->input_focus, $window->id, 'window still focused'); 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 windows fullscreen is disabled when another one is enabled # Verify that a windows fullscreen is disabled when another one is enabled
@ -349,15 +342,15 @@ $window = open_window;
$other = open_window; $other = open_window;
is($x->input_focus, $other->id, 'other window focused'); 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'; cmd 'fullscreen enable';
is($x->input_focus, $other->id, 'other window focused'); 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'; cmd '[id="' . $window->id . '"] fullscreen enable';
is($x->input_focus, $window->id, 'window focused'); 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 # Verify that when a global fullscreen is enabled the window is focused and

View File

@ -304,7 +304,7 @@ for ($type = 1; $type <= 2; $type++) {
cmd 'move right'; cmd 'move right';
cmd '[id="' . $w3->id . '"] focus'; cmd '[id="' . $w3->id . '"] focus';
sync_with_i3; sync_with_i3;
my $ws = get_ws($tmp); $ws = get_ws($tmp);
ok(!$ws->{urgent}, 'urgent flag not set on workspace'); ok(!$ws->{urgent}, 'urgent flag not set on workspace');
############################################################################## ##############################################################################

View File

@ -16,61 +16,25 @@
use i3test; use i3test;
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
################################
# Workspaces requests and events
################################
my $old_ws = get_ws(focused_ws()); my $old_ws = get_ws(focused_ws());
# Events
# We are switching to an empty workpspace from an empty workspace, so we expect # We are switching to an empty workpspace from an empty workspace, so we expect
# to receive "init", "focus", and "empty". # to receive "init", "focus", and "empty".
my $init = AnyEvent->condvar; my @events = events_for(
my $focus = AnyEvent->condvar; sub { cmd 'workspace 2' },
my $empty = AnyEvent->condvar; 'workspace');
$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 $current_ws = get_ws(focused_ws()); my $current_ws = get_ws(focused_ws());
ok($init_event, 'workspace "init" event received'); is(scalar @events, 3, 'Received 3 events');
is($init_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the initted workspace con'); 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($events[1]->{change}, 'focus', 'Second event has change = focus');
is($focus_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con'); is($events[1]->{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]->{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($events[2]->{change}, 'empty', 'Third event has change = empty');
is($empty_event->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con'); is($events[2]->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con');
done_testing; done_testing;

View File

@ -157,9 +157,6 @@ isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
cmd 'focus child'; cmd 'focus child';
is($x->input_focus, $right2->id, 'bottom right window focused again'); 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'; cmd 'focus up';
is($x->input_focus, $right1->id, 'allowed focus up'); is($x->input_focus, $right1->id, 'allowed focus up');
@ -178,9 +175,6 @@ is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
cmd 'focus up'; cmd 'focus up';
is($x->input_focus, $right2->id, 'allowed focus wrap (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 # Same tests when we're in non-global fullscreen mode. It should now be possible
# to focus a container in a different workspace. # to focus a container in a different workspace.
@ -202,9 +196,6 @@ isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
cmd 'focus child'; cmd 'focus child';
is($x->input_focus, $right2->id, 'bottom right window focused again'); 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'; cmd 'focus up';
is($x->input_focus, $right1->id, 'allowed focus up'); is($x->input_focus, $right1->id, 'allowed focus up');
@ -323,6 +314,105 @@ verify_move(2, 'prevented move to workspace by name');
cmd "move to workspace prev"; cmd "move to workspace prev";
verify_move(2, 'prevented move to workspace by position'); 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; done_testing;

View File

@ -21,18 +21,44 @@ use i3test i3_autostart => 0;
sub open_special { sub open_special {
my %args = @_; my %args = @_;
$args{name} //= 'special window'; $args{name} //= 'special window';
$args{wm_class} //= 'special';
# We use dont_map because i3 will not map the window on the current # 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). # workspace. Thus, open_window would time out in wait_for_map (2 seconds).
my $window = open_window( my $window = open_window(
%args, %args,
wm_class => 'special',
dont_map => 1, dont_map => 1,
); );
$window->map; $window->map;
return $window; 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 # start a window and see that it does not get assigned with an empty config
##################################################################### #####################################################################
@ -87,33 +113,67 @@ $window->destroy;
exit_gracefully($pid); exit_gracefully($pid);
##################################################################### #####################################################################
# start a window and see that it gets assigned to a workspace which has content # start a window and see that it gets assigned to a formerly unused
# already, next to the existing node. # numbered workspace
#####################################################################
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);
$tmp = fresh_workspace;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
$workspaces = get_workspace_names;
ok(!("2" ~~ @{$workspaces}), 'workspace number 2 does not exist yet');
$window = open_special;
sync_with_i3;
ok(@{get_ws_content($tmp)} == 0, 'still no containers');
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); $pid = launch_with_config($config);
# initialize the target workspace, then go to a fresh one test_workspace_assignment("targetws");
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');
# 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;
sync_with_i3;
ok(@{get_ws_content($tmp)} == 0, 'still no containers');
ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws');
exit_gracefully($pid); exit_gracefully($pid);
@ -143,8 +203,127 @@ my $content = get_ws($tmp);
ok(@{$content->{nodes}} == 0, 'no tiling cons'); ok(@{$content->{nodes}} == 0, 'no tiling cons');
ok(@{$content->{floating_nodes}} == 1, 'one floating con'); 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); exit_gracefully($pid);
##################################################################### #####################################################################
@ -181,7 +360,7 @@ $tmp = fresh_workspace;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
my @docked = get_dock_clients; my @docked = get_dock_clients;
is(@docked, 0, 'one dock client yet'); is(@docked, 0, 'no dock client yet');
$window = open_special( $window = open_special(
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),

View File

@ -375,7 +375,74 @@ ok(@content == 2, 'two containers opened');
isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed'); isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
isnt($content[1]->{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); 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; done_testing;

View File

@ -95,7 +95,19 @@ EOT
is(launch_get_border($config), 'none', 'no border'); 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; done_testing;

View File

@ -429,7 +429,7 @@ does_i3_live;
################################################################################ ################################################################################
clear_scratchpad; clear_scratchpad;
my $ws = fresh_workspace; $ws = fresh_workspace;
open_window; open_window;
my $scratch = get_focused($ws); my $scratch = get_focused($ws);

View File

@ -190,7 +190,7 @@ exit_gracefully($pid);
# 7: check floating_maximum_size with cmd_size # 7: check floating_maximum_size with cmd_size
################################################################################ ################################################################################
my $config = <<EOT; $config = <<EOT;
# i3 config file (v4) # i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
@ -201,12 +201,12 @@ EOT
$pid = launch_with_config($config); $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 'border none';
cmd 'resize set 101 91'; cmd 'resize set 101 91';
sync_with_i3; sync_with_i3;
my $rect = $window->rect; $rect = $window->rect;
is($rect->{width}, 100, 'width did not exceed maximum width'); is($rect->{width}, 100, 'width did not exceed maximum width');
is($rect->{height}, 90, 'height did not exceed maximum height'); is($rect->{height}, 90, 'height did not exceed maximum height');

View File

@ -24,7 +24,7 @@ use i3test;
sub send_net_active_window { sub send_net_active_window {
my ($id, $source) = @_; my ($id, $source) = @_;
$source = ($source eq 'pager' ? 2 : 0); $source = (((defined $source) && ($source eq 'pager')) ? 2 : 0);
my $msg = pack "CCSLLLLLLL", my $msg = pack "CCSLLLLLLL",
X11::XCB::CLIENT_MESSAGE, # response_type X11::XCB::CLIENT_MESSAGE, # response_type
@ -137,7 +137,7 @@ is($x->input_focus, $win3->id, 'window 3 still focused');
# is received. # is received.
################################################################################ ################################################################################
my $scratch = open_window; $scratch = open_window;
is($x->input_focus, $scratch->id, 'to-scratchpad window has focus'); is($x->input_focus, $scratch->id, 'to-scratchpad window has focus');

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