Merge branch 'next' into master
This commit is contained in:
commit
cdf9a8f77e
|
@ -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
|
|
@ -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_386} linux32 ./travis/debian-build.sh deb/debian-i386/DIST
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_UBUNTU_386} linux32 ./travis/debian-build.sh deb/ubuntu-i386/DIST
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/clang-analyze.sh
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/docs.sh
|
||||
- ./travis/skip-pkg.sh || travis/prep-bintray.sh
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ use AnyEvent::Socket;
|
|||
use AnyEvent;
|
||||
use Encode;
|
||||
use Scalar::Util qw(tainted);
|
||||
use Carp;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
|
@ -98,11 +99,12 @@ use constant TYPE_GET_BAR_CONFIG => 6;
|
|||
use constant TYPE_GET_VERSION => 7;
|
||||
use constant TYPE_GET_BINDING_MODES => 8;
|
||||
use constant TYPE_GET_CONFIG => 9;
|
||||
use constant TYPE_SEND_TICK => 10;
|
||||
|
||||
our %EXPORT_TAGS = ( 'all' => [
|
||||
qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
|
||||
TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION
|
||||
TYPE_GET_BINDING_MODES TYPE_GET_CONFIG)
|
||||
TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK)
|
||||
] );
|
||||
|
||||
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
|
||||
|
@ -119,6 +121,7 @@ my %events = (
|
|||
barconfig_update => ($event_mask | 4),
|
||||
binding => ($event_mask | 5),
|
||||
shutdown => ($event_mask | 6),
|
||||
tick => ($event_mask | 7),
|
||||
_error => 0xFFFFFFFF,
|
||||
);
|
||||
|
||||
|
@ -187,7 +190,7 @@ sub new {
|
|||
# We use getpwuid() instead of $ENV{HOME} because the latter is tainted
|
||||
# and thus produces warnings when running tests with perl -T
|
||||
my $home = (getpwuid($<))[7];
|
||||
die "Could not get home directory" unless $home and -d $home;
|
||||
confess "Could not get home directory" unless $home and -d $home;
|
||||
$path =~ s/~/$home/g;
|
||||
}
|
||||
|
||||
|
@ -331,9 +334,9 @@ scalar), if specified.
|
|||
sub message {
|
||||
my ($self, $type, $content) = @_;
|
||||
|
||||
die "No message type specified" unless defined($type);
|
||||
confess "No message type specified" unless defined($type);
|
||||
|
||||
die "No connection to i3" unless defined($self->{ipchdl});
|
||||
confess "No connection to i3" unless defined($self->{ipchdl});
|
||||
|
||||
my $payload = "";
|
||||
if ($content) {
|
||||
|
@ -374,7 +377,7 @@ sub _ensure_connection {
|
|||
|
||||
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
|
||||
|
@ -518,6 +521,18 @@ sub 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)
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
4.14.1-non-git
|
||||
4.15-non-git
|
||||
|
|
|
@ -78,6 +78,11 @@ EXTRA_DIST = \
|
|||
AnyEvent-I3/t/manifest.t \
|
||||
AnyEvent-I3/t/pod-coverage.t \
|
||||
AnyEvent-I3/t/pod.t \
|
||||
contrib/dump-asy.pl \
|
||||
contrib/gtk-tree-watch.pl \
|
||||
contrib/i3-wsbar \
|
||||
contrib/per-workspace-layout.pl \
|
||||
contrib/trivial-bar-script.sh \
|
||||
docs/asciidoc-git.conf \
|
||||
docs/bigpicture.png \
|
||||
docs/i3-pod2html \
|
||||
|
@ -113,7 +118,7 @@ EXTRA_DIST = \
|
|||
I3_VERSION \
|
||||
LICENSE \
|
||||
PACKAGE-MAINTAINER \
|
||||
RELEASE-NOTES-4.14.1 \
|
||||
RELEASE-NOTES-4.15 \
|
||||
generate-command-parser.pl \
|
||||
parser-specs/commands.spec \
|
||||
parser-specs/config.spec \
|
||||
|
|
|
@ -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).
|
|
@ -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 i3’s byte order in memory-safe languages
|
||||
• docs/ipc: document the GET_CONFIG request
|
||||
• docs/userguide: fix formatting issue
|
||||
• docs/userguide: explain why Mod4 is usually preferred as a modifier
|
||||
• docs/userguide: use more idiomatic english (full-size, so-called)
|
||||
• docs/userguide: switch from removed goto command to focus
|
||||
• docs/userguide: mention <criteria> in focus
|
||||
• docs/userguide: remove outdated 2013 last-modified date
|
||||
• dump-asy: add prerequisite checks
|
||||
• dump-asy: fix warnings about empty container names
|
||||
• i3-dump-log: enable shmlog on demand
|
||||
• i3-sensible-terminal: add “kitty”, “guake”, “tilda”
|
||||
• i3-sensible-editor: add “gvim”
|
||||
• i3bar: add --release flag for bindsym in bar blocks
|
||||
• i3bar: add relative coordinates in JSON for click events
|
||||
• ipc: rename COMMAND to RUN_COMMAND for consistency
|
||||
• ipc: implement tick event for less flaky tests
|
||||
• ipc: add error reply to “focus <window_mode>”
|
||||
• ipc: send success response for nop
|
||||
• default config: add $mod+r to toggle resize mode
|
||||
• default config: use variables for workspace names to avoid repetition
|
||||
• introduce “assign <criteria> [→] [workspace] [number] <workspace>”
|
||||
• introduce “assign <criteria> [→] output left|right|up|down|primary|<output>”
|
||||
• introduce a “focus_wrapping” option (subsumes “force_focus_wrapping”)
|
||||
• introduce percentage point resizing for floating containers:
|
||||
“resize set <width> [px | ppt] <height> [px | ppt]”
|
||||
• introduce “resize set <width> ppt <height> ppt” for tiling windows
|
||||
• rename “new_window” and “new_float” to “default_border” and
|
||||
“default_floating_border” (the old names keep working)
|
||||
• output names (e.g. “DP2”) can now be used as synonyms for monitor names
|
||||
(e.g. “Dell UP2414Q”).
|
||||
• the “swap” command now works with fullscreen windows
|
||||
• raise floating windows to top when they are focused programmatically
|
||||
• _NET_ACTIVE_WINDOW: invalidate focus to force SetInputFocus call
|
||||
• make focus handling consistent when changing focus between outputs
|
||||
• round non-integer Xft.dpi values
|
||||
• tiling resize: remove minimum size
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• i3bar: fix various memory leaks
|
||||
• i3bar: fix crash when no status_command is provided
|
||||
• fix uninitialized variables in init_dpi_end, tree_restore
|
||||
• fix incorrectly set up signal handling
|
||||
• fix “swap” debug log message
|
||||
• fix crash when specifying invalid con_id for “swap”
|
||||
• fix crash upon restart with window marks
|
||||
• fix crash when config file does not end in a newline
|
||||
• fix crash in append_layout
|
||||
• fix crash in layout toggle command
|
||||
• fix crash when switching monitors
|
||||
• fix use-after-free in randr_init error path
|
||||
• fix move accidentally moving windows across outputs
|
||||
• fix crash when floating window is tiled while being resized
|
||||
• fix out-of-bounds memory read
|
||||
• fix memory leak when config conversion fails
|
||||
• fix layout toggle split, which didn’t work until enabling tabbed/stack mode
|
||||
once
|
||||
• move XCB event handling into xcb_prepare_cb
|
||||
• avert endless loop on unexpected EOF in ipc messages
|
||||
• perform proper cleanup for signals with Term action
|
||||
• don’t match containers in the scratchpad with criteria
|
||||
• fix “workspace show” related issues
|
||||
• fix config file conversion with long variable names
|
||||
• fix config file conversion memory initialization
|
||||
• prevent access of freed workspace in _workspace_show
|
||||
• disable fullscreen when required when programmatically focusing windows
|
||||
• free last_motion_notify
|
||||
• don’t raise floating windows when focused because of focus_follows_mouse
|
||||
• correctly set EWMH atoms when closing a workspace
|
||||
• don’t raise floating windows when workspace is shown
|
||||
• keep focus order when encapsulating workspaces
|
||||
• validate layout files before loading
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
Alex Lu, Ben Creasy, Bennett Piater, Cast, chressie, clonejo, Dan Elkouby,
|
||||
Daniel Mueller, DebianWall, Diki Ananta, Edward Betts, hwangcc23, Ingo Bürk,
|
||||
Jan Alexander Steffens, Johannes Lange, Kent Fredric, livanh, Martin
|
||||
T. H. Sandsmark, Michael Siegel, Orestis Floros, Pallav Agarwal, Pawel
|
||||
S. Veselov, Pietro Cerutti, Theo Buehler, Thomas Praxl, Tyler Brazier,
|
||||
Vladimir Panteleev, walker0643, Wes Roberts, xzfc
|
||||
|
||||
-- Michael Stapelberg, 2018-03-10
|
|
@ -2,7 +2,7 @@
|
|||
# Run autoreconf -fi to generate a configure script from this file.
|
||||
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([i3], [4.14.1], [https://github.com/i3/i3/issues])
|
||||
AC_INIT([i3], [4.15], [https://github.com/i3/i3/issues])
|
||||
# For AX_EXTEND_SRCDIR
|
||||
AX_ENABLE_BUILDDIR
|
||||
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
|
||||
|
|
|
@ -13,7 +13,14 @@ use warnings;
|
|||
use Data::Dumper;
|
||||
use AnyEvent::I3;
|
||||
use File::Temp;
|
||||
use File::Basename;
|
||||
use v5.10;
|
||||
use IPC::Cmd qw[can_run];
|
||||
|
||||
# prerequisites check so we can be specific about failures caused
|
||||
# by not having these tools in the path
|
||||
can_run('asy') or die 'Please install asymptote';
|
||||
can_run('gv') or die 'Please install gv';
|
||||
|
||||
my $i3 = i3();
|
||||
|
||||
|
@ -30,7 +37,7 @@ sub dump_node {
|
|||
|
||||
my $o = ($n->{orientation} eq 'none' ? "u" : ($n->{orientation} eq 'horizontal' ? "h" : "v"));
|
||||
my $w = (defined($n->{window}) ? $n->{window} : "N");
|
||||
my $na = $n->{name};
|
||||
my $na = ($n->{name} or "[Empty]");
|
||||
$na =~ s/#/\\#/g;
|
||||
$na =~ s/\$/\\\$/g;
|
||||
$na =~ s/&/\\&/g;
|
||||
|
@ -38,7 +45,7 @@ sub dump_node {
|
|||
$na =~ s/~/\\textasciitilde{}/g;
|
||||
my $type = 'leaf';
|
||||
if (!defined($n->{window})) {
|
||||
$type = $n->{orientation} . '-split';
|
||||
$type = $n->{layout};
|
||||
}
|
||||
my $name = qq|``$na'' ($type)|;
|
||||
|
||||
|
@ -75,4 +82,5 @@ say $tmp "draw(n" . $root->{id} . ", (0, 0));";
|
|||
close($tmp);
|
||||
my $rep = "$tmp";
|
||||
$rep =~ s/asy$/eps/;
|
||||
system("cd /tmp && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep");
|
||||
my $tmp_dir = dirname($rep);
|
||||
system("cd $tmp_dir && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep");
|
||||
|
|
|
@ -14,6 +14,7 @@ use warnings;
|
|||
use AnyEvent;
|
||||
use AnyEvent::I3;
|
||||
use v5.10;
|
||||
use utf8;
|
||||
|
||||
my %layouts = (
|
||||
'4' => 'tabbed',
|
||||
|
|
|
@ -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
|
||||
|
||||
* 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: Fix border rendering (border lines were "cutting" through)
|
||||
* Bugfix: Raise floating windows immediately when dragging/resizing
|
||||
* Bugfix: Make focus switching work accross outputs again
|
||||
* Bugfix: Make focus switching work across outputs again
|
||||
* Bugfix: migration-script: handle resize top/bottom correctly
|
||||
* Bugfix: Fix focus issue when moving containers to workspaces
|
||||
* Bugfix: Warp cursor when changing outputs again
|
||||
|
|
|
@ -1,2 +1,7 @@
|
|||
debian/tmp/etc
|
||||
debian/tmp/usr
|
||||
contrib/dump-asy.pl usr/share/doc/i3-wm/examples/
|
||||
contrib/gtk-tree-watch.pl usr/share/doc/i3-wm/examples/
|
||||
contrib/i3-wsbar usr/share/doc/i3-wm/examples/
|
||||
contrib/per-workspace-layout.pl usr/share/doc/i3-wm/examples/
|
||||
contrib/trivial-bar-script.sh usr/share/doc/i3-wm/examples/
|
||||
|
|
|
@ -17,5 +17,9 @@ override_dh_auto_configure:
|
|||
# The default is /usr/share/doc/i3
|
||||
dh_auto_configure -- --docdir=/usr/share/doc/i3-wm
|
||||
|
||||
override_dh_builddeb:
|
||||
# bintray does not support xz currently.
|
||||
dh_builddeb -- -Zgzip
|
||||
|
||||
%:
|
||||
dh $@ --parallel --builddirectory=build --with=autoreconf
|
||||
|
|
|
@ -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
|
||||
information might be necessary to completely diagnose the problem.
|
||||
|
||||
When debugging with us in IRC, be prepared to use a so called nopaste service
|
||||
When debugging with us in IRC, be prepared to use a so-called nopaste service
|
||||
such as https://pastebin.com because pasting large amounts of text in IRC
|
||||
sometimes leads to incomplete lines (servers have line length limitations) or
|
||||
flood kicks.
|
||||
|
|
|
@ -177,7 +177,8 @@ separator_block_width::
|
|||
markup::
|
||||
A string that indicates how the text of the block should be parsed. Set to
|
||||
+"pango"+ to use https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup].
|
||||
Set to +"none"+ to not use any markup (default).
|
||||
Set to +"none"+ to not use any markup (default). Pango markup only works
|
||||
if you use a pango font.
|
||||
|
||||
If you want to put in your own entries into a block, prefix the key with an
|
||||
underscore (_). i3bar will ignore all keys it doesn’t understand, and prefixing
|
||||
|
@ -236,6 +237,11 @@ x, y::
|
|||
X11 root window coordinates where the click occurred
|
||||
button::
|
||||
X11 button ID (for example 1 to 3 for left/middle/right mouse button)
|
||||
relative_x, relative_y::
|
||||
Coordinates where the click occurred, with respect to the top left corner
|
||||
of the block
|
||||
width, height::
|
||||
Width and height (in px) of the block
|
||||
|
||||
*Example*:
|
||||
------------------------------------------
|
||||
|
@ -244,6 +250,10 @@ button::
|
|||
"instance": "eth0",
|
||||
"button": 1,
|
||||
"x": 1320,
|
||||
"y": 1400
|
||||
"y": 1400,
|
||||
"relative_x": 12,
|
||||
"relative_y": 8,
|
||||
"width": 50,
|
||||
"height": 22
|
||||
}
|
||||
------------------------------------------
|
||||
|
|
45
docs/ipc
45
docs/ipc
|
@ -64,6 +64,7 @@ to do that).
|
|||
| 7 | +GET_VERSION+ | <<_version_reply,VERSION>> | Gets the i3 version.
|
||||
| 8 | +GET_BINDING_MODES+ | <<_binding_modes_reply,BINDING_MODES>> | Gets the names of all currently configured binding modes.
|
||||
| 9 | +GET_CONFIG+ | <<_config_reply,CONFIG>> | Returns the last loaded i3 config.
|
||||
| 10 | +SEND_TICK+ | <<_tick_reply,TICK>> | Sends a tick event with the specified payload.
|
||||
|======================================================
|
||||
|
||||
So, a typical message could look like this:
|
||||
|
@ -126,6 +127,8 @@ BINDING_MODES (8)::
|
|||
Reply to the GET_BINDING_MODES message.
|
||||
GET_CONFIG (9)::
|
||||
Reply to the GET_CONFIG message.
|
||||
TICK (10)::
|
||||
Reply to the SEND_TICK message.
|
||||
|
||||
[[_command_reply]]
|
||||
=== COMMAND reply
|
||||
|
@ -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" }
|
||||
-------------------
|
||||
|
||||
[[_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
|
||||
|
||||
|
@ -694,6 +710,10 @@ binding (5)::
|
|||
mouse
|
||||
shutdown (6)::
|
||||
Sent when the ipc shuts down because of a restart or exit by user command
|
||||
tick (7)::
|
||||
Sent when the ipc client subscribes to the tick event (with +"first":
|
||||
true+) or when any ipc client sends a SEND_TICK message (with +"first":
|
||||
false+).
|
||||
|
||||
*Example:*
|
||||
--------------------------------------------------------------------
|
||||
|
@ -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)
|
||||
|
||||
[[libraries]]
|
||||
|
@ -881,6 +922,7 @@ C++::
|
|||
* https://github.com/drmgc/i3ipcpp
|
||||
Go::
|
||||
* https://github.com/mdirkse/i3ipc-go
|
||||
* https://github.com/i3/go-i3
|
||||
JavaScript::
|
||||
* https://github.com/acrisci/i3ipc-gjs
|
||||
Lua::
|
||||
|
@ -958,3 +1000,6 @@ detect the byte order i3 is using:
|
|||
payload. Then, receive the pending +COMMAND+ message reply in big endian.
|
||||
|
||||
5. From here on out, send/receive all messages using the detected byte order.
|
||||
|
||||
Find an example implementation of this technique in
|
||||
https://github.com/i3/go-i3/blob/master/byteorder.go
|
||||
|
|
|
@ -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
|
||||
run the tests on a separate X server instance (using Xephyr).
|
||||
|
||||
Xephyr will open a window where you can inspect the running test. You can run
|
||||
the tests without an X session with Xvfb, such as with +xvfb-run
|
||||
./complete-run+. This will also speed up the tests significantly especially on
|
||||
machines without a powerful video card.
|
||||
Xephyr will open a window where you can inspect the running test. By default,
|
||||
tests are run under Xvfb.
|
||||
|
||||
.Example invocation of +complete-run.pl+
|
||||
---------------------------------------
|
||||
|
|
123
docs/userguide
123
docs/userguide
|
@ -1,7 +1,6 @@
|
|||
i3 User’s Guide
|
||||
===============
|
||||
Michael Stapelberg <michael@i3wm.org>
|
||||
March 2013
|
||||
|
||||
This document contains all the information you need to configure and use the i3
|
||||
window manager. If it does not, please check https://www.reddit.com/r/i3wm/
|
||||
|
@ -11,7 +10,7 @@ mailing list.
|
|||
== Default keybindings
|
||||
|
||||
For the "too long; didn’t read" people, here is an overview of the default
|
||||
keybindings (click to see the full size image):
|
||||
keybindings (click to see the full-size image):
|
||||
|
||||
*Keys to use with $mod (Alt):*
|
||||
|
||||
|
@ -35,7 +34,8 @@ above, just decline i3-config-wizard’s offer and base your config on
|
|||
|
||||
Throughout this guide, the keyword +$mod+ will be used to refer to the
|
||||
configured modifier. This is the Alt key (+Mod1+) by default, with the Windows
|
||||
key (+Mod4+) being a popular alternative.
|
||||
key (+Mod4+) being a popular alternative that largely prevents conflicts with
|
||||
application-defined shortcuts.
|
||||
|
||||
=== Opening terminals and moving around
|
||||
|
||||
|
@ -196,7 +196,7 @@ out to be complicated to use (snapping), understand and implement.
|
|||
|
||||
=== The tree consists of Containers
|
||||
|
||||
The building blocks of our tree are so called +Containers+. A +Container+ can
|
||||
The building blocks of our tree are so-called +Containers+. A +Container+ can
|
||||
host a window (meaning an X11 window, one that you can actually see and use,
|
||||
like a browser). Alternatively, it could contain one or more +Containers+. A
|
||||
simple example is the workspace: When you start i3 with a single monitor, a
|
||||
|
@ -509,7 +509,7 @@ mode "$mode_launcher" {
|
|||
=== The floating modifier
|
||||
|
||||
To move floating windows with your mouse, you can either grab their titlebar
|
||||
or configure the so called floating modifier which you can then press and
|
||||
or configure the so-called floating modifier which you can then press and
|
||||
click anywhere in the window itself to move it. The most common setup is to
|
||||
use the same key you use for managing windows (Mod1 for example). Then
|
||||
you can press Mod1, click into a window using your left mouse button, and drag
|
||||
|
@ -585,23 +585,26 @@ workspace_layout default|stacking|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
|
||||
+normal+. Note that new_float applies only to windows which are starting out as
|
||||
+normal+. Note that default_floating_border applies only to windows which are starting out as
|
||||
floating windows, e.g., dialog windows, but not windows that are floated later on.
|
||||
|
||||
*Syntax*:
|
||||
---------------------------------------------
|
||||
new_window normal|none|pixel
|
||||
new_window normal|pixel <px>
|
||||
new_float normal|none|pixel
|
||||
new_float normal|pixel <px>
|
||||
default_border normal|none|pixel
|
||||
default_border normal|pixel <px>
|
||||
default_floating_border normal|none|pixel
|
||||
default_floating_border normal|pixel <px>
|
||||
---------------------------------------------
|
||||
|
||||
Please note that +new_window+ and +new_float+ have been deprecated in favor of the above options
|
||||
and will be removed in a future release. We strongly recommend using the new options instead.
|
||||
|
||||
*Example*:
|
||||
---------------------
|
||||
new_window pixel
|
||||
default_border pixel
|
||||
---------------------
|
||||
|
||||
The "normal" and "pixel" border styles support an optional border width in
|
||||
|
@ -609,11 +612,11 @@ pixels:
|
|||
|
||||
*Example*:
|
||||
---------------------
|
||||
# The same as new_window none
|
||||
new_window pixel 0
|
||||
# The same as default_border none
|
||||
default_border pixel 0
|
||||
|
||||
# A 3 px border
|
||||
new_window pixel 3
|
||||
default_border pixel 3
|
||||
---------------------
|
||||
|
||||
|
||||
|
@ -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), you’d need to have
|
||||
to match on 'Firefox' in this case.
|
||||
|
||||
You can also assign a window to show up on a specific output. You can use RandR
|
||||
names such as +VGA1+ or names relative to the output with the currently focused
|
||||
workspace such as +left+ and +down+.
|
||||
|
||||
Assignments are processed by i3 in the order in which they appear in the config
|
||||
file. The first one which matches the window wins and later assignments are not
|
||||
considered.
|
||||
|
||||
*Syntax*:
|
||||
------------------------------------------------------------
|
||||
assign <criteria> [→] [workspace] <workspace>
|
||||
assign <criteria> [→] [workspace] [number] <workspace>
|
||||
assign <criteria> [→] output left|right|up|down|primary|<output>
|
||||
------------------------------------------------------------
|
||||
|
||||
*Examples*:
|
||||
|
@ -783,11 +791,28 @@ assign [class="^URxvt$"] → 2
|
|||
# Assignment to a named workspace
|
||||
assign [class="^URxvt$"] → work
|
||||
|
||||
# Assign to the workspace with number 2, regardless of name
|
||||
assign [class="^URxvt$"] → number 2
|
||||
|
||||
# You can also specify a number + name. If the workspace with number 2 exists, assign will skip the text part.
|
||||
assign [class="^URxvt$"] → number "2: work"
|
||||
|
||||
# Start urxvt -name irssi
|
||||
assign [class="^URxvt$" instance="^irssi$"] → 3
|
||||
|
||||
# Assign urxvt to the output right of the current one
|
||||
assign [class="^URxvt$"] → output right
|
||||
|
||||
# Assign urxvt to the primary output
|
||||
assign [class="^URxvt$"] → output primary
|
||||
----------------------
|
||||
|
||||
Note that the arrow is not required, it just looks good :-). If you decide to
|
||||
Note that you might not have a primary output configured yet. To do so, run:
|
||||
-------------------------
|
||||
xrandr --output <output> --primary
|
||||
-------------------------
|
||||
|
||||
Also, the arrow is not required, it just looks good :-). If you decide to
|
||||
use it, it has to be a UTF-8 encoded arrow, not `->` or something like that.
|
||||
|
||||
To get the class and instance, you can use +xprop+. After clicking on the
|
||||
|
@ -1033,26 +1058,39 @@ popup_during_fullscreen smart
|
|||
|
||||
=== Focus wrapping
|
||||
|
||||
When being in a tabbed or stacked container, the first container will be
|
||||
focused when you use +focus down+ on the last container -- the focus wraps. If
|
||||
however there is another stacked/tabbed container in that direction, focus will
|
||||
be set on that container. This is the default behavior so you can navigate to
|
||||
all your windows without having to use +focus parent+.
|
||||
By default, when in a container with several windows or child containers, the
|
||||
opposite window will be focused when trying to move the focus over the edge of
|
||||
a container (and there are no other containers in that direction) -- the focus
|
||||
wraps.
|
||||
|
||||
If desired, you can disable this behavior by setting the +focus_wrapping+
|
||||
configuration directive to the value +no+.
|
||||
|
||||
When enabled, focus wrapping does not occur by default if there is another
|
||||
window or container in the specified direction, and focus will instead be set
|
||||
on that window or container. This is the default behavior so you can navigate
|
||||
to all your windows without having to use +focus parent+.
|
||||
|
||||
If you want the focus to *always* wrap and you are aware of using +focus
|
||||
parent+ to switch to different containers, you can use the
|
||||
+force_focus_wrapping+ configuration directive. After enabling it, the focus
|
||||
will always wrap.
|
||||
parent+ to switch to different containers, you can instead set +focus_wrapping+
|
||||
to the value +force+.
|
||||
|
||||
*Syntax*:
|
||||
---------------------------
|
||||
force_focus_wrapping yes|no
|
||||
focus_wrapping yes|no|force
|
||||
|
||||
# Legacy syntax, equivalent to "focus_wrapping force"
|
||||
force_focus_wrapping yes
|
||||
---------------------------
|
||||
|
||||
*Example*:
|
||||
------------------------
|
||||
force_focus_wrapping yes
|
||||
------------------------
|
||||
*Examples*:
|
||||
-----------------
|
||||
# Disable focus wrapping
|
||||
focus_wrapping no
|
||||
|
||||
# Force focus wrapping
|
||||
focus_wrapping force
|
||||
-----------------
|
||||
|
||||
=== Forcing Xinerama
|
||||
|
||||
|
@ -1341,7 +1379,7 @@ and will be removed in a future release. We strongly recommend using the more ge
|
|||
|
||||
*Syntax*:
|
||||
----------------------------
|
||||
bindsym button<n> <command>
|
||||
bindsym [--release] button<n> <command>
|
||||
----------------------------
|
||||
|
||||
*Example*:
|
||||
|
@ -1349,6 +1387,8 @@ bindsym button<n> <command>
|
|||
bar {
|
||||
# disable clicking on workspace buttons
|
||||
bindsym button1 nop
|
||||
# Take a screenshot by right clicking on the bar
|
||||
bindsym --release button3 exec --no-startup-id import /tmp/latest-screenshot.png
|
||||
# execute custom script when scrolling downwards
|
||||
bindsym button5 exec ~/.i3/scripts/custom_wheel_down
|
||||
}
|
||||
|
@ -1913,6 +1953,9 @@ bindsym $mod+t floating toggle
|
|||
To change focus, you can use the +focus+ command. The following options are
|
||||
available:
|
||||
|
||||
<criteria>::
|
||||
Sets focus to the container that matches the specified criteria.
|
||||
See <<command_criteria>>.
|
||||
left|right|up|down::
|
||||
Sets focus to the nearest container in the given direction.
|
||||
parent::
|
||||
|
@ -1932,6 +1975,7 @@ output::
|
|||
|
||||
*Syntax*:
|
||||
----------------------------------------------
|
||||
<criteria> focus
|
||||
focus left|right|down|up
|
||||
focus parent|child|floating|tiling|mode_toggle
|
||||
focus output left|right|up|down|primary|<output>
|
||||
|
@ -1939,6 +1983,9 @@ focus output left|right|up|down|primary|<output>
|
|||
|
||||
*Examples*:
|
||||
-------------------------------------------------
|
||||
# Focus firefox
|
||||
bindsym $mod+F1 [class="Firefox"] focus
|
||||
|
||||
# Focus container on the left, bottom, top, right
|
||||
bindsym $mod+j focus left
|
||||
bindsym $mod+k focus down
|
||||
|
@ -2232,7 +2279,6 @@ bindsym $mod+x move container to output VGA1
|
|||
bindsym $mod+x move container to output primary
|
||||
--------------------------------------------------------
|
||||
|
||||
-------------------------------
|
||||
Note that you might not have a primary output configured yet. To do so, run:
|
||||
-------------------------
|
||||
xrandr --output <output> --primary
|
||||
|
@ -2267,7 +2313,7 @@ If you want to resize containers/windows using your keyboard, you can use the
|
|||
*Syntax*:
|
||||
-------------------------------------------------------
|
||||
resize grow|shrink <direction> [<px> px [or <ppt> ppt]]
|
||||
resize set <width> [px] <height> [px]
|
||||
resize set <width> [px | ppt] <height> [px | ppt]
|
||||
-------------------------------------------------------
|
||||
|
||||
Direction can either be one of +up+, +down+, +left+ or +right+. Or you can be
|
||||
|
@ -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
|
||||
is 10 pixels). The ppt argument means percentage points and specifies by how
|
||||
many percentage points a *tiling container* should be grown or shrunk (the
|
||||
default is 10 percentage points). Note that +resize set+ will only work for
|
||||
floating containers.
|
||||
default is 10 percentage points).
|
||||
|
||||
Notes about +resize set+: a value of 0 for <width> or <height> means "do
|
||||
not resize in this direction", and resizing a tiling container by +px+ is not
|
||||
implemented.
|
||||
|
||||
It is recommended to define bindings for resizing in a dedicated binding mode.
|
||||
See <<binding_modes>> and the example in the i3
|
||||
|
@ -2363,10 +2412,10 @@ TODO: make i3-input replace %s
|
|||
*Examples*:
|
||||
---------------------------------------
|
||||
# Read 1 character and mark the current window with this character
|
||||
bindsym $mod+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
|
||||
bindsym $mod+m exec i3-input -F 'mark %s' -l 1 -P 'Mark: '
|
||||
|
||||
# Read 1 character and go to the window with the character
|
||||
bindsym $mod+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
|
||||
bindsym $mod+g exec i3-input -F '[con_mark="%s"] focus' -l 1 -P 'Goto: '
|
||||
---------------------------------------
|
||||
|
||||
Alternatively, if you do not want to mess with +i3-input+, you could create
|
||||
|
|
59
etc/config
59
etc/config
|
@ -22,7 +22,7 @@ font pango:monospace 8
|
|||
# The font above is very space-efficient, that is, it looks good, sharp and
|
||||
# clear in small sizes. However, its unicode glyph coverage is limited, the old
|
||||
# X core fonts rendering does not support right-to-left and this being a bitmap
|
||||
# font, it doesn’t scale on retina/hidpi displays.
|
||||
# font, it doesn't scale on retina/hidpi displays.
|
||||
|
||||
# use these keys for focus, movement, and resize directions when reaching for
|
||||
# the arrows is not convenient
|
||||
|
@ -104,29 +104,43 @@ bindsym Mod1+Shift+minus move scratchpad
|
|||
# If there are multiple scratchpad windows, this command cycles through them.
|
||||
bindsym Mod1+minus scratchpad show
|
||||
|
||||
# Define names for default workspaces for which we configure key bindings later on.
|
||||
# We use variables to avoid repeating the names in multiple places.
|
||||
set $ws1 "1"
|
||||
set $ws2 "2"
|
||||
set $ws3 "3"
|
||||
set $ws4 "4"
|
||||
set $ws5 "5"
|
||||
set $ws6 "6"
|
||||
set $ws7 "7"
|
||||
set $ws8 "8"
|
||||
set $ws9 "9"
|
||||
set $ws10 "10"
|
||||
|
||||
|
||||
# switch to workspace
|
||||
bindsym Mod1+1 workspace 1
|
||||
bindsym Mod1+2 workspace 2
|
||||
bindsym Mod1+3 workspace 3
|
||||
bindsym Mod1+4 workspace 4
|
||||
bindsym Mod1+5 workspace 5
|
||||
bindsym Mod1+6 workspace 6
|
||||
bindsym Mod1+7 workspace 7
|
||||
bindsym Mod1+8 workspace 8
|
||||
bindsym Mod1+9 workspace 9
|
||||
bindsym Mod1+0 workspace 10
|
||||
bindsym Mod1+1 workspace $ws1
|
||||
bindsym Mod1+2 workspace $ws2
|
||||
bindsym Mod1+3 workspace $ws3
|
||||
bindsym Mod1+4 workspace $ws4
|
||||
bindsym Mod1+5 workspace $ws5
|
||||
bindsym Mod1+6 workspace $ws6
|
||||
bindsym Mod1+7 workspace $ws7
|
||||
bindsym Mod1+8 workspace $ws8
|
||||
bindsym Mod1+9 workspace $ws9
|
||||
bindsym Mod1+0 workspace $ws10
|
||||
|
||||
# move focused container to workspace
|
||||
bindsym Mod1+Shift+1 move container to workspace 1
|
||||
bindsym Mod1+Shift+2 move container to workspace 2
|
||||
bindsym Mod1+Shift+3 move container to workspace 3
|
||||
bindsym Mod1+Shift+4 move container to workspace 4
|
||||
bindsym Mod1+Shift+5 move container to workspace 5
|
||||
bindsym Mod1+Shift+6 move container to workspace 6
|
||||
bindsym Mod1+Shift+7 move container to workspace 7
|
||||
bindsym Mod1+Shift+8 move container to workspace 8
|
||||
bindsym Mod1+Shift+9 move container to workspace 9
|
||||
bindsym Mod1+Shift+0 move container to workspace 10
|
||||
bindsym Mod1+Shift+1 move container to workspace $ws1
|
||||
bindsym Mod1+Shift+2 move container to workspace $ws2
|
||||
bindsym Mod1+Shift+3 move container to workspace $ws3
|
||||
bindsym Mod1+Shift+4 move container to workspace $ws4
|
||||
bindsym Mod1+Shift+5 move container to workspace $ws5
|
||||
bindsym Mod1+Shift+6 move container to workspace $ws6
|
||||
bindsym Mod1+Shift+7 move container to workspace $ws7
|
||||
bindsym Mod1+Shift+8 move container to workspace $ws8
|
||||
bindsym Mod1+Shift+9 move container to workspace $ws9
|
||||
bindsym Mod1+Shift+0 move container to workspace $ws10
|
||||
|
||||
# reload the configuration file
|
||||
bindsym Mod1+Shift+c reload
|
||||
|
@ -154,9 +168,10 @@ mode "resize" {
|
|||
bindsym Up resize shrink height 10 px or 10 ppt
|
||||
bindsym Right resize grow width 10 px or 10 ppt
|
||||
|
||||
# back to normal: Enter or Escape
|
||||
# back to normal: Enter or Escape or Mod1+r
|
||||
bindsym Return mode "default"
|
||||
bindsym Escape mode "default"
|
||||
bindsym Mod1+r mode "default"
|
||||
}
|
||||
|
||||
bindsym Mod1+r mode "resize"
|
||||
|
|
|
@ -91,29 +91,42 @@ bindcode $mod+38 focus parent
|
|||
# focus the child container
|
||||
#bindsym $mod+d focus child
|
||||
|
||||
# Define names for default workspaces for which we configure key bindings later on.
|
||||
# We use variables to avoid repeating the names in multiple places.
|
||||
set $ws1 "1"
|
||||
set $ws2 "2"
|
||||
set $ws3 "3"
|
||||
set $ws4 "4"
|
||||
set $ws5 "5"
|
||||
set $ws6 "6"
|
||||
set $ws7 "7"
|
||||
set $ws8 "8"
|
||||
set $ws9 "9"
|
||||
set $ws10 "10"
|
||||
|
||||
# switch to workspace
|
||||
bindcode $mod+10 workspace 1
|
||||
bindcode $mod+11 workspace 2
|
||||
bindcode $mod+12 workspace 3
|
||||
bindcode $mod+13 workspace 4
|
||||
bindcode $mod+14 workspace 5
|
||||
bindcode $mod+15 workspace 6
|
||||
bindcode $mod+16 workspace 7
|
||||
bindcode $mod+17 workspace 8
|
||||
bindcode $mod+18 workspace 9
|
||||
bindcode $mod+19 workspace 10
|
||||
bindcode $mod+10 workspace $ws1
|
||||
bindcode $mod+11 workspace $ws2
|
||||
bindcode $mod+12 workspace $ws3
|
||||
bindcode $mod+13 workspace $ws4
|
||||
bindcode $mod+14 workspace $ws5
|
||||
bindcode $mod+15 workspace $ws6
|
||||
bindcode $mod+16 workspace $ws7
|
||||
bindcode $mod+17 workspace $ws8
|
||||
bindcode $mod+18 workspace $ws9
|
||||
bindcode $mod+19 workspace $ws10
|
||||
|
||||
# move focused container to workspace
|
||||
bindcode $mod+Shift+10 move container to workspace 1
|
||||
bindcode $mod+Shift+11 move container to workspace 2
|
||||
bindcode $mod+Shift+12 move container to workspace 3
|
||||
bindcode $mod+Shift+13 move container to workspace 4
|
||||
bindcode $mod+Shift+14 move container to workspace 5
|
||||
bindcode $mod+Shift+15 move container to workspace 6
|
||||
bindcode $mod+Shift+16 move container to workspace 7
|
||||
bindcode $mod+Shift+17 move container to workspace 8
|
||||
bindcode $mod+Shift+18 move container to workspace 9
|
||||
bindcode $mod+Shift+19 move container to workspace 10
|
||||
bindcode $mod+Shift+10 move container to workspace $ws1
|
||||
bindcode $mod+Shift+11 move container to workspace $ws2
|
||||
bindcode $mod+Shift+12 move container to workspace $ws3
|
||||
bindcode $mod+Shift+13 move container to workspace $ws4
|
||||
bindcode $mod+Shift+14 move container to workspace $ws5
|
||||
bindcode $mod+Shift+15 move container to workspace $ws6
|
||||
bindcode $mod+Shift+16 move container to workspace $ws7
|
||||
bindcode $mod+Shift+17 move container to workspace $ws8
|
||||
bindcode $mod+Shift+18 move container to workspace $ws9
|
||||
bindcode $mod+Shift+19 move container to workspace $ws10
|
||||
|
||||
# reload the configuration file
|
||||
bindcode $mod+Shift+54 reload
|
||||
|
@ -141,9 +154,10 @@ mode "resize" {
|
|||
bindcode 111 resize shrink height 10 px or 10 ppt
|
||||
bindcode 114 resize grow width 10 px or 10 ppt
|
||||
|
||||
# back to normal: Enter or Escape
|
||||
# back to normal: Enter or Escape or $mod+r
|
||||
bindcode 36 mode "default"
|
||||
bindcode 9 mode "default"
|
||||
bindcode $mod+27 mode "default"
|
||||
}
|
||||
|
||||
bindcode $mod+27 mode "resize"
|
||||
|
|
|
@ -116,17 +116,16 @@ my @keys = sort { (length($b) <=> length($a)) or ($a cmp $b) } keys %states;
|
|||
|
||||
open(my $enumfh, '>', "GENERATED_${prefix}_enums.h");
|
||||
|
||||
# XXX: we might want to have a way to do this without a trailing comma, but gcc
|
||||
# seems to eat it.
|
||||
my %statenum;
|
||||
say $enumfh 'typedef enum {';
|
||||
my $cnt = 0;
|
||||
for my $state (@keys, '__CALL') {
|
||||
say $enumfh " $state = $cnt,";
|
||||
say $enumfh ',' if $cnt > 0;
|
||||
print $enumfh " $state = $cnt";
|
||||
$statenum{$state} = $cnt;
|
||||
$cnt++;
|
||||
}
|
||||
say $enumfh '} cmdp_state;';
|
||||
say $enumfh "\n} cmdp_state;";
|
||||
close($enumfh);
|
||||
|
||||
# Third step: Generate the call function.
|
||||
|
@ -225,7 +224,7 @@ for my $state (@keys) {
|
|||
$next_state = '__CALL';
|
||||
}
|
||||
my $identifier = $token->{identifier};
|
||||
say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } }, |;
|
||||
say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } },|;
|
||||
}
|
||||
say $tokfh '};';
|
||||
}
|
||||
|
|
|
@ -58,12 +58,10 @@
|
|||
#error "SYSCONFDIR not defined"
|
||||
#endif
|
||||
|
||||
#define FREE(pointer) \
|
||||
do { \
|
||||
if (pointer != NULL) { \
|
||||
free(pointer); \
|
||||
pointer = NULL; \
|
||||
} \
|
||||
#define FREE(pointer) \
|
||||
do { \
|
||||
free(pointer); \
|
||||
pointer = NULL; \
|
||||
} while (0)
|
||||
|
||||
#include "xcb.h"
|
||||
|
@ -94,7 +92,7 @@ static xcb_get_modifier_mapping_reply_t *modmap_reply;
|
|||
static i3Font font;
|
||||
static i3Font bold_font;
|
||||
static int char_width;
|
||||
static char *socket_path;
|
||||
static char *socket_path = NULL;
|
||||
static xcb_window_t win;
|
||||
static surface_t surface;
|
||||
static xcb_key_symbols_t *symbols;
|
||||
|
@ -744,7 +742,6 @@ static void finish() {
|
|||
|
||||
int main(int argc, char *argv[]) {
|
||||
char *xdg_config_home;
|
||||
socket_path = getenv("I3SOCK");
|
||||
char *pattern = "pango:monospace 8";
|
||||
char *patternbold = "pango:monospace bold 8";
|
||||
int o, option_index = 0;
|
||||
|
@ -824,12 +821,6 @@ int main(int argc, char *argv[]) {
|
|||
&xkb_base_error) != 1)
|
||||
errx(EXIT_FAILURE, "Could not setup XKB extension.");
|
||||
|
||||
if (socket_path == NULL)
|
||||
socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);
|
||||
|
||||
if (socket_path == NULL)
|
||||
socket_path = "/tmp/i3-ipc.sock";
|
||||
|
||||
keysyms = xcb_key_symbols_alloc(conn);
|
||||
xcb_get_modifier_mapping_cookie_t modmap_cookie;
|
||||
modmap_cookie = xcb_get_modifier_mapping(conn);
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "libi3.h"
|
||||
#include "shmlog.h"
|
||||
|
@ -38,6 +39,29 @@ static uint32_t wrap_count;
|
|||
static i3_shmlog_header *header;
|
||||
static char *logbuffer,
|
||||
*walk;
|
||||
static int ipcfd = -1;
|
||||
|
||||
static volatile bool interrupted = false;
|
||||
|
||||
static void sighandler(int signal) {
|
||||
interrupted = true;
|
||||
}
|
||||
|
||||
static void disable_shmlog(void) {
|
||||
const char *disablecmd = "debuglog off; shmlog off";
|
||||
if (ipc_send_message(ipcfd, strlen(disablecmd),
|
||||
I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)disablecmd) != 0)
|
||||
err(EXIT_FAILURE, "IPC send");
|
||||
|
||||
/* Ensure the command was sent by waiting for the reply: */
|
||||
uint32_t reply_length = 0;
|
||||
uint8_t *reply = NULL;
|
||||
if (ipc_recv_message(ipcfd, I3_IPC_REPLY_TYPE_COMMAND,
|
||||
&reply_length, &reply) != 0) {
|
||||
err(EXIT_FAILURE, "IPC recv");
|
||||
}
|
||||
free(reply);
|
||||
}
|
||||
|
||||
static int check_for_wrap(void) {
|
||||
if (wrap_count == header->wrap_count)
|
||||
|
@ -59,6 +83,14 @@ static void print_till_end(void) {
|
|||
walk += len;
|
||||
}
|
||||
|
||||
void errorlog(char *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int o, option_index = 0;
|
||||
bool verbose = false;
|
||||
|
@ -123,15 +155,35 @@ int main(int argc, char *argv[]) {
|
|||
exit(1);
|
||||
}
|
||||
if (root_atom_contents("I3_CONFIG_PATH", conn, screen) != NULL) {
|
||||
fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled.\n\n");
|
||||
if (!is_debug_build()) {
|
||||
fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled. Enabling SHM log until cancelled\n\n");
|
||||
ipcfd = ipc_connect(NULL);
|
||||
const char *enablecmd = "debuglog on; shmlog 5242880";
|
||||
if (ipc_send_message(ipcfd, strlen(enablecmd),
|
||||
I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)enablecmd) != 0)
|
||||
err(EXIT_FAILURE, "IPC send");
|
||||
/* By the time we receive a reply, I3_SHMLOG_PATH is set: */
|
||||
uint32_t reply_length = 0;
|
||||
uint8_t *reply = NULL;
|
||||
if (ipc_recv_message(ipcfd, I3_IPC_REPLY_TYPE_COMMAND,
|
||||
&reply_length, &reply) != 0) {
|
||||
err(EXIT_FAILURE, "IPC recv");
|
||||
}
|
||||
free(reply);
|
||||
|
||||
atexit(disable_shmlog);
|
||||
|
||||
/* Retry: */
|
||||
shmname = root_atom_contents("I3_SHMLOG_PATH", NULL, 0);
|
||||
if (shmname == NULL && !is_debug_build()) {
|
||||
fprintf(stderr, "You seem to be using a release version of i3:\n %s\n\n", I3_VERSION);
|
||||
fprintf(stderr, "Release versions do not use SHM logging by default,\ntherefore i3-dump-log does not work.\n\n");
|
||||
fprintf(stderr, "Please follow this guide instead:\nhttps://i3wm.org/docs/debugging-release-version.html\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
errx(EXIT_FAILURE, "Cannot get I3_SHMLOG_PATH atom contents. Is i3 running on this display?");
|
||||
if (shmname == NULL) {
|
||||
errx(EXIT_FAILURE, "Cannot get I3_SHMLOG_PATH atom contents. Is i3 running on this display?");
|
||||
}
|
||||
}
|
||||
|
||||
if (*shmname == '\0')
|
||||
|
@ -182,22 +234,32 @@ int main(int argc, char *argv[]) {
|
|||
print_till_end();
|
||||
|
||||
#if !defined(__OpenBSD__)
|
||||
if (follow) {
|
||||
/* Since pthread_cond_wait() expects a mutex, we need to provide one.
|
||||
if (!follow) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Handle SIGINT gracefully to invoke atexit handlers, if any. */
|
||||
struct sigaction action;
|
||||
action.sa_handler = sighandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = 0;
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
|
||||
/* Since pthread_cond_wait() expects a mutex, we need to provide one.
|
||||
* To not lock i3 (that’s bad, mhkay?) we just define one outside of
|
||||
* the shared memory. */
|
||||
pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_lock(&dummy_mutex);
|
||||
while (1) {
|
||||
pthread_cond_wait(&(header->condvar), &dummy_mutex);
|
||||
/* If this was not a spurious wakeup, print the new lines. */
|
||||
if (header->offset_next_write != offset_next_write) {
|
||||
offset_next_write = header->offset_next_write;
|
||||
print_till_end();
|
||||
}
|
||||
pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_lock(&dummy_mutex);
|
||||
while (!interrupted) {
|
||||
pthread_cond_wait(&(header->condvar), &dummy_mutex);
|
||||
/* If this was not a spurious wakeup, print the new lines. */
|
||||
if (header->offset_next_write != offset_next_write) {
|
||||
offset_next_write = header->offset_next_write;
|
||||
print_till_end();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
exit(0);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -5,12 +5,10 @@
|
|||
#include <err.h>
|
||||
|
||||
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
|
||||
#define FREE(pointer) \
|
||||
do { \
|
||||
if (pointer != NULL) { \
|
||||
free(pointer); \
|
||||
pointer = NULL; \
|
||||
} \
|
||||
#define FREE(pointer) \
|
||||
do { \
|
||||
free(pointer); \
|
||||
pointer = NULL; \
|
||||
} while (0)
|
||||
|
||||
extern xcb_window_t root;
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
* the command will be sent to i3 */
|
||||
static char *format;
|
||||
|
||||
static char *socket_path;
|
||||
static int sockfd;
|
||||
static xcb_key_symbols_t *symbols;
|
||||
static bool modeswitch_active = false;
|
||||
|
@ -374,7 +373,7 @@ free_resources:
|
|||
|
||||
int main(int argc, char *argv[]) {
|
||||
format = sstrdup("%s");
|
||||
socket_path = getenv("I3SOCK");
|
||||
char *socket_path = NULL;
|
||||
char *pattern = sstrdup("pango:monospace 8");
|
||||
int o, option_index = 0;
|
||||
|
||||
|
@ -438,12 +437,6 @@ int main(int argc, char *argv[]) {
|
|||
if (!conn || xcb_connection_has_error(conn))
|
||||
die("Cannot open display\n");
|
||||
|
||||
if (socket_path == NULL)
|
||||
socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);
|
||||
|
||||
if (socket_path == NULL)
|
||||
socket_path = "/tmp/i3-ipc.sock";
|
||||
|
||||
sockfd = ipc_connect(socket_path);
|
||||
|
||||
root_screen = xcb_aux_get_screen(conn, screen);
|
||||
|
|
|
@ -38,8 +38,6 @@
|
|||
|
||||
#include <i3/ipc.h>
|
||||
|
||||
static char *socket_path;
|
||||
|
||||
/*
|
||||
* 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)
|
||||
err(EXIT_FAILURE, "pledge");
|
||||
#endif
|
||||
char *env_socket_path = getenv("I3SOCK");
|
||||
if (env_socket_path)
|
||||
socket_path = sstrdup(env_socket_path);
|
||||
else
|
||||
socket_path = NULL;
|
||||
char *socket_path = NULL;
|
||||
int o, option_index = 0;
|
||||
uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND;
|
||||
char *payload = NULL;
|
||||
|
@ -183,8 +177,7 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
|
||||
if (o == 's') {
|
||||
if (socket_path != NULL)
|
||||
free(socket_path);
|
||||
free(socket_path);
|
||||
socket_path = sstrdup(optarg);
|
||||
} else if (o == 't') {
|
||||
if (strcasecmp(optarg, "command") == 0) {
|
||||
|
@ -207,9 +200,11 @@ int main(int argc, char *argv[]) {
|
|||
message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION;
|
||||
} else if (strcasecmp(optarg, "get_config") == 0) {
|
||||
message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
|
||||
} else if (strcasecmp(optarg, "send_tick") == 0) {
|
||||
message_type = I3_IPC_MESSAGE_TYPE_SEND_TICK;
|
||||
} else {
|
||||
printf("Unknown message type\n");
|
||||
printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n");
|
||||
printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config, send_tick\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (o == 'q') {
|
||||
|
@ -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.
|
||||
* This way, you don’t have to do i3-msg 'mark foo', you can use
|
||||
* i3-msg mark foo */
|
||||
|
@ -251,17 +239,7 @@ int main(int argc, char *argv[]) {
|
|||
if (!payload)
|
||||
payload = sstrdup("");
|
||||
|
||||
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
if (sockfd == -1)
|
||||
err(EXIT_FAILURE, "Could not create socket");
|
||||
|
||||
struct sockaddr_un addr;
|
||||
memset(&addr, 0, sizeof(struct sockaddr_un));
|
||||
addr.sun_family = AF_LOCAL;
|
||||
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
|
||||
if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
|
||||
err(EXIT_FAILURE, "Could not connect to i3 on socket \"%s\"", socket_path);
|
||||
|
||||
int sockfd = ipc_connect(socket_path);
|
||||
if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1)
|
||||
err(EXIT_FAILURE, "IPC: write()");
|
||||
free(payload);
|
||||
|
|
|
@ -5,12 +5,10 @@
|
|||
#include <err.h>
|
||||
|
||||
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
|
||||
#define FREE(pointer) \
|
||||
do { \
|
||||
if (pointer != NULL) { \
|
||||
free(pointer); \
|
||||
pointer = NULL; \
|
||||
} \
|
||||
#define FREE(pointer) \
|
||||
do { \
|
||||
free(pointer); \
|
||||
pointer = NULL; \
|
||||
} while (0)
|
||||
|
||||
#define xmacro(atom) xcb_atom_t A_##atom;
|
||||
|
|
|
@ -575,7 +575,9 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
case XCB_CONFIGURE_NOTIFY: {
|
||||
xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event;
|
||||
draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height);
|
||||
if (configure_notify->width > 0 && configure_notify->height > 0) {
|
||||
draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# mechanism to find the preferred editor
|
||||
|
||||
# Hopefully one of these is installed (no flamewars about preference please!):
|
||||
for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mcedit; do
|
||||
for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mcedit gvim; do
|
||||
if command -v "$editor" > /dev/null 2>&1; then
|
||||
exec "$editor" "$@"
|
||||
fi
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
# We welcome patches that add distribution-specific mechanisms to find the
|
||||
# preferred terminal emulator. On Debian, there is the x-terminal-emulator
|
||||
# symlink for example.
|
||||
for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole; do
|
||||
for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda; do
|
||||
if command -v "$terminal" > /dev/null 2>&1; then
|
||||
exec "$terminal" "$@"
|
||||
fi
|
||||
|
|
|
@ -85,4 +85,4 @@ bool child_want_click_events(void);
|
|||
* Generates a click event, if enabled.
|
||||
*
|
||||
*/
|
||||
void send_block_clicked(int button, const char *name, const char *instance, int x, int y);
|
||||
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);
|
||||
|
|
|
@ -27,6 +27,7 @@ typedef enum { M_DOCK = 0,
|
|||
typedef struct binding_t {
|
||||
int input_code;
|
||||
char *command;
|
||||
bool release;
|
||||
|
||||
TAILQ_ENTRY(binding_t)
|
||||
bindings;
|
||||
|
|
|
@ -33,6 +33,12 @@ void parse_outputs_json(char* json);
|
|||
*/
|
||||
void init_outputs(void);
|
||||
|
||||
/*
|
||||
* free() all outputs data structures.
|
||||
*
|
||||
*/
|
||||
void free_outputs(void);
|
||||
|
||||
/*
|
||||
* Returns the output with the given name
|
||||
*
|
||||
|
|
|
@ -20,12 +20,10 @@
|
|||
#define STARTS_WITH(string, len, needle) (((len) >= strlen((needle))) && strncasecmp((string), (needle), strlen((needle))) == 0)
|
||||
|
||||
/* Securely free p */
|
||||
#define FREE(p) \
|
||||
do { \
|
||||
if (p != NULL) { \
|
||||
free(p); \
|
||||
p = NULL; \
|
||||
} \
|
||||
#define FREE(p) \
|
||||
do { \
|
||||
free(p); \
|
||||
p = NULL; \
|
||||
} while (0)
|
||||
|
||||
/* Securely free single-linked list */
|
||||
|
|
|
@ -9,4 +9,5 @@ ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
|
|||
ATOM_DO(_NET_SYSTEM_TRAY_COLORS)
|
||||
ATOM_DO(_XEMBED_INFO)
|
||||
ATOM_DO(_XEMBED)
|
||||
ATOM_DO(I3_SYNC)
|
||||
#undef ATOM_DO
|
||||
|
|
|
@ -106,7 +106,7 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
|
|||
va_list args;
|
||||
va_start(args, format);
|
||||
if (vasprintf(&message, format, args) == -1) {
|
||||
return;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
struct status_block *err_block = scalloc(1, sizeof(struct status_block));
|
||||
|
@ -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_TAIL(&statusline_head, message_block, blocks);
|
||||
|
||||
finish:
|
||||
FREE(message);
|
||||
va_end(args);
|
||||
}
|
||||
|
@ -595,7 +596,7 @@ void child_click_events_key(const char *key) {
|
|||
* Generates a click event, if enabled.
|
||||
*
|
||||
*/
|
||||
void send_block_clicked(int button, const char *name, const char *instance, int x, int y) {
|
||||
void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int width, int height) {
|
||||
if (!child.click_events) {
|
||||
return;
|
||||
}
|
||||
|
@ -623,6 +624,18 @@ void send_block_clicked(int button, const char *name, const char *instance, int
|
|||
child_click_events_key("y");
|
||||
yajl_gen_integer(gen, y);
|
||||
|
||||
child_click_events_key("relative_x");
|
||||
yajl_gen_integer(gen, x_rel);
|
||||
|
||||
child_click_events_key("relative_y");
|
||||
yajl_gen_integer(gen, y_rel);
|
||||
|
||||
child_click_events_key("width");
|
||||
yajl_gen_integer(gen, width);
|
||||
|
||||
child_click_events_key("height");
|
||||
yajl_gen_integer(gen, height);
|
||||
|
||||
yajl_gen_map_close(gen);
|
||||
child_write_output();
|
||||
}
|
||||
|
|
|
@ -107,34 +107,34 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
|
|||
|
||||
if (!strcmp(cur_key, "mode")) {
|
||||
DLOG("mode = %.*s, len = %d\n", len, val, len);
|
||||
config.hide_on_modifier = (len == 4 && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK
|
||||
: (len == 4 && !strncmp((const char *)val, "hide", strlen("hide")) ? M_HIDE
|
||||
: M_INVISIBLE));
|
||||
config.hide_on_modifier = (len == strlen("dock") && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK
|
||||
: (len == strlen("hide") && !strncmp((const char *)val, "hide", strlen("hide")) ? M_HIDE
|
||||
: M_INVISIBLE));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "hidden_state")) {
|
||||
DLOG("hidden_state = %.*s, len = %d\n", len, val, len);
|
||||
config.hidden_state = (len == 4 && !strncmp((const char *)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW);
|
||||
config.hidden_state = (len == strlen("hide") && !strncmp((const char *)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "modifier")) {
|
||||
DLOG("modifier = %.*s\n", len, val);
|
||||
if (len == 4 && !strncmp((const char *)val, "none", strlen("none"))) {
|
||||
if (len == strlen("none") && !strncmp((const char *)val, "none", strlen("none"))) {
|
||||
config.modifier = XCB_NONE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (len == 5 && !strncmp((const char *)val, "shift", strlen("shift"))) {
|
||||
if (len == strlen("shift") && !strncmp((const char *)val, "shift", strlen("shift"))) {
|
||||
config.modifier = ShiftMask;
|
||||
return 1;
|
||||
}
|
||||
if (len == 4 && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
|
||||
if (len == strlen("ctrl") && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
|
||||
config.modifier = ControlMask;
|
||||
return 1;
|
||||
}
|
||||
if (len == 4 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
|
||||
if (len == strlen("Mod") + 1 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
|
||||
switch (val[3]) {
|
||||
case '1':
|
||||
config.modifier = Mod1Mask;
|
||||
|
@ -179,7 +179,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
|
|||
|
||||
if (!strcmp(cur_key, "position")) {
|
||||
DLOG("position = %.*s\n", len, val);
|
||||
config.position = (len == 3 && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT);
|
||||
config.position = (len == strlen("top") && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -192,6 +192,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
|
|||
|
||||
if (!strcmp(cur_key, "font")) {
|
||||
DLOG("font = %.*s\n", len, val);
|
||||
FREE(config.fontname);
|
||||
sasprintf(&config.fontname, "%.*s", len, val);
|
||||
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) {
|
||||
if (parsing_bindings) {
|
||||
if (strcmp(cur_key, "release") == 0) {
|
||||
binding_t *binding = TAILQ_LAST(&(config.bindings), bindings_head);
|
||||
if (binding == NULL) {
|
||||
ELOG("There is no binding to put the current command onto. This is a bug in i3.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
binding->release = val;
|
||||
return 1;
|
||||
}
|
||||
|
||||
ELOG("Unknown key \"%s\" while parsing bar bindings.\n", cur_key);
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "binding_mode_indicator")) {
|
||||
DLOG("binding_mode_indicator = %d\n", val);
|
||||
config.disable_binding_mode_indicator = !val;
|
||||
|
|
|
@ -64,17 +64,14 @@ void got_subscribe_reply(char *reply) {
|
|||
*/
|
||||
void got_output_reply(char *reply) {
|
||||
DLOG("Clearing old output configuration...\n");
|
||||
i3_output *o_walk;
|
||||
SLIST_FOREACH(o_walk, outputs, slist) {
|
||||
destroy_window(o_walk);
|
||||
}
|
||||
FREE_SLIST(outputs, i3_output);
|
||||
free_outputs();
|
||||
|
||||
DLOG("Parsing outputs JSON...\n");
|
||||
parse_outputs_json(reply);
|
||||
DLOG("Reconfiguring windows...\n");
|
||||
reconfig_windows(false);
|
||||
|
||||
i3_output *o_walk;
|
||||
SLIST_FOREACH(o_walk, outputs, slist) {
|
||||
kick_tray_clients(o_walk);
|
||||
}
|
||||
|
@ -177,7 +174,7 @@ void got_bar_config_update(char *event) {
|
|||
|
||||
/* update the configuration with the received settings */
|
||||
DLOG("Received bar config update \"%s\"\n", event);
|
||||
char *old_command = sstrdup(config.command);
|
||||
char *old_command = config.command ? sstrdup(config.command) : NULL;
|
||||
bar_display_mode_t old_mode = config.hide_on_modifier;
|
||||
parse_config_json(event);
|
||||
if (old_mode != config.hide_on_modifier) {
|
||||
|
@ -189,7 +186,7 @@ void got_bar_config_update(char *event) {
|
|||
init_colors(&(config.colors));
|
||||
|
||||
/* restart status command process */
|
||||
if (strcmp(old_command, config.command) != 0) {
|
||||
if (old_command && strcmp(old_command, config.command) != 0) {
|
||||
kill_child();
|
||||
start_child(config.command);
|
||||
}
|
||||
|
|
|
@ -182,7 +182,5 @@ int main(int argc, char **argv) {
|
|||
clean_xcb();
|
||||
ev_default_destroy();
|
||||
|
||||
free_workspaces();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -173,6 +173,12 @@ static int outputs_start_map_cb(void *params_) {
|
|||
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)
|
||||
*
|
||||
|
@ -199,9 +205,7 @@ static int outputs_end_map_cb(void *params_) {
|
|||
if (!handle_output) {
|
||||
DLOG("Ignoring output \"%s\", not configured to handle it.\n",
|
||||
params->outputs_walk->name);
|
||||
FREE(params->outputs_walk->name);
|
||||
FREE(params->outputs_walk->workspaces);
|
||||
FREE(params->outputs_walk->trayclients);
|
||||
clear_output(params->outputs_walk);
|
||||
FREE(params->outputs_walk);
|
||||
FREE(params->cur_key);
|
||||
return 1;
|
||||
|
@ -217,6 +221,9 @@ static int outputs_end_map_cb(void *params_) {
|
|||
target->primary = params->outputs_walk->primary;
|
||||
target->ws = params->outputs_walk->ws;
|
||||
target->rect = params->outputs_walk->rect;
|
||||
|
||||
clear_output(params->outputs_walk);
|
||||
FREE(params->outputs_walk);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
@ -260,7 +267,6 @@ void init_outputs(void) {
|
|||
*/
|
||||
void parse_outputs_json(char *json) {
|
||||
struct outputs_json_params params;
|
||||
|
||||
params.outputs_walk = NULL;
|
||||
params.cur_key = NULL;
|
||||
params.json = json;
|
||||
|
@ -286,6 +292,27 @@ void parse_outputs_json(char *json) {
|
|||
yajl_free(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* free() all outputs data structures.
|
||||
*
|
||||
*/
|
||||
void free_outputs(void) {
|
||||
free_workspaces();
|
||||
|
||||
i3_output *outputs_walk;
|
||||
if (outputs == NULL) {
|
||||
return;
|
||||
}
|
||||
SLIST_FOREACH(outputs_walk, outputs, slist) {
|
||||
destroy_window(outputs_walk);
|
||||
if (outputs_walk->trayclients != NULL && !TAILQ_EMPTY(outputs_walk->trayclients)) {
|
||||
FREE_TAILQ(outputs_walk->trayclients, trayclient);
|
||||
}
|
||||
clear_output(outputs_walk);
|
||||
}
|
||||
FREE_SLIST(outputs, i3_output);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the output with the given name
|
||||
*
|
||||
|
|
|
@ -83,7 +83,6 @@ int mod_pressed = 0;
|
|||
|
||||
/* Event watchers, to interact with the user */
|
||||
ev_prepare *xcb_prep;
|
||||
ev_check *xcb_chk;
|
||||
ev_io *xcb_io;
|
||||
ev_io *xkb_io;
|
||||
|
||||
|
@ -440,6 +439,18 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
|
|||
xcb_flush(xcb_connection);
|
||||
}
|
||||
|
||||
static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_release) {
|
||||
binding_t *binding;
|
||||
TAILQ_FOREACH(binding, &(config.bindings), bindings) {
|
||||
if ((binding->input_code != input_code) || (binding->release != event_is_release))
|
||||
continue;
|
||||
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a button press event (i.e. a mouse click on one of our bars).
|
||||
* We determine, whether the click occurred on a workspace button or if the scroll-
|
||||
|
@ -461,10 +472,16 @@ void handle_button(xcb_button_press_event_t *event) {
|
|||
return;
|
||||
}
|
||||
|
||||
int32_t x = event->event_x >= 0 ? event->event_x : 0;
|
||||
|
||||
DLOG("Got button %d\n", event->detail);
|
||||
|
||||
/* During button release events, only check for custom commands. */
|
||||
const bool event_is_release = (event->response_type & ~0x80) == XCB_BUTTON_RELEASE;
|
||||
if (event_is_release) {
|
||||
execute_custom_command(event->detail, event_is_release);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t x = event->event_x >= 0 ? event->event_x : 0;
|
||||
int workspace_width = 0;
|
||||
i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
* the default behavior. */
|
||||
binding_t *binding;
|
||||
TAILQ_FOREACH(binding, &(config.bindings), bindings) {
|
||||
if (binding->input_code != event->detail)
|
||||
continue;
|
||||
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
|
||||
if (execute_custom_command(event->detail, event_is_release)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -678,8 +691,26 @@ static void configure_trayclients(void) {
|
|||
*
|
||||
*/
|
||||
static void handle_client_message(xcb_client_message_event_t *event) {
|
||||
if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
|
||||
event->format == 32) {
|
||||
if (event->type == atoms[I3_SYNC]) {
|
||||
xcb_window_t window = event->data.data32[0];
|
||||
uint32_t rnd = event->data.data32[1];
|
||||
DLOG("[i3 sync protocol] Forwarding random value %d, X11 window 0x%08x to i3\n", rnd, window);
|
||||
|
||||
void *reply = scalloc(32, 1);
|
||||
xcb_client_message_event_t *ev = reply;
|
||||
|
||||
ev->response_type = XCB_CLIENT_MESSAGE;
|
||||
ev->window = window;
|
||||
ev->type = atoms[I3_SYNC];
|
||||
ev->format = 32;
|
||||
ev->data.data32[0] = window;
|
||||
ev->data.data32[1] = rnd;
|
||||
|
||||
xcb_send_event(conn, false, xcb_root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)ev);
|
||||
xcb_flush(conn);
|
||||
free(reply);
|
||||
} else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
|
||||
event->format == 32) {
|
||||
DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
|
||||
/* event->data.data32[0] is the timestamp */
|
||||
uint32_t op = event->data.data32[1];
|
||||
|
@ -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
|
||||
* then (and only then)
|
||||
* This function is called immediately before the main loop locks. We check for
|
||||
* events from X11, handle them, then flush our outgoing queue.
|
||||
*
|
||||
*/
|
||||
void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
|
||||
xcb_flush(xcb_connection);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is called immediately after the main loop locks, so when one
|
||||
* of the watchers registered an event.
|
||||
* We check whether an X-Event arrived and handle it.
|
||||
*
|
||||
*/
|
||||
void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
|
||||
xcb_generic_event_t *event;
|
||||
|
||||
if (xcb_connection_has_error(xcb_connection)) {
|
||||
|
@ -1157,6 +1178,7 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
|
|||
}
|
||||
|
||||
break;
|
||||
case XCB_BUTTON_RELEASE:
|
||||
case XCB_BUTTON_PRESS:
|
||||
/* Button press events are mouse buttons clicked on one of our bars */
|
||||
handle_button((xcb_button_press_event_t *)event);
|
||||
|
@ -1192,6 +1214,8 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
|
|||
}
|
||||
free(event);
|
||||
}
|
||||
|
||||
xcb_flush(xcb_connection);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1249,21 +1273,12 @@ char *init_xcb_early() {
|
|||
/* The various watchers to communicate with xcb */
|
||||
xcb_io = smalloc(sizeof(ev_io));
|
||||
xcb_prep = smalloc(sizeof(ev_prepare));
|
||||
xcb_chk = smalloc(sizeof(ev_check));
|
||||
|
||||
ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
|
||||
ev_prepare_init(xcb_prep, &xcb_prep_cb);
|
||||
ev_check_init(xcb_chk, &xcb_chk_cb);
|
||||
|
||||
/* Within an event loop iteration, run the xcb_chk watcher last: other
|
||||
* watchers might call xcb_flush(), which, unexpectedly, can also read
|
||||
* events into the queue (see _xcb_conn_wait). Hence, we need to drain xcb’s
|
||||
* queue last, otherwise we risk dead-locking. */
|
||||
ev_set_priority(xcb_chk, EV_MINPRI);
|
||||
|
||||
ev_io_start(main_loop, xcb_io);
|
||||
ev_prepare_start(main_loop, xcb_prep);
|
||||
ev_check_start(main_loop, xcb_chk);
|
||||
|
||||
/* Now we get the atoms and save them in a nice data structure */
|
||||
get_atoms();
|
||||
|
@ -1499,16 +1514,7 @@ void init_tray_colors(void) {
|
|||
*
|
||||
*/
|
||||
void clean_xcb(void) {
|
||||
i3_output *o_walk;
|
||||
free_workspaces();
|
||||
SLIST_FOREACH(o_walk, outputs, slist) {
|
||||
destroy_window(o_walk);
|
||||
FREE(o_walk->trayclients);
|
||||
FREE(o_walk->workspaces);
|
||||
FREE(o_walk->name);
|
||||
}
|
||||
FREE_SLIST(outputs, i3_output);
|
||||
FREE(outputs);
|
||||
free_outputs();
|
||||
|
||||
free_font();
|
||||
|
||||
|
@ -1517,11 +1523,9 @@ void clean_xcb(void) {
|
|||
xcb_aux_sync(xcb_connection);
|
||||
xcb_disconnect(xcb_connection);
|
||||
|
||||
ev_check_stop(main_loop, xcb_chk);
|
||||
ev_prepare_stop(main_loop, xcb_prep);
|
||||
ev_io_stop(main_loop, xcb_io);
|
||||
|
||||
FREE(xcb_chk);
|
||||
FREE(xcb_prep);
|
||||
FREE(xcb_io);
|
||||
}
|
||||
|
@ -1689,7 +1693,8 @@ void reconfig_windows(bool redraw_bars) {
|
|||
* */
|
||||
values[3] = XCB_EVENT_MASK_EXPOSURE |
|
||||
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
|
||||
XCB_EVENT_MASK_BUTTON_PRESS;
|
||||
XCB_EVENT_MASK_BUTTON_PRESS |
|
||||
XCB_EVENT_MASK_BUTTON_RELEASE;
|
||||
if (config.hide_on_modifier == M_DOCK) {
|
||||
/* If the bar is normally visible, catch visibility change events to suspend
|
||||
* the status process when the bar is obscured by full-screened windows. */
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <errno.h>
|
||||
#include <err.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
/**
|
||||
* 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]'.
|
||||
|
|
|
@ -38,6 +38,12 @@ void con_free(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.
|
||||
*
|
||||
|
@ -222,6 +228,22 @@ void con_unmark(Con *con, const char *name);
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -314,7 +336,16 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates,
|
|||
* 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.
|
||||
|
|
|
@ -49,6 +49,7 @@ CFGFUN(workspace_layout, const char *layout);
|
|||
CFGFUN(workspace_back_and_forth, const char *value);
|
||||
CFGFUN(focus_follows_mouse, const char *value);
|
||||
CFGFUN(mouse_warping, const char *value);
|
||||
CFGFUN(focus_wrapping, const char *value);
|
||||
CFGFUN(force_focus_wrapping, const char *value);
|
||||
CFGFUN(force_xinerama, const char *value);
|
||||
CFGFUN(disable_randr15, const char *value);
|
||||
|
@ -57,7 +58,8 @@ CFGFUN(force_display_urgency_hint, const long duration_ms);
|
|||
CFGFUN(focus_on_window_activation, const char *mode);
|
||||
CFGFUN(show_marks, const char *value);
|
||||
CFGFUN(hide_edge_borders, const char *borders);
|
||||
CFGFUN(assign, const char *workspace);
|
||||
CFGFUN(assign_output, const char *output);
|
||||
CFGFUN(assign, const char *workspace, bool is_number);
|
||||
CFGFUN(no_focus);
|
||||
CFGFUN(ipc_socket, const char *path);
|
||||
CFGFUN(restart_state, const char *path);
|
||||
|
@ -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_single, const char *colorclass, const char *color);
|
||||
CFGFUN(floating_modifier, const char *modifiers);
|
||||
CFGFUN(new_window, const char *windowtype, const char *border, const long width);
|
||||
CFGFUN(default_border, const char *windowtype, const char *border, const long width);
|
||||
CFGFUN(workspace, const char *workspace, const char *output);
|
||||
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command);
|
||||
|
||||
|
@ -82,7 +84,7 @@ CFGFUN(bar_verbose, const char *verbose);
|
|||
CFGFUN(bar_modifier, const char *modifier);
|
||||
CFGFUN(bar_wheel_up_cmd, const char *command);
|
||||
CFGFUN(bar_wheel_down_cmd, const char *command);
|
||||
CFGFUN(bar_bindsym, const char *button, const char *command);
|
||||
CFGFUN(bar_bindsym, const char *button, const char *release, const char *command);
|
||||
CFGFUN(bar_position, const char *position);
|
||||
CFGFUN(bar_i3bar_command, const char *i3bar_command);
|
||||
CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);
|
||||
|
|
|
@ -137,15 +137,24 @@ struct Config {
|
|||
* comes with i3. Thus, you can turn it off entirely. */
|
||||
bool disable_workspace_bar;
|
||||
|
||||
/** Think of the following layout: Horizontal workspace with a tabbed
|
||||
* con on the left of the screen and a terminal on the right of the
|
||||
* screen. You are in the second container in the tabbed container and
|
||||
* focus to the right. By default, i3 will set focus to the terminal on
|
||||
* the right. If you are in the first container in the tabbed container
|
||||
* however, focusing to the left will wrap. This option forces i3 to
|
||||
* always wrap, which will result in you having to use "focus parent"
|
||||
* more often. */
|
||||
bool force_focus_wrapping;
|
||||
/** When focus wrapping is enabled (the default), attempting to
|
||||
* move focus past the edge of the screen (in other words, in a
|
||||
* direction in which there are no more containers to focus) will
|
||||
* cause the focus to wrap to the opposite edge of the current
|
||||
* container. When it is disabled, nothing happens; the current
|
||||
* focus is preserved.
|
||||
*
|
||||
* Additionally, focus wrapping may be forced. Think of the
|
||||
* following layout: Horizontal workspace with a tabbed con on the
|
||||
* left of the screen and a terminal on the right of the
|
||||
* screen. You are in the second container in the tabbed container
|
||||
* and focus to the right. By default, i3 will set focus to the
|
||||
* terminal on the right. If you are in the first container in the
|
||||
* tabbed container however, focusing to the left will
|
||||
* wrap. Setting focus_wrapping to FOCUS_WRAPPING_FORCE forces i3
|
||||
* to always wrap, which will result in you having to use "focus
|
||||
* parent" more often. */
|
||||
focus_wrapping_t focus_wrapping;
|
||||
|
||||
/** By default, use the RandR API for multi-monitor setups.
|
||||
* Unfortunately, the nVidia binary graphics driver doesn't support
|
||||
|
@ -375,6 +384,9 @@ struct Barbinding {
|
|||
/** The command which is to be executed for this button. */
|
||||
char *command;
|
||||
|
||||
/** If true, the command will be executed after the button is released. */
|
||||
bool release;
|
||||
|
||||
TAILQ_ENTRY(Barbinding)
|
||||
bindings;
|
||||
};
|
||||
|
|
|
@ -133,6 +133,15 @@ typedef enum {
|
|||
POINTER_WARPING_NONE = 1
|
||||
} warping_t;
|
||||
|
||||
/**
|
||||
* Focus wrapping modes.
|
||||
*/
|
||||
typedef enum {
|
||||
FOCUS_WRAPPING_OFF = 0,
|
||||
FOCUS_WRAPPING_ON = 1,
|
||||
FOCUS_WRAPPING_FORCE = 2
|
||||
} focus_wrapping_t;
|
||||
|
||||
/**
|
||||
* Stores a rectangle, for example the size of a window, the child window etc.
|
||||
* It needs to be packed so that the compiler will not add any padding bytes.
|
||||
|
@ -556,7 +565,9 @@ struct Assignment {
|
|||
A_ANY = 0,
|
||||
A_COMMAND = (1 << 0),
|
||||
A_TO_WORKSPACE = (1 << 1),
|
||||
A_NO_FOCUS = (1 << 2)
|
||||
A_NO_FOCUS = (1 << 2),
|
||||
A_TO_WORKSPACE_NUMBER = (1 << 3),
|
||||
A_TO_OUTPUT = (1 << 4)
|
||||
} type;
|
||||
|
||||
/** the criteria to check if a window matches */
|
||||
|
@ -566,6 +577,7 @@ struct Assignment {
|
|||
union {
|
||||
char *command;
|
||||
char *workspace;
|
||||
char *output;
|
||||
} dest;
|
||||
|
||||
TAILQ_ENTRY(Assignment)
|
||||
|
|
|
@ -74,3 +74,4 @@ extern bool xcursor_supported, xkb_supported;
|
|||
extern xcb_window_t root;
|
||||
extern struct ev_loop *main_loop;
|
||||
extern bool only_check_config;
|
||||
extern bool force_xinerama;
|
||||
|
|
|
@ -60,6 +60,9 @@ typedef struct i3_ipc_header {
|
|||
/** Request the raw last loaded i3 config. */
|
||||
#define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9
|
||||
|
||||
/** Send a tick event to all subscribers. */
|
||||
#define I3_IPC_MESSAGE_TYPE_SEND_TICK 10
|
||||
|
||||
/*
|
||||
* Messages from i3 to clients
|
||||
*
|
||||
|
@ -74,6 +77,7 @@ typedef struct i3_ipc_header {
|
|||
#define I3_IPC_REPLY_TYPE_VERSION 7
|
||||
#define I3_IPC_REPLY_TYPE_BINDING_MODES 8
|
||||
#define I3_IPC_REPLY_TYPE_CONFIG 9
|
||||
#define I3_IPC_REPLY_TYPE_TICK 10
|
||||
|
||||
/*
|
||||
* Events from i3 to clients. Events have the first bit set high.
|
||||
|
@ -101,3 +105,6 @@ typedef struct i3_ipc_header {
|
|||
|
||||
/** The shutdown event will be triggered when the ipc shuts down */
|
||||
#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6)
|
||||
|
||||
/** The tick event will be sent upon a tick IPC message */
|
||||
#define I3_IPC_EVENT_TICK (I3_IPC_EVENT_MASK | 7)
|
||||
|
|
|
@ -31,6 +31,10 @@ typedef struct ipc_client {
|
|||
int num_events;
|
||||
char **events;
|
||||
|
||||
/* For clients which subscribe to the tick event: whether the first tick
|
||||
* event has been sent by i3. */
|
||||
bool first_tick_sent;
|
||||
|
||||
TAILQ_ENTRY(ipc_client)
|
||||
clients;
|
||||
} ipc_client;
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
|
||||
#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);
|
||||
|
|
|
@ -47,16 +47,21 @@
|
|||
break; \
|
||||
}
|
||||
|
||||
#define FREE(pointer) \
|
||||
do { \
|
||||
if (pointer != NULL) { \
|
||||
free(pointer); \
|
||||
pointer = NULL; \
|
||||
} \
|
||||
#define FREE(pointer) \
|
||||
do { \
|
||||
free(pointer); \
|
||||
pointer = NULL; \
|
||||
} while (0)
|
||||
|
||||
#define CALL(obj, member, ...) obj->member(obj, ##__VA_ARGS__)
|
||||
|
||||
#define SWAP(first, second, type) \
|
||||
do { \
|
||||
type tmp_SWAP = first; \
|
||||
first = second; \
|
||||
second = tmp_SWAP; \
|
||||
} while (0)
|
||||
|
||||
int min(int a, int b);
|
||||
int max(int a, int b);
|
||||
bool rect_contains(Rect rect, uint32_t x, uint32_t y);
|
||||
|
|
|
@ -43,12 +43,13 @@ void init_dpi(void) {
|
|||
}
|
||||
|
||||
char *endptr;
|
||||
dpi = strtol(resource, &endptr, 10);
|
||||
if (dpi == LONG_MAX || dpi == LONG_MIN || dpi < 0 || *endptr != '\0' || endptr == resource) {
|
||||
double in_dpi = strtod(resource, &endptr);
|
||||
if (in_dpi == HUGE_VAL || dpi < 0 || *endptr != '\0' || endptr == resource) {
|
||||
ELOG("Xft.dpi = %s is an invalid number and couldn't be parsed.\n", resource);
|
||||
dpi = 0;
|
||||
goto init_dpi_end;
|
||||
}
|
||||
dpi = (long)round(in_dpi);
|
||||
|
||||
DLOG("Found Xft.dpi = %ld.\n", dpi);
|
||||
|
||||
|
|
|
@ -22,6 +22,25 @@
|
|||
*
|
||||
*/
|
||||
int ipc_connect(const char *socket_path) {
|
||||
char *path = NULL;
|
||||
if (socket_path != NULL) {
|
||||
path = sstrdup(socket_path);
|
||||
}
|
||||
|
||||
if (path == NULL) {
|
||||
if ((path = getenv("I3SOCK")) != NULL) {
|
||||
path = sstrdup(path);
|
||||
}
|
||||
}
|
||||
|
||||
if (path == NULL) {
|
||||
path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
|
||||
}
|
||||
|
||||
if (path == NULL) {
|
||||
path = sstrdup("/tmp/i3-ipc.sock");
|
||||
}
|
||||
|
||||
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
if (sockfd == -1)
|
||||
err(EXIT_FAILURE, "Could not create socket");
|
||||
|
@ -31,9 +50,9 @@ int ipc_connect(const char *socket_path) {
|
|||
struct sockaddr_un addr;
|
||||
memset(&addr, 0, sizeof(struct sockaddr_un));
|
||||
addr.sun_family = AF_LOCAL;
|
||||
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
|
||||
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
|
||||
if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
|
||||
err(EXIT_FAILURE, "Could not connect to i3");
|
||||
|
||||
err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path);
|
||||
free(path);
|
||||
return sockfd;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <i3/ipc.h>
|
||||
|
||||
|
@ -41,14 +42,21 @@ int ipc_recv_message(int sockfd, uint32_t *message_type,
|
|||
if (n == -1)
|
||||
return -1;
|
||||
if (n == 0) {
|
||||
return -2;
|
||||
if (read_bytes == 0) {
|
||||
return -2;
|
||||
} else {
|
||||
ELOG("IPC: unexpected EOF while reading header, got %" PRIu32 " bytes, want %" PRIu32 " bytes\n",
|
||||
read_bytes, to_read);
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
|
||||
read_bytes += n;
|
||||
}
|
||||
|
||||
if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
|
||||
ELOG("IPC: invalid magic in reply\n");
|
||||
ELOG("IPC: invalid magic in header, got \"%.*s\", want \"%s\"\n",
|
||||
(int)strlen(I3_IPC_MAGIC), walk, I3_IPC_MAGIC);
|
||||
return -3;
|
||||
}
|
||||
|
||||
|
@ -61,13 +69,18 @@ int ipc_recv_message(int sockfd, uint32_t *message_type,
|
|||
*reply = smalloc(*reply_length);
|
||||
|
||||
read_bytes = 0;
|
||||
int n;
|
||||
while (read_bytes < *reply_length) {
|
||||
if ((n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes)) == -1) {
|
||||
const int n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes);
|
||||
if (n == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
if (n == 0) {
|
||||
ELOG("IPC: unexpected EOF while reading payload, got %" PRIu32 " bytes, want %" PRIu32 " bytes\n",
|
||||
read_bytes, *reply_length);
|
||||
return -3;
|
||||
}
|
||||
|
||||
read_bytes += n;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ It tries to start one of the following (in that order):
|
|||
* jed
|
||||
* gedit
|
||||
* mcedit
|
||||
* gvim
|
||||
|
||||
Please don’t complain about the order: If the user has any preference, they will
|
||||
have $VISUAL or $EDITOR set.
|
||||
|
|
|
@ -44,6 +44,9 @@ It tries to start one of the following (in that order):
|
|||
* tilix
|
||||
* terminix
|
||||
* konsole
|
||||
* kitty
|
||||
* guake
|
||||
* tilda
|
||||
|
||||
Please don’t complain about the order: If the user has any preference, they will
|
||||
have $TERMINAL set or modified their i3 configuration file.
|
||||
|
|
|
@ -258,14 +258,16 @@ state RESIZE_SET:
|
|||
-> RESIZE_WIDTH
|
||||
|
||||
state RESIZE_WIDTH:
|
||||
'px'
|
||||
mode_width = 'px', 'ppt'
|
||||
->
|
||||
height = number
|
||||
-> RESIZE_HEIGHT
|
||||
|
||||
state RESIZE_HEIGHT:
|
||||
'px', end
|
||||
-> call cmd_resize_set(&width, &height)
|
||||
mode_height = 'px', 'ppt'
|
||||
->
|
||||
end
|
||||
-> call cmd_resize_set(&width, $mode_width, &height, $mode_height)
|
||||
|
||||
# rename workspace <name> to <name>
|
||||
# rename workspace to <name>
|
||||
|
|
|
@ -29,13 +29,15 @@ state INITIAL:
|
|||
'floating_modifier' -> FLOATING_MODIFIER
|
||||
'default_orientation' -> DEFAULT_ORIENTATION
|
||||
'workspace_layout' -> WORKSPACE_LAYOUT
|
||||
windowtype = 'new_window', 'new_float' -> NEW_WINDOW
|
||||
windowtype = 'default_border', 'new_window', 'default_floating_border', 'new_float'
|
||||
-> DEFAULT_BORDER
|
||||
'hide_edge_borders' -> HIDE_EDGE_BORDERS
|
||||
'for_window' -> FOR_WINDOW
|
||||
'assign' -> ASSIGN
|
||||
'no_focus' -> NO_FOCUS
|
||||
'focus_follows_mouse' -> FOCUS_FOLLOWS_MOUSE
|
||||
'mouse_warping' -> MOUSE_WARPING
|
||||
'focus_wrapping' -> FOCUS_WRAPPING
|
||||
'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING
|
||||
'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA
|
||||
'disable_randr15', 'disable-randr15' -> DISABLE_RANDR15
|
||||
|
@ -104,25 +106,25 @@ state WORKSPACE_LAYOUT:
|
|||
layout = 'default', 'stacking', 'stacked', 'tabbed'
|
||||
-> call cfg_workspace_layout($layout)
|
||||
|
||||
# new_window <normal|1pixel|none>
|
||||
# new_float <normal|1pixel|none>
|
||||
state NEW_WINDOW:
|
||||
# <default_border|new_window> <normal|1pixel|none>
|
||||
# <default_floating_border|new_float> <normal|1pixel|none>
|
||||
state DEFAULT_BORDER:
|
||||
border = 'normal', 'pixel'
|
||||
-> NEW_WINDOW_PIXELS
|
||||
-> DEFAULT_BORDER_PIXELS
|
||||
border = '1pixel', 'none'
|
||||
-> call cfg_new_window($windowtype, $border, -1)
|
||||
-> call cfg_default_border($windowtype, $border, -1)
|
||||
|
||||
state NEW_WINDOW_PIXELS:
|
||||
state DEFAULT_BORDER_PIXELS:
|
||||
end
|
||||
-> call cfg_new_window($windowtype, $border, 2)
|
||||
-> call cfg_default_border($windowtype, $border, 2)
|
||||
width = number
|
||||
-> NEW_WINDOW_PIXELS_PX
|
||||
-> DEFAULT_BORDER_PIXELS_PX
|
||||
|
||||
state NEW_WINDOW_PIXELS_PX:
|
||||
state DEFAULT_BORDER_PIXELS_PX:
|
||||
'px'
|
||||
->
|
||||
end
|
||||
-> call cfg_new_window($windowtype, $border, &width)
|
||||
-> call cfg_default_border($windowtype, $border, &width)
|
||||
|
||||
# hide_edge_borders <none|vertical|horizontal|both|smart>
|
||||
# also hide_edge_borders <bool> for compatibility
|
||||
|
@ -141,7 +143,7 @@ state FOR_WINDOW_COMMAND:
|
|||
command = string
|
||||
-> call cfg_for_window($command)
|
||||
|
||||
# assign <criteria> [→] workspace
|
||||
# assign <criteria> [→] [workspace | output] <name>
|
||||
state ASSIGN:
|
||||
'['
|
||||
-> call cfg_criteria_init(ASSIGN_WORKSPACE); CRITERIA
|
||||
|
@ -149,10 +151,22 @@ state ASSIGN:
|
|||
state ASSIGN_WORKSPACE:
|
||||
'→'
|
||||
->
|
||||
'output'
|
||||
-> ASSIGN_OUTPUT
|
||||
'workspace'
|
||||
->
|
||||
'number'
|
||||
-> ASSIGN_WORKSPACE_NUMBER
|
||||
workspace = string
|
||||
-> call cfg_assign($workspace)
|
||||
-> call cfg_assign($workspace, 0)
|
||||
|
||||
state ASSIGN_OUTPUT:
|
||||
output = string
|
||||
-> call cfg_assign_output($output)
|
||||
|
||||
state ASSIGN_WORKSPACE_NUMBER:
|
||||
number = string
|
||||
-> call cfg_assign($number, 1)
|
||||
|
||||
# no_focus <criteria>
|
||||
state NO_FOCUS:
|
||||
|
@ -197,6 +211,11 @@ state MOUSE_WARPING:
|
|||
value = 'none', 'output'
|
||||
-> call cfg_mouse_warping($value)
|
||||
|
||||
# focus_wrapping
|
||||
state FOCUS_WRAPPING:
|
||||
value = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive', 'force'
|
||||
-> call cfg_focus_wrapping($value)
|
||||
|
||||
# force_focus_wrapping
|
||||
state FORCE_FOCUS_WRAPPING:
|
||||
value = word
|
||||
|
@ -483,12 +502,16 @@ state BAR_WHEEL_DOWN_CMD:
|
|||
-> call cfg_bar_wheel_down_cmd($command); BAR
|
||||
|
||||
state BAR_BINDSYM:
|
||||
release = '--release'
|
||||
->
|
||||
button = word
|
||||
-> BAR_BINDSYM_COMMAND
|
||||
|
||||
state BAR_BINDSYM_COMMAND:
|
||||
release = '--release'
|
||||
->
|
||||
command = string
|
||||
-> call cfg_bar_bindsym($button, $command); BAR
|
||||
-> call cfg_bar_bindsym($button, $release, $command); BAR
|
||||
|
||||
state BAR_POSITION:
|
||||
position = 'top', 'bottom'
|
||||
|
|
12
release.sh
12
release.sh
|
@ -1,9 +1,9 @@
|
|||
#!/bin/zsh
|
||||
# This script is used to prepare a new release of i3.
|
||||
|
||||
export RELEASE_VERSION="4.13"
|
||||
export PREVIOUS_VERSION="4.12"
|
||||
export RELEASE_BRANCH="next"
|
||||
export RELEASE_VERSION="4.14.1"
|
||||
export PREVIOUS_VERSION="4.14"
|
||||
export RELEASE_BRANCH="master"
|
||||
|
||||
if [ ! -e "../i3.github.io" ]
|
||||
then
|
||||
|
@ -232,9 +232,9 @@ echo ""
|
|||
echo " cd ${TMPDIR}"
|
||||
echo " sendmail -t < email.txt"
|
||||
echo ""
|
||||
echo "Update milestones on GitHub:"
|
||||
echo " Set due date of ${RELEASE_VERSION} to $(date +'%Y-$m-%d') and close the milestone"
|
||||
echo " Create milestone for the next version with unset due date"
|
||||
echo "Update milestones on GitHub (only for new major versions):"
|
||||
echo " Set due date of ${RELEASE_VERSION} to $(date +'%Y-%m-%d') and close the milestone"
|
||||
echo " Create milestone for the next major version with unset due date"
|
||||
echo ""
|
||||
echo "Announce on:"
|
||||
echo " twitter"
|
||||
|
|
|
@ -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);
|
||||
for (int i = 0; i < 5 && remaining > 0; i++) {
|
||||
const int len = strnlen(walk, remaining);
|
||||
remaining -= len;
|
||||
switch (i) {
|
||||
case 0:
|
||||
sasprintf((char **)&(xkb_names->rules), "%.*s", len, walk);
|
||||
|
@ -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);
|
||||
walk += (len + 1);
|
||||
remaining -= (len + 1);
|
||||
}
|
||||
|
||||
free(atom_reply);
|
||||
|
|
|
@ -49,7 +49,7 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
|
|||
break;
|
||||
}
|
||||
|
||||
bool res = resize_find_tiling_participants(&first, &second, search_direction);
|
||||
bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
|
||||
if (!res) {
|
||||
LOG("No second container in this direction found.\n");
|
||||
return false;
|
||||
|
@ -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. */
|
||||
Con *focused = con->parent;
|
||||
focused = TAILQ_FIRST(&(focused->focus_head));
|
||||
con_focus(focused);
|
||||
con_activate(focused);
|
||||
/* To prevent scrolling from going outside the container (see ticket
|
||||
* #557), we first check if scrolling is possible at all. */
|
||||
bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
|
||||
|
@ -256,7 +256,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
}
|
||||
|
||||
/* 2: focus this con. */
|
||||
con_focus(con);
|
||||
con_activate(con);
|
||||
|
||||
/* 3: For floating containers, we also want to raise them on click.
|
||||
* We will skip handling events on floating cons in fullscreen mode */
|
||||
|
|
303
src/commands.c
303
src/commands.c
|
@ -78,14 +78,6 @@
|
|||
} \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Returns true if a is definitely greater than b (using the given epsilon)
|
||||
*
|
||||
*/
|
||||
static bool definitelyGreaterThan(float a, float b, float epsilon) {
|
||||
return (a - b) > ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks whether we switched to a new workspace and returns false in that case,
|
||||
* signaling that further workspace switching should be done by the calling function
|
||||
|
@ -269,14 +261,20 @@ void cmd_criteria_add(I3_CMD, const char *ctype, const char *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
|
||||
* next|prev|next_on_output|prev_on_output|current'.
|
||||
*
|
||||
*/
|
||||
void cmd_move_con_to_workspace(I3_CMD, const char *which) {
|
||||
owindow *current;
|
||||
|
||||
DLOG("which=%s\n", which);
|
||||
|
||||
/* We have nothing to move:
|
||||
|
@ -309,10 +307,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) {
|
|||
return;
|
||||
}
|
||||
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
DLOG("matching: %p / %s\n", current->con, current->con->name);
|
||||
con_move_to_workspace(current->con, ws, true, false, false);
|
||||
}
|
||||
move_matches_to_workspace(ws);
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
// XXX: default reply for now, make this a better reply
|
||||
|
@ -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) {
|
||||
owindow *current;
|
||||
Con *ws;
|
||||
|
||||
ws = workspace_back_and_forth_get();
|
||||
|
||||
Con *ws = workspace_back_and_forth_get();
|
||||
if (ws == NULL) {
|
||||
yerror("No workspace was previously active.");
|
||||
return;
|
||||
|
@ -336,10 +327,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
|
|||
|
||||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
DLOG("matching: %p / %s\n", current->con, current->con->name);
|
||||
con_move_to_workspace(current->con, ws, true, false, false);
|
||||
}
|
||||
move_matches_to_workspace(ws);
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
// XXX: default reply for now, make this a better reply
|
||||
|
@ -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);
|
||||
owindow *current;
|
||||
|
||||
/* We have nothing to move:
|
||||
* 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;
|
||||
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
DLOG("matching: %p / %s\n", current->con, current->con->name);
|
||||
con_move_to_workspace(current->con, ws, true, false, false);
|
||||
}
|
||||
move_matches_to_workspace(ws);
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
// XXX: default reply for now, make this a better reply
|
||||
|
@ -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) {
|
||||
const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
|
||||
owindow *current;
|
||||
|
||||
/* We have nothing to move:
|
||||
* when criteria was specified but didn't match any window or
|
||||
|
@ -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);
|
||||
/* get the workspace */
|
||||
Con *output, *workspace = NULL;
|
||||
Con *output, *ws = NULL;
|
||||
|
||||
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)
|
||||
GREP_FIRST(workspace, output_get_content(output),
|
||||
GREP_FIRST(ws, output_get_content(output),
|
||||
child->num == parsed_num);
|
||||
|
||||
if (!workspace) {
|
||||
workspace = workspace_get(which, NULL);
|
||||
if (!ws) {
|
||||
ws = workspace_get(which, NULL);
|
||||
}
|
||||
|
||||
if (!no_auto_back_and_forth)
|
||||
workspace = maybe_auto_back_and_forth_workspace(workspace);
|
||||
ws = maybe_auto_back_and_forth_workspace(ws);
|
||||
|
||||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
DLOG("matching: %p / %s\n", current->con, current->con->name);
|
||||
con_move_to_workspace(current->con, workspace, true, false, false);
|
||||
}
|
||||
move_matches_to_workspace(ws);
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
// XXX: default reply for now, make this a better reply
|
||||
|
@ -511,7 +491,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c
|
|||
else
|
||||
search_direction = D_DOWN;
|
||||
|
||||
bool res = resize_find_tiling_participants(&first, &second, search_direction);
|
||||
bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
|
||||
if (!res) {
|
||||
LOG("No second container in this direction found.\n");
|
||||
ysuccess(false);
|
||||
|
@ -525,8 +505,8 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, c
|
|||
LOG("default percentage = %f\n", percentage);
|
||||
|
||||
/* resize */
|
||||
LOG("second->percent = %f\n", second->percent);
|
||||
LOG("first->percent before = %f\n", first->percent);
|
||||
LOG("second->percent before = %f\n", second->percent);
|
||||
if (first->percent == 0.0)
|
||||
first->percent = percentage;
|
||||
if (second->percent == 0.0)
|
||||
|
@ -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);
|
||||
LOG("new_first_percent = %f\n", new_first_percent);
|
||||
LOG("new_second_percent = %f\n", new_second_percent);
|
||||
/* Ensure that the new percentages are positive and greater than
|
||||
* 0.05 to have a reasonable minimum size. */
|
||||
if (definitelyGreaterThan(new_first_percent, 0.05, DBL_EPSILON) &&
|
||||
definitelyGreaterThan(new_second_percent, 0.05, DBL_EPSILON)) {
|
||||
first->percent += ((double)ppt / 100.0);
|
||||
second->percent -= ((double)ppt / 100.0);
|
||||
/* Ensure that the new percentages are positive. */
|
||||
if (new_first_percent > 0.0 && new_second_percent > 0.0) {
|
||||
first->percent = new_first_percent;
|
||||
second->percent = new_second_percent;
|
||||
LOG("first->percent after = %f\n", first->percent);
|
||||
LOG("second->percent after = %f\n", second->percent);
|
||||
} else {
|
||||
|
@ -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) {
|
||||
LOG("width/height resize\n");
|
||||
|
||||
/* get the appropriate current container (skip stacked/tabbed cons) */
|
||||
while (current->parent->layout == L_STACKED ||
|
||||
current->parent->layout == L_TABBED)
|
||||
current = current->parent;
|
||||
|
||||
/* Then further go up until we find one with the matching orientation. */
|
||||
orientation_t search_orientation =
|
||||
(strcmp(direction, "width") == 0 ? HORIZ : VERT);
|
||||
|
||||
while (current->type != CT_WORKSPACE &&
|
||||
current->type != CT_FLOATING_CON &&
|
||||
(con_orientation(current->parent) != search_orientation || con_num_children(current->parent) == 1))
|
||||
current = current->parent;
|
||||
Con *dummy = NULL;
|
||||
direction_t search_direction = (strcmp(direction, "width") == 0 ? D_LEFT : D_DOWN);
|
||||
bool search_result = resize_find_tiling_participants(¤t, &dummy, search_direction, true);
|
||||
if (search_result == false) {
|
||||
ysuccess(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* get the default percentage */
|
||||
int children = con_num_children(current->parent);
|
||||
|
@ -572,24 +546,6 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way
|
|||
double percentage = 1.0 / children;
|
||||
LOG("default percentage = %f\n", percentage);
|
||||
|
||||
orientation_t orientation = con_orientation(current->parent);
|
||||
|
||||
if ((orientation == HORIZ &&
|
||||
strcmp(direction, "height") == 0) ||
|
||||
(orientation == VERT &&
|
||||
strcmp(direction, "width") == 0)) {
|
||||
LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
|
||||
(orientation == HORIZ ? "horizontal" : "vertical"));
|
||||
ysuccess(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (children == 1) {
|
||||
LOG("This is the only container, cannot resize.\n");
|
||||
ysuccess(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure all the other children have a percentage set. */
|
||||
Con *child;
|
||||
TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
|
||||
|
@ -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);
|
||||
LOG("new_current_percent = %f\n", new_current_percent);
|
||||
LOG("subtract_percent = %f\n", subtract_percent);
|
||||
/* Ensure that the new percentages are positive and greater than
|
||||
* 0.05 to have a reasonable minimum size. */
|
||||
/* Ensure that the new percentages are positive. */
|
||||
TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
|
||||
if (child == current)
|
||||
continue;
|
||||
if (!definitelyGreaterThan(child->percent - subtract_percent, 0.05, DBL_EPSILON)) {
|
||||
if (child->percent - subtract_percent <= 0.0) {
|
||||
LOG("Not resizing, already at minimum size (child %p would end up with a size of %.f\n", child, child->percent - subtract_percent);
|
||||
ysuccess(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON)) {
|
||||
if (new_current_percent <= 0.0) {
|
||||
LOG("Not resizing, already at minimum size\n");
|
||||
ysuccess(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
current->percent += ((double)ppt / 100.0);
|
||||
current->percent = new_current_percent;
|
||||
LOG("current->percent after = %f\n", current->percent);
|
||||
|
||||
TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
|
||||
|
@ -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) {
|
||||
DLOG("resizing to %ldx%ld px\n", cwidth, cheight);
|
||||
if (cwidth <= 0 || cheight <= 0) {
|
||||
ELOG("Resize failed: dimensions cannot be negative (was %ldx%ld)\n", cwidth, cheight);
|
||||
void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height) {
|
||||
DLOG("resizing to %ld %s x %ld %s\n", cwidth, mode_width, cheight, mode_height);
|
||||
if (cwidth < 0 || cheight < 0) {
|
||||
ELOG("Resize failed: dimensions cannot be negative (was %ld %s x %ld %s)\n", cwidth, mode_width, cheight, mode_height);
|
||||
return;
|
||||
}
|
||||
|
||||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
owindow *current;
|
||||
bool success = true;
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
Con *floating_con;
|
||||
if ((floating_con = con_inside_floating(current->con))) {
|
||||
Con *output = con_get_output(floating_con);
|
||||
if (cwidth == 0) {
|
||||
cwidth = output->rect.width;
|
||||
} else if (mode_width && strcmp(mode_width, "ppt") == 0) {
|
||||
cwidth = output->rect.width * ((double)cwidth / 100.0);
|
||||
}
|
||||
if (cheight == 0) {
|
||||
cheight = output->rect.height;
|
||||
} else if (mode_height && strcmp(mode_height, "ppt") == 0) {
|
||||
cheight = output->rect.height * ((double)cheight / 100.0);
|
||||
}
|
||||
floating_resize(floating_con, cwidth, cheight);
|
||||
} else {
|
||||
ELOG("Resize failed: %p not a floating container\n", current->con);
|
||||
if (current->con->window && current->con->window->dock) {
|
||||
DLOG("This is a dock window. Not resizing (con = %p)\n)", current->con);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cwidth > 0 && mode_width && strcmp(mode_width, "ppt") == 0) {
|
||||
/* get the appropriate current container (skip stacked/tabbed cons) */
|
||||
Con *target = current->con;
|
||||
Con *dummy;
|
||||
resize_find_tiling_participants(&target, &dummy, D_LEFT, true);
|
||||
|
||||
/* Calculate new size for the target container */
|
||||
double current_percent = target->percent;
|
||||
char *action_string;
|
||||
long adjustment;
|
||||
|
||||
if (current_percent > cwidth) {
|
||||
action_string = "shrink";
|
||||
adjustment = (int)(current_percent * 100) - cwidth;
|
||||
} else {
|
||||
action_string = "grow";
|
||||
adjustment = cwidth - (int)(current_percent * 100);
|
||||
}
|
||||
|
||||
/* perform resizing and report failure if not possible */
|
||||
if (!cmd_resize_tiling_width_height(current_match, cmd_output,
|
||||
target, action_string, "width", adjustment)) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (cheight > 0 && mode_width && strcmp(mode_width, "ppt") == 0) {
|
||||
/* get the appropriate current container (skip stacked/tabbed cons) */
|
||||
Con *target = current->con;
|
||||
Con *dummy;
|
||||
resize_find_tiling_participants(&target, &dummy, D_DOWN, true);
|
||||
|
||||
/* Calculate new size for the target container */
|
||||
double current_percent = target->percent;
|
||||
char *action_string;
|
||||
long adjustment;
|
||||
|
||||
if (current_percent > cheight) {
|
||||
action_string = "shrink";
|
||||
adjustment = (int)(current_percent * 100) - cheight;
|
||||
} else {
|
||||
action_string = "grow";
|
||||
adjustment = cheight - (int)(current_percent * 100);
|
||||
}
|
||||
|
||||
/* perform resizing and report failure if not possible */
|
||||
if (!cmd_resize_tiling_width_height(current_match, cmd_output,
|
||||
target, action_string, "height", adjustment)) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
// XXX: default reply for now, make this a better reply
|
||||
ysuccess(true);
|
||||
ysuccess(success);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -760,6 +781,7 @@ void cmd_nop(I3_CMD, const char *comment) {
|
|||
LOG("-------------------------------------------------\n");
|
||||
LOG(" NOP: %s\n", comment);
|
||||
LOG("-------------------------------------------------\n");
|
||||
ysuccess(true);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1044,25 +1066,7 @@ void cmd_move_con_to_output(I3_CMD, const char *name) {
|
|||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
DLOG("matching: %p / %s\n", current->con, current->con->name);
|
||||
|
||||
Output *current_output = get_output_for_con(current->con);
|
||||
assert(current_output != NULL);
|
||||
|
||||
Output *output = get_output_from_string(current_output, name);
|
||||
if (output == NULL) {
|
||||
ELOG("Could not find output \"%s\", skipping.\n", name);
|
||||
had_error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Con *ws = NULL;
|
||||
GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
|
||||
if (ws == NULL) {
|
||||
ELOG("Could not find a visible workspace on output %p.\n", output);
|
||||
had_error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
con_move_to_workspace(current->con, ws, true, false, false);
|
||||
had_error |= !con_move_to_output_name(current->con, name, true);
|
||||
}
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
|
@ -1132,6 +1136,10 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) {
|
|||
owindow *current;
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
Con *ws = con_get_workspace(current->con);
|
||||
if (con_is_internal(ws)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool success = workspace_move_to_output(ws, name);
|
||||
if (!success) {
|
||||
ELOG("Failed to move workspace to output.\n");
|
||||
|
@ -1257,6 +1265,20 @@ void cmd_focus_direction(I3_CMD, const char *direction) {
|
|||
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'.
|
||||
*
|
||||
|
@ -1264,28 +1286,34 @@ void cmd_focus_direction(I3_CMD, const char *direction) {
|
|||
void cmd_focus_window_mode(I3_CMD, const char *window_mode) {
|
||||
DLOG("window_mode = %s\n", window_mode);
|
||||
|
||||
Con *ws = con_get_workspace(focused);
|
||||
if (ws != NULL) {
|
||||
if (strcmp(window_mode, "mode_toggle") == 0) {
|
||||
if (con_inside_floating(focused))
|
||||
window_mode = "tiling";
|
||||
else
|
||||
window_mode = "floating";
|
||||
}
|
||||
Con *current;
|
||||
TAILQ_FOREACH(current, &(ws->focus_head), focused) {
|
||||
if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) ||
|
||||
(strcmp(window_mode, "tiling") == 0 && current->type == CT_FLOATING_CON))
|
||||
continue;
|
||||
|
||||
con_focus(con_descend_focused(current));
|
||||
break;
|
||||
}
|
||||
bool to_floating = false;
|
||||
if (strcmp(window_mode, "mode_toggle") == 0) {
|
||||
to_floating = !con_inside_floating(focused);
|
||||
} else if (strcmp(window_mode, "floating") == 0) {
|
||||
to_floating = true;
|
||||
} else if (strcmp(window_mode, "tiling") == 0) {
|
||||
to_floating = false;
|
||||
}
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
// XXX: default reply for now, make this a better reply
|
||||
ysuccess(true);
|
||||
Con *ws = con_get_workspace(focused);
|
||||
Con *current;
|
||||
bool success = false;
|
||||
TAILQ_FOREACH(current, &(ws->focus_head), focused) {
|
||||
if ((to_floating && current->type != CT_FLOATING_CON) ||
|
||||
(!to_floating && current->type == CT_FLOATING_CON))
|
||||
continue;
|
||||
|
||||
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)
|
||||
continue;
|
||||
|
||||
/* Check the fullscreen focus constraints. */
|
||||
if (!con_fullscreen_permits_focusing(current->con)) {
|
||||
LOG("Cannot change focus while in fullscreen mode (fullscreen rules).\n");
|
||||
ysuccess(false);
|
||||
return;
|
||||
}
|
||||
|
||||
/* In case this is a scratchpad window, call scratchpad_show(). */
|
||||
if (ws == __i3_scratch) {
|
||||
scratchpad_show(current->con);
|
||||
|
@ -1372,13 +1393,13 @@ void cmd_focus(I3_CMD) {
|
|||
* So we focus 'current' to make it the currently focused window of
|
||||
* the target workspace, then revert focus. */
|
||||
Con *currently_focused = focused;
|
||||
con_focus(current->con);
|
||||
con_focus(currently_focused);
|
||||
cmd_focus_force_focus(current->con);
|
||||
con_activate(currently_focused);
|
||||
|
||||
/* Now switch to the workspace, then focus */
|
||||
workspace_show(ws);
|
||||
LOG("focusing %p / %s\n", current->con, current->con->name);
|
||||
con_focus(current->con);
|
||||
con_activate(current->con);
|
||||
count++;
|
||||
}
|
||||
|
||||
|
@ -1490,7 +1511,7 @@ void cmd_move_direction(I3_CMD, const char *direction, long move_px) {
|
|||
|
||||
/* the move command should not disturb focus */
|
||||
if (focused != initially_focused)
|
||||
con_focus(initially_focused);
|
||||
con_activate(initially_focused);
|
||||
|
||||
// XXX: default reply for now, make this a better reply
|
||||
ysuccess(true);
|
||||
|
@ -1615,7 +1636,7 @@ void cmd_open(I3_CMD) {
|
|||
LOG("opening new container\n");
|
||||
Con *con = tree_open_con(NULL, NULL);
|
||||
con->layout = L_SPLITH;
|
||||
con_focus(con);
|
||||
con_activate(con);
|
||||
|
||||
y(map_open);
|
||||
ystr("success");
|
||||
|
@ -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. */
|
||||
con_focus(previously_focused);
|
||||
con_activate(previously_focused);
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
ysuccess(true);
|
||||
|
|
178
src/con.c
178
src/con.c
|
@ -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.
|
||||
*
|
||||
|
@ -795,6 +816,62 @@ Con *con_for_window(Con *con, i3Window *window, Match **store_match) {
|
|||
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.
|
||||
*
|
||||
|
@ -994,9 +1071,9 @@ void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode) {
|
|||
Con *old_focused = focused;
|
||||
if (fullscreen_mode == CF_GLOBAL && cur_ws != con_ws)
|
||||
workspace_show(con_ws);
|
||||
con_focus(con);
|
||||
con_activate(con);
|
||||
if (fullscreen_mode != CF_GLOBAL && cur_ws != con_ws)
|
||||
con_focus(old_focused);
|
||||
con_activate(old_focused);
|
||||
|
||||
con_set_fullscreen_mode(con, fullscreen_mode);
|
||||
}
|
||||
|
@ -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
|
||||
* back to the originally-focused workspace. */
|
||||
Con *old_focus = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head));
|
||||
con_focus(con_descend_focused(con));
|
||||
con_activate(con_descend_focused(con));
|
||||
|
||||
/* Restore focus if the output's focused workspace has changed. */
|
||||
if (con_get_workspace(focused) != old_focus)
|
||||
con_focus(old_focus);
|
||||
con_activate(old_focus);
|
||||
}
|
||||
|
||||
/* 7: when moving to another workspace, we leave the focus on the current
|
||||
|
@ -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.
|
||||
* Otherwise we would give focus to some window on different workspace. */
|
||||
if (!ignore_focus && source_ws == current_ws)
|
||||
con_focus(con_descend_focused(focus_next));
|
||||
con_activate(con_descend_focused(focus_next));
|
||||
|
||||
/* 8. If anything within the container is associated with a startup sequence,
|
||||
* delete it so child windows won't be created on the old workspace. */
|
||||
|
@ -1300,12 +1377,33 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
|
|||
* visible workspace on the given output.
|
||||
*
|
||||
*/
|
||||
void con_move_to_output(Con *con, Output *output) {
|
||||
void con_move_to_output(Con *con, Output *output, bool fix_coordinates) {
|
||||
Con *ws = NULL;
|
||||
GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
|
||||
assert(ws != NULL);
|
||||
DLOG("Moving con %p to output %s\n", con, output_primary_name(output));
|
||||
con_move_to_workspace(con, ws, false, false, false);
|
||||
con_move_to_workspace(con, ws, fix_coordinates, false, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the given container to the currently focused container on the
|
||||
* visible workspace on the output specified by the given name.
|
||||
* The current output for the container is used to resolve relative names
|
||||
* such as left, right, up, down.
|
||||
*
|
||||
*/
|
||||
bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates) {
|
||||
Output *current_output = get_output_for_con(con);
|
||||
assert(current_output != NULL);
|
||||
|
||||
Output *output = get_output_from_string(current_output, name);
|
||||
if (output == NULL) {
|
||||
ELOG("Could not find output \"%s\"\n", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
con_move_to_output(con, output, fix_coordinates);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1735,7 +1833,7 @@ void con_set_layout(Con *con, layout_t layout) {
|
|||
con->workspace_layout = ws_layout;
|
||||
DLOG("Setting layout to %d\n", layout);
|
||||
con->layout = layout;
|
||||
} else if (layout == L_STACKED || layout == L_TABBED) {
|
||||
} else if (layout == L_STACKED || layout == L_TABBED || layout == L_SPLITV || layout == L_SPLITH) {
|
||||
DLOG("Creating new split container\n");
|
||||
/* 1: create a new split container */
|
||||
Con *new = con_new(NULL, NULL);
|
||||
|
@ -1746,17 +1844,9 @@ void con_set_layout(Con *con, layout_t layout) {
|
|||
new->layout = layout;
|
||||
new->last_split_layout = con->last_split_layout;
|
||||
|
||||
/* Save the container that was focused before we move containers
|
||||
* around, but only if the container is visible (otherwise focus
|
||||
* will be restored properly automatically when switching). */
|
||||
Con *old_focused = TAILQ_FIRST(&(con->focus_head));
|
||||
if (old_focused == TAILQ_END(&(con->focus_head)))
|
||||
old_focused = NULL;
|
||||
if (old_focused != NULL &&
|
||||
!workspace_is_visible(con_get_workspace(old_focused)))
|
||||
old_focused = NULL;
|
||||
|
||||
/* 3: move the existing cons of this workspace below the new con */
|
||||
Con **focus_order = get_focus_order(con);
|
||||
|
||||
DLOG("Moving cons\n");
|
||||
Con *child;
|
||||
while (!TAILQ_EMPTY(&(con->nodes_head))) {
|
||||
|
@ -1765,13 +1855,13 @@ void con_set_layout(Con *con, layout_t layout) {
|
|||
con_attach(child, new, true);
|
||||
}
|
||||
|
||||
set_focus_order(new, focus_order);
|
||||
free(focus_order);
|
||||
|
||||
/* 4: attach the new split container to the workspace */
|
||||
DLOG("Attaching new split to ws\n");
|
||||
con_attach(new, con, false);
|
||||
|
||||
if (old_focused)
|
||||
con_focus(old_focused);
|
||||
|
||||
tree_flatten(croot);
|
||||
}
|
||||
con_force_split_parents_redraw(con);
|
||||
|
@ -1827,6 +1917,10 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
|
|||
* change to the opposite split layout. */
|
||||
if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) {
|
||||
layout = parent->last_split_layout;
|
||||
/* In case last_split_layout was not initialized… */
|
||||
if (layout == L_DEFAULT) {
|
||||
layout = L_SPLITH;
|
||||
}
|
||||
} else {
|
||||
layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH;
|
||||
}
|
||||
|
@ -2045,14 +2139,7 @@ bool con_fullscreen_permits_focusing(Con *con) {
|
|||
|
||||
/* Allow it only if the container to be focused is contained within the
|
||||
* current fullscreen container. */
|
||||
do {
|
||||
if (con->parent == fs)
|
||||
return true;
|
||||
con = con->parent;
|
||||
} while (con);
|
||||
|
||||
/* Focusing con would hide it behind a fullscreen window, disallow it. */
|
||||
return false;
|
||||
return con_has_parent(con, fs);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -2294,15 +2381,14 @@ bool con_swap(Con *first, Con *second) {
|
|||
Con *current_ws = con_get_workspace(old_focus);
|
||||
const bool focused_within_first = (first == old_focus || con_has_parent(old_focus, first));
|
||||
const bool focused_within_second = (second == old_focus || con_has_parent(old_focus, second));
|
||||
fullscreen_mode_t first_fullscreen_mode = first->fullscreen_mode;
|
||||
fullscreen_mode_t second_fullscreen_mode = second->fullscreen_mode;
|
||||
|
||||
if (!con_fullscreen_permits_focusing(first_ws)) {
|
||||
DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", first_ws->name);
|
||||
return false;
|
||||
if (first_fullscreen_mode != CF_NONE) {
|
||||
con_disable_fullscreen(first);
|
||||
}
|
||||
|
||||
if (!con_fullscreen_permits_focusing(second_ws)) {
|
||||
DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", second_ws->name);
|
||||
return false;
|
||||
if (second_fullscreen_mode != CF_NONE) {
|
||||
con_disable_fullscreen(second);
|
||||
}
|
||||
|
||||
double first_percent = first->percent;
|
||||
|
@ -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
|
||||
* moved the first one at this point.*/
|
||||
if (first_ws != second_ws && focused_within_first) {
|
||||
con_focus(con_descend_focused(current_ws));
|
||||
con_activate(con_descend_focused(current_ws));
|
||||
}
|
||||
|
||||
/* Move second to where first has been originally. */
|
||||
|
@ -2384,15 +2470,15 @@ bool con_swap(Con *first, Con *second) {
|
|||
*/
|
||||
if (focused_within_first) {
|
||||
if (first_ws == second_ws) {
|
||||
con_focus(old_focus);
|
||||
con_activate(old_focus);
|
||||
} else {
|
||||
con_focus(con_descend_focused(second));
|
||||
con_activate(con_descend_focused(second));
|
||||
}
|
||||
} else if (focused_within_second) {
|
||||
if (first_ws == second_ws) {
|
||||
con_focus(old_focus);
|
||||
con_activate(old_focus);
|
||||
} else {
|
||||
con_focus(con_descend_focused(first));
|
||||
con_activate(con_descend_focused(first));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2403,7 +2489,17 @@ bool con_swap(Con *first, Con *second) {
|
|||
second->percent = first_percent;
|
||||
fake->percent = 0.0;
|
||||
|
||||
SWAP(first_fullscreen_mode, second_fullscreen_mode, fullscreen_mode_t);
|
||||
|
||||
swap_end:
|
||||
/* The two windows exchange their original fullscreen status */
|
||||
if (first_fullscreen_mode != CF_NONE) {
|
||||
con_enable_fullscreen(first, first_fullscreen_mode);
|
||||
}
|
||||
if (second_fullscreen_mode != CF_NONE) {
|
||||
con_enable_fullscreen(second, second_fullscreen_mode);
|
||||
}
|
||||
|
||||
/* We don't actually need this since percentages-wise we haven't changed
|
||||
* anything, but we'll better be safe than sorry and just make sure as we'd
|
||||
* otherwise crash i3. */
|
||||
|
|
|
@ -227,6 +227,8 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
|
|||
if (config.workspace_urgency_timer == 0)
|
||||
config.workspace_urgency_timer = 0.5;
|
||||
|
||||
config.focus_wrapping = FOCUS_WRAPPING_ON;
|
||||
|
||||
parse_configuration(override_configpath, true);
|
||||
|
||||
if (reload) {
|
||||
|
|
|
@ -197,7 +197,7 @@ CFGFUN(workspace_layout, const char *layout) {
|
|||
config.default_layout = L_TABBED;
|
||||
}
|
||||
|
||||
CFGFUN(new_window, const char *windowtype, const char *border, const long width) {
|
||||
CFGFUN(default_border, const char *windowtype, const char *border, const long width) {
|
||||
int border_style;
|
||||
int border_width;
|
||||
|
||||
|
@ -215,7 +215,8 @@ CFGFUN(new_window, const char *windowtype, const char *border, const long width)
|
|||
border_width = width;
|
||||
}
|
||||
|
||||
if (strcmp(windowtype, "new_window") == 0) {
|
||||
if ((strcmp(windowtype, "default_border") == 0) ||
|
||||
(strcmp(windowtype, "new_window") == 0)) {
|
||||
DLOG("default tiled border style = %d and border width = %d (%d physical px)\n",
|
||||
border_style, border_width, logical_px(border_width));
|
||||
config.default_border = border_style;
|
||||
|
@ -264,8 +265,27 @@ CFGFUN(disable_randr15, const char *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) {
|
||||
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) {
|
||||
|
@ -377,15 +397,35 @@ CFGFUN(color, const char *colorclass, const char *border, const char *background
|
|||
#undef APPLY_COLORS
|
||||
}
|
||||
|
||||
CFGFUN(assign, const char *workspace) {
|
||||
CFGFUN(assign_output, const char *output) {
|
||||
if (match_is_empty(current_match)) {
|
||||
ELOG("Match is empty, ignoring this assignment\n");
|
||||
return;
|
||||
}
|
||||
|
||||
DLOG("New assignment, using above criteria, to output \"%s\".\n", output);
|
||||
Assignment *assignment = scalloc(1, sizeof(Assignment));
|
||||
match_copy(&(assignment->match), current_match);
|
||||
assignment->type = A_TO_OUTPUT;
|
||||
assignment->dest.output = sstrdup(output);
|
||||
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
|
||||
}
|
||||
|
||||
CFGFUN(assign, const char *workspace, bool is_number) {
|
||||
if (match_is_empty(current_match)) {
|
||||
ELOG("Match is empty, ignoring this assignment\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_number && ws_name_to_number(workspace) == -1) {
|
||||
ELOG("Could not parse initial part of \"%s\" as a number.\n", workspace);
|
||||
return;
|
||||
}
|
||||
|
||||
DLOG("New assignment, using above criteria, to workspace \"%s\".\n", workspace);
|
||||
Assignment *assignment = scalloc(1, sizeof(Assignment));
|
||||
match_copy(&(assignment->match), current_match);
|
||||
assignment->type = A_TO_WORKSPACE;
|
||||
assignment->type = is_number ? A_TO_WORKSPACE_NUMBER : A_TO_WORKSPACE;
|
||||
assignment->dest.workspace = sstrdup(workspace);
|
||||
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
|
||||
}
|
||||
|
@ -463,7 +503,7 @@ CFGFUN(bar_modifier, const char *modifier) {
|
|||
current_bar->modifier = M_NONE;
|
||||
}
|
||||
|
||||
static void bar_configure_binding(const char *button, const char *command) {
|
||||
static void bar_configure_binding(const char *button, const char *release, const char *command) {
|
||||
if (strncasecmp(button, "button", strlen("button")) != 0) {
|
||||
ELOG("Bindings for a bar can only be mouse bindings, not \"%s\", ignoring.\n", button);
|
||||
return;
|
||||
|
@ -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);
|
||||
return;
|
||||
}
|
||||
const bool release_bool = release != NULL;
|
||||
|
||||
struct Barbinding *current;
|
||||
TAILQ_FOREACH(current, &(current_bar->bar_bindings), bindings) {
|
||||
if (current->input_code == input_code) {
|
||||
if (current->input_code == input_code && current->release == release_bool) {
|
||||
ELOG("command for button %s was already specified, ignoring.\n", button);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding));
|
||||
new_binding->release = release_bool;
|
||||
new_binding->input_code = input_code;
|
||||
new_binding->command = sstrdup(command);
|
||||
TAILQ_INSERT_TAIL(&(current_bar->bar_bindings), new_binding, bindings);
|
||||
|
@ -491,16 +533,16 @@ static void bar_configure_binding(const char *button, 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);
|
||||
bar_configure_binding("button4", command);
|
||||
bar_configure_binding("button4", NULL, command);
|
||||
}
|
||||
|
||||
CFGFUN(bar_wheel_down_cmd, const char *command) {
|
||||
ELOG("'wheel_down_cmd' is deprecated. Please us 'bindsym button5 %s' instead.\n", command);
|
||||
bar_configure_binding("button5", command);
|
||||
bar_configure_binding("button5", NULL, command);
|
||||
}
|
||||
|
||||
CFGFUN(bar_bindsym, const char *button, const char *command) {
|
||||
bar_configure_binding(button, command);
|
||||
CFGFUN(bar_bindsym, const char *button, const char *release, const char *command) {
|
||||
bar_configure_binding(button, release, command);
|
||||
}
|
||||
|
||||
CFGFUN(bar_position, const char *position) {
|
||||
|
|
|
@ -743,7 +743,7 @@ static char *migrate_config(char *input, off_t size) {
|
|||
|
||||
/* read the script’s output */
|
||||
int conv_size = 65535;
|
||||
char *converted = smalloc(conv_size);
|
||||
char *converted = scalloc(conv_size, 1);
|
||||
int read_bytes = 0, ret;
|
||||
do {
|
||||
if (read_bytes == conv_size) {
|
||||
|
@ -764,6 +764,7 @@ static char *migrate_config(char *input, off_t size) {
|
|||
wait(&status);
|
||||
if (!WIFEXITED(status)) {
|
||||
fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
|
||||
FREE(converted);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -778,6 +779,7 @@ static char *migrate_config(char *input, off_t size) {
|
|||
fprintf(stderr, "# i3 config file (v4)\n");
|
||||
/* TODO: nag the user with a message to include a hint for i3 in their config file */
|
||||
}
|
||||
FREE(converted);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -900,7 +902,9 @@ bool parse_file(const char *f, bool use_nagbar) {
|
|||
|
||||
FREE(current_config);
|
||||
current_config = scalloc(stbuf.st_size + 1, 1);
|
||||
fread(current_config, 1, stbuf.st_size, fstr);
|
||||
if ((ssize_t)fread(current_config, 1, stbuf.st_size, fstr) != stbuf.st_size) {
|
||||
die("Could not fread: %s\n", strerror(errno));
|
||||
}
|
||||
rewind(fstr);
|
||||
|
||||
bool invalid_sets = false;
|
||||
|
@ -1061,7 +1065,7 @@ bool parse_file(const char *f, bool use_nagbar) {
|
|||
int version = detect_version(buf);
|
||||
if (version == 3) {
|
||||
/* We need to convert this v3 configuration */
|
||||
char *converted = migrate_config(new, stbuf.st_size);
|
||||
char *converted = migrate_config(new, strlen(new));
|
||||
if (converted != NULL) {
|
||||
ELOG("\n");
|
||||
ELOG("****************************************************************\n");
|
||||
|
|
|
@ -55,10 +55,6 @@ static yajl_callbacks version_callbacks = {
|
|||
*
|
||||
*/
|
||||
void display_running_version(void) {
|
||||
char *socket_path = root_atom_contents("I3_SOCKET_PATH", conn, conn_screen);
|
||||
if (socket_path == NULL)
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
char *pid_from_atom = root_atom_contents("I3_PID", conn, conn_screen);
|
||||
if (pid_from_atom == NULL) {
|
||||
/* If I3_PID is not set, the running version is older than 4.2-200. */
|
||||
|
@ -71,18 +67,7 @@ void display_running_version(void) {
|
|||
printf("(Getting version from running i3, press ctrl-c to abort…)");
|
||||
fflush(stdout);
|
||||
|
||||
/* TODO: refactor this with the code for sending commands */
|
||||
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
if (sockfd == -1)
|
||||
err(EXIT_FAILURE, "Could not create socket");
|
||||
|
||||
struct sockaddr_un addr;
|
||||
memset(&addr, 0, sizeof(struct sockaddr_un));
|
||||
addr.sun_family = AF_LOCAL;
|
||||
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
|
||||
if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
|
||||
err(EXIT_FAILURE, "Could not connect to i3");
|
||||
|
||||
int sockfd = ipc_connect(NULL);
|
||||
if (ipc_send_message(sockfd, 0, I3_IPC_MESSAGE_TYPE_GET_VERSION,
|
||||
(uint8_t *)"") == -1)
|
||||
err(EXIT_FAILURE, "IPC: write()");
|
||||
|
@ -184,5 +169,4 @@ void display_running_version(void) {
|
|||
yajl_free(handle);
|
||||
free(reply);
|
||||
free(pid_from_atom);
|
||||
free(socket_path);
|
||||
}
|
||||
|
|
|
@ -318,7 +318,7 @@ void floating_enable(Con *con, bool automatic) {
|
|||
render_con(con, false);
|
||||
|
||||
if (set_focus)
|
||||
con_focus(con);
|
||||
con_activate(con);
|
||||
|
||||
/* Check if we need to re-assign it to a different workspace because of its
|
||||
* coordinates and exit if that was done successfully. */
|
||||
|
@ -382,7 +382,7 @@ void floating_disable(Con *con, bool automatic) {
|
|||
con_fix_percent(con->parent);
|
||||
|
||||
if (set_focus)
|
||||
con_focus(con);
|
||||
con_activate(con);
|
||||
|
||||
floating_set_hint_atom(con, false);
|
||||
ipc_send_window_event("floating", con);
|
||||
|
@ -449,7 +449,8 @@ bool floating_maybe_reassign_ws(Con *con) {
|
|||
Con *ws = TAILQ_FIRST(&(content->focus_head));
|
||||
DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name);
|
||||
con_move_to_workspace(con, ws, false, true, false);
|
||||
con_focus(con_descend_focused(con));
|
||||
workspace_show(ws);
|
||||
con_activate(con_descend_focused(con));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -667,7 +668,7 @@ void floating_resize_window(Con *con, const bool proportional,
|
|||
|
||||
/* Custom data structure used to track dragging-related events. */
|
||||
struct drag_x11_cb {
|
||||
ev_check check;
|
||||
ev_prepare prepare;
|
||||
|
||||
/* Whether this modal event loop should be exited and with which result. */
|
||||
drag_result_t result;
|
||||
|
@ -686,7 +687,7 @@ struct drag_x11_cb {
|
|||
const void *extra;
|
||||
};
|
||||
|
||||
static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
|
||||
static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
|
||||
struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
|
||||
xcb_motion_notify_event_t *last_motion_notify = NULL;
|
||||
xcb_generic_event_t *event;
|
||||
|
@ -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)
|
||||
free(event);
|
||||
|
||||
if (dragloop->result != DRAGGING)
|
||||
if (dragloop->result != DRAGGING) {
|
||||
free(last_motion_notify);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (last_motion_notify == NULL)
|
||||
|
@ -765,6 +768,8 @@ static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
|
|||
dragloop->extra);
|
||||
}
|
||||
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,
|
||||
.extra = extra,
|
||||
};
|
||||
ev_check *check = &loop.check;
|
||||
ev_prepare *prepare = &loop.prepare;
|
||||
if (con)
|
||||
loop.old_rect = con->rect;
|
||||
ev_check_init(check, xcb_drag_check_cb);
|
||||
check->data = &loop;
|
||||
ev_prepare_init(prepare, xcb_drag_prepare_cb);
|
||||
prepare->data = &loop;
|
||||
main_set_x11_cb(false);
|
||||
ev_check_start(main_loop, check);
|
||||
ev_prepare_start(main_loop, prepare);
|
||||
|
||||
while (loop.result == DRAGGING)
|
||||
ev_run(main_loop, EVRUN_ONCE);
|
||||
|
||||
ev_check_stop(main_loop, check);
|
||||
ev_prepare_stop(main_loop, prepare);
|
||||
main_set_x11_cb(true);
|
||||
|
||||
xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
|
||||
|
|
|
@ -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))) {
|
||||
DLOG("Focusing con = %p\n", con);
|
||||
workspace_show(ws);
|
||||
con_focus(con);
|
||||
con_activate(con);
|
||||
tree_render();
|
||||
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
|
||||
DLOG("Marking con = %p urgent\n", con);
|
||||
|
@ -774,7 +774,9 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
|||
scratchpad_show(con);
|
||||
} else {
|
||||
workspace_show(ws);
|
||||
con_focus(con);
|
||||
/* Re-set focus, even if unchanged from i3’s perspective. */
|
||||
focused_id = XCB_NONE;
|
||||
con_activate(con);
|
||||
}
|
||||
} else {
|
||||
/* Request is from an application. */
|
||||
|
@ -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))) {
|
||||
DLOG("Focusing con = %p\n", con);
|
||||
workspace_show(ws);
|
||||
con_focus(con);
|
||||
con_activate(con);
|
||||
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
|
||||
DLOG("Marking con = %p urgent\n", con);
|
||||
con_set_urgency(con, true);
|
||||
|
@ -1243,7 +1245,7 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
|
|||
if (ws != con_get_workspace(focused))
|
||||
workspace_show(ws);
|
||||
|
||||
con_focus(con);
|
||||
con_activate(con);
|
||||
/* We update focused_id because we don’t need to set focus again */
|
||||
focused_id = event->event;
|
||||
tree_render();
|
||||
|
@ -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);
|
||||
|
||||
if (force_xinerama) {
|
||||
return;
|
||||
}
|
||||
randr_query_outputs();
|
||||
}
|
||||
|
||||
|
|
53
src/ipc.c
53
src/ipc.c
|
@ -572,6 +572,8 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) {
|
|||
y(integer, current->input_code);
|
||||
ystr("command");
|
||||
ystr(current->command);
|
||||
ystr("release");
|
||||
y(bool, current->release == B_UPON_KEYRELEASE);
|
||||
|
||||
y(map_close);
|
||||
}
|
||||
|
@ -1046,8 +1048,9 @@ static int add_subscription(void *extra, const unsigned char *s,
|
|||
memcpy(client->events[event], s, len);
|
||||
|
||||
DLOG("client is now subscribed to:\n");
|
||||
for (int i = 0; i < client->num_events; i++)
|
||||
for (int i = 0; i < client->num_events; i++) {
|
||||
DLOG("event %s\n", client->events[i]);
|
||||
}
|
||||
DLOG("(done)\n");
|
||||
|
||||
return 1;
|
||||
|
@ -1099,6 +1102,25 @@ IPC_HANDLER(subscribe) {
|
|||
yajl_free(p);
|
||||
const char *reply = "{\"success\":true}";
|
||||
ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
|
||||
|
||||
if (client->first_tick_sent) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_tick = false;
|
||||
for (int i = 0; i < client->num_events; i++) {
|
||||
if (strcmp(client->events[i], "tick") == 0) {
|
||||
is_tick = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!is_tick) {
|
||||
return;
|
||||
}
|
||||
|
||||
client->first_tick_sent = true;
|
||||
const char *payload = "{\"first\":true,\"payload\":\"\"}";
|
||||
ipc_send_message(client->fd, strlen(payload), I3_IPC_EVENT_TICK, (const uint8_t *)payload);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1122,9 +1144,35 @@ IPC_HANDLER(get_config) {
|
|||
y(free);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends the tick event from the message payload to subscribers. Establishes a
|
||||
* synchronization point in event-related tests.
|
||||
*/
|
||||
IPC_HANDLER(send_tick) {
|
||||
yajl_gen gen = ygenalloc();
|
||||
|
||||
y(map_open);
|
||||
|
||||
ystr("payload");
|
||||
yajl_gen_string(gen, (unsigned char *)message, message_size);
|
||||
|
||||
y(map_close);
|
||||
|
||||
const unsigned char *payload;
|
||||
ylength length;
|
||||
y(get_buf, &payload, &length);
|
||||
|
||||
ipc_send_event("tick", I3_IPC_EVENT_TICK, (const char *)payload);
|
||||
y(free);
|
||||
|
||||
const char *reply = "{\"success\":true}";
|
||||
ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_TICK, (const uint8_t *)reply);
|
||||
DLOG("Sent tick event\n");
|
||||
}
|
||||
|
||||
/* The index of each callback function corresponds to the numeric
|
||||
* value of the message type (see include/i3/ipc.h) */
|
||||
handler_t handlers[10] = {
|
||||
handler_t handlers[11] = {
|
||||
handle_run_command,
|
||||
handle_get_workspaces,
|
||||
handle_subscribe,
|
||||
|
@ -1135,6 +1183,7 @@ handler_t handlers[10] = {
|
|||
handle_get_version,
|
||||
handle_get_binding_modes,
|
||||
handle_get_config,
|
||||
handle_send_tick,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -654,6 +654,6 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
|
|||
yajl_free(hand);
|
||||
|
||||
if (to_focus) {
|
||||
con_focus(to_focus);
|
||||
con_activate(to_focus);
|
||||
}
|
||||
}
|
||||
|
|
125
src/main.c
125
src/main.c
|
@ -35,9 +35,9 @@ struct rlimit original_rlimit_core;
|
|||
/** The number of file descriptors passed via socket activation. */
|
||||
int listen_fds;
|
||||
|
||||
/* We keep the xcb_check watcher around to be able to enable and disable it
|
||||
/* We keep the xcb_prepare watcher around to be able to enable and disable it
|
||||
* temporarily for drag_pointer(). */
|
||||
static struct ev_check *xcb_check;
|
||||
static struct ev_prepare *xcb_prepare;
|
||||
|
||||
extern Con *focused;
|
||||
|
||||
|
@ -92,29 +92,26 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment
|
|||
bool xcursor_supported = true;
|
||||
bool xkb_supported = true;
|
||||
|
||||
bool force_xinerama = false;
|
||||
|
||||
/*
|
||||
* This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
|
||||
* This callback is only a dummy, see xcb_prepare_cb.
|
||||
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
|
||||
*
|
||||
*/
|
||||
static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
|
||||
/* empty, because xcb_prepare_cb and xcb_check_cb are used */
|
||||
/* empty, because xcb_prepare_cb are used */
|
||||
}
|
||||
|
||||
/*
|
||||
* Flush before blocking (and waiting for new events)
|
||||
* Called just before the event loop sleeps. Ensures xcb’s incoming and outgoing
|
||||
* queues are empty so that any activity will trigger another event loop
|
||||
* iteration, and hence another xcb_prepare_cb invocation.
|
||||
*
|
||||
*/
|
||||
static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Instead of polling the X connection socket we leave this to
|
||||
* xcb_poll_for_event() which knows better than we can ever know.
|
||||
*
|
||||
*/
|
||||
static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
|
||||
/* Process all queued (and possibly new) events before the event loop
|
||||
sleeps. */
|
||||
xcb_generic_event_t *event;
|
||||
|
||||
while ((event = xcb_poll_for_event(conn)) != NULL) {
|
||||
|
@ -137,6 +134,9 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
|
|||
|
||||
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) {
|
||||
DLOG("Setting main X11 callback to enabled=%d\n", enable);
|
||||
if (enable) {
|
||||
ev_check_start(main_loop, xcb_check);
|
||||
ev_prepare_start(main_loop, xcb_prepare);
|
||||
/* Trigger the watcher explicitly to handle all remaining X11 events.
|
||||
* drag_pointer()’s event handler exits in the middle of the loop. */
|
||||
ev_feed_event(main_loop, xcb_check, 0);
|
||||
ev_feed_event(main_loop, xcb_prepare, 0);
|
||||
} else {
|
||||
ev_check_stop(main_loop, xcb_check);
|
||||
ev_prepare_stop(main_loop, xcb_prepare);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,19 +174,62 @@ static void i3_exit(void) {
|
|||
fflush(stderr);
|
||||
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)
|
||||
*
|
||||
* Unlinks the SHM log and re-raises the signal.
|
||||
* Exits the program gracefully.
|
||||
*
|
||||
*/
|
||||
static void handle_signal(int sig, siginfo_t *info, void *data) {
|
||||
if (*shmlogname != '\0') {
|
||||
shm_unlink(shmlogname);
|
||||
static void handle_term_signal(struct ev_loop *loop, ev_signal *signal, int revents) {
|
||||
/* We exit gracefully here in the sense that cleanup handlers
|
||||
* installed via atexit are invoked. */
|
||||
exit(128 + signal->signum);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up handlers for all signals with default action "Term", see signal(7)
|
||||
*
|
||||
*/
|
||||
static void setup_term_handlers(void) {
|
||||
static struct ev_signal signal_watchers[6];
|
||||
size_t num_watchers = sizeof(signal_watchers) / sizeof(signal_watchers[0]);
|
||||
|
||||
/* We have to rely on libev functionality here and should not use
|
||||
* sigaction handlers because we need to invoke the exit handlers
|
||||
* and cannot do so from an asynchronous signal handling context as
|
||||
* not all code triggered during exit is signal safe (and exiting
|
||||
* the main loop from said handler is not easily possible). libev's
|
||||
* signal handlers does not impose such a constraint on us. */
|
||||
ev_signal_init(&signal_watchers[0], handle_term_signal, SIGHUP);
|
||||
ev_signal_init(&signal_watchers[1], handle_term_signal, SIGINT);
|
||||
ev_signal_init(&signal_watchers[2], handle_term_signal, SIGALRM);
|
||||
ev_signal_init(&signal_watchers[3], handle_term_signal, SIGTERM);
|
||||
ev_signal_init(&signal_watchers[4], handle_term_signal, SIGUSR1);
|
||||
ev_signal_init(&signal_watchers[5], handle_term_signal, SIGUSR1);
|
||||
for (size_t i = 0; i < num_watchers; i++) {
|
||||
ev_signal_start(main_loop, &signal_watchers[i]);
|
||||
/* The signal handlers should not block ev_run from returning
|
||||
* and so none of the signal handlers should hold a reference to
|
||||
* the main loop. */
|
||||
ev_unref(main_loop);
|
||||
}
|
||||
raise(sig);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
@ -197,7 +240,6 @@ int main(int argc, char *argv[]) {
|
|||
bool autostart = true;
|
||||
char *layout_path = NULL;
|
||||
bool delete_layout_path = false;
|
||||
bool force_xinerama = false;
|
||||
bool disable_randr15 = false;
|
||||
char *fake_outputs = NULL;
|
||||
bool disable_signalhandler = false;
|
||||
|
@ -550,6 +592,10 @@ int main(int argc, char *argv[]) {
|
|||
config.ipc_socket_path = sstrdup(config.ipc_socket_path);
|
||||
}
|
||||
|
||||
if (config.force_xinerama) {
|
||||
force_xinerama = true;
|
||||
}
|
||||
|
||||
xcb_void_cookie_t cookie;
|
||||
cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK});
|
||||
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
|
||||
|
@ -668,7 +714,7 @@ int main(int argc, char *argv[]) {
|
|||
fake_outputs_init(fake_outputs);
|
||||
FREE(fake_outputs);
|
||||
config.fake_outputs = NULL;
|
||||
} else if (force_xinerama || config.force_xinerama) {
|
||||
} else if (force_xinerama) {
|
||||
/* Force Xinerama (for drivers which don't support RandR yet, esp. the
|
||||
* nVidia binary graphics driver), when specified either in the config
|
||||
* file or on command-line */
|
||||
|
@ -720,7 +766,7 @@ int main(int argc, char *argv[]) {
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -776,15 +822,11 @@ int main(int argc, char *argv[]) {
|
|||
ewmh_update_desktop_viewport();
|
||||
|
||||
struct ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io));
|
||||
xcb_check = scalloc(1, sizeof(struct ev_check));
|
||||
struct ev_prepare *xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
|
||||
xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
|
||||
|
||||
ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
|
||||
ev_io_start(main_loop, xcb_watcher);
|
||||
|
||||
ev_check_init(xcb_check, xcb_check_cb);
|
||||
ev_check_start(main_loop, xcb_check);
|
||||
|
||||
ev_prepare_init(xcb_prepare, xcb_prepare_cb);
|
||||
ev_prepare_start(main_loop, xcb_prepare);
|
||||
|
||||
|
@ -854,15 +896,15 @@ int main(int argc, char *argv[]) {
|
|||
err(EXIT_FAILURE, "pledge");
|
||||
#endif
|
||||
|
||||
struct sigaction action;
|
||||
|
||||
action.sa_sigaction = handle_signal;
|
||||
action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
|
||||
sigemptyset(&action.sa_mask);
|
||||
|
||||
if (!disable_signalhandler)
|
||||
setup_signal_handler();
|
||||
else {
|
||||
struct sigaction action;
|
||||
|
||||
action.sa_sigaction = handle_core_signal;
|
||||
action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
|
||||
sigemptyset(&action.sa_mask);
|
||||
|
||||
/* Catch all signals with default action "Core", see signal(7) */
|
||||
if (sigaction(SIGQUIT, &action, NULL) == -1 ||
|
||||
sigaction(SIGILL, &action, NULL) == -1 ||
|
||||
|
@ -872,14 +914,7 @@ int main(int argc, char *argv[]) {
|
|||
ELOG("Could not setup signal handler.\n");
|
||||
}
|
||||
|
||||
/* Catch all signals with default action "Term", see signal(7) */
|
||||
if (sigaction(SIGHUP, &action, NULL) == -1 ||
|
||||
sigaction(SIGINT, &action, NULL) == -1 ||
|
||||
sigaction(SIGALRM, &action, NULL) == -1 ||
|
||||
sigaction(SIGUSR1, &action, NULL) == -1 ||
|
||||
sigaction(SIGUSR2, &action, NULL) == -1)
|
||||
ELOG("Could not setup signal handler.\n");
|
||||
|
||||
setup_term_handlers();
|
||||
/* Ignore SIGPIPE to survive errors when an IPC client disconnects
|
||||
* while we are sending them a message */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
@ -922,7 +957,7 @@ int main(int argc, char *argv[]) {
|
|||
free(command);
|
||||
}
|
||||
|
||||
/* Make sure to destroy the event loop to invoke the cleeanup callbacks
|
||||
/* Make sure to destroy the event loop to invoke the cleanup callbacks
|
||||
* when calling exit() */
|
||||
atexit(i3_exit);
|
||||
|
||||
|
|
29
src/manage.c
29
src/manage.c
|
@ -259,9 +259,26 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
|||
Con *wm_desktop_ws = NULL;
|
||||
|
||||
/* If not, check if it is assigned to a specific workspace */
|
||||
if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE))) {
|
||||
if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE)) ||
|
||||
(assignment = assignment_for(cwindow, A_TO_WORKSPACE_NUMBER))) {
|
||||
DLOG("Assignment matches (%p)\n", match);
|
||||
Con *assigned_ws = workspace_get(assignment->dest.workspace, NULL);
|
||||
|
||||
Con *assigned_ws = NULL;
|
||||
if (assignment->type == A_TO_WORKSPACE_NUMBER) {
|
||||
Con *output = NULL;
|
||||
long parsed_num = ws_name_to_number(assignment->dest.workspace);
|
||||
|
||||
/* This will only work for workspaces that already exist. */
|
||||
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
|
||||
GREP_FIRST(assigned_ws, output_get_content(output), child->num == parsed_num);
|
||||
}
|
||||
}
|
||||
/* A_TO_WORKSPACE type assignment or fallback from A_TO_WORKSPACE_NUMBER
|
||||
* when the target workspace number does not exist yet. */
|
||||
if (!assigned_ws) {
|
||||
assigned_ws = workspace_get(assignment->dest.workspace, NULL);
|
||||
}
|
||||
|
||||
nc = con_descend_tiling_focused(assigned_ws);
|
||||
DLOG("focused on ws %s: %p / %s\n", assigned_ws->name, nc, nc->name);
|
||||
if (nc->type == CT_WORKSPACE)
|
||||
|
@ -305,6 +322,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
|||
} else
|
||||
nc = tree_open_con(NULL, cwindow);
|
||||
}
|
||||
|
||||
if ((assignment = assignment_for(cwindow, A_TO_OUTPUT))) {
|
||||
con_move_to_output_name(nc, assignment->dest.output, true);
|
||||
}
|
||||
} else {
|
||||
/* M_BELOW inserts the new window as a child of the one which was
|
||||
* matched (e.g. dock areas) */
|
||||
|
@ -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
|
||||
* presentations to work out of the box. */
|
||||
if (output != NULL)
|
||||
con_move_to_output(nc, output);
|
||||
con_move_to_output(nc, output, false);
|
||||
con_toggle_fullscreen(nc, CF_OUTPUT);
|
||||
}
|
||||
fs = NULL;
|
||||
|
@ -625,7 +646,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
|||
* proper window event sequence. */
|
||||
if (set_focus && nc->mapped) {
|
||||
DLOG("Now setting focus.\n");
|
||||
con_focus(nc);
|
||||
con_activate(nc);
|
||||
}
|
||||
|
||||
tree_render();
|
||||
|
|
|
@ -118,7 +118,7 @@ static void move_to_output_directed(Con *con, direction_t direction) {
|
|||
attach_to_workspace(con, ws, direction);
|
||||
|
||||
/* fix the focus stack */
|
||||
con_focus(con);
|
||||
con_activate(con);
|
||||
|
||||
/* force re-painting the indicators */
|
||||
FREE(con->deco_render_params);
|
||||
|
|
|
@ -99,7 +99,8 @@ void output_push_sticky_windows(Con *to_focus) {
|
|||
continue;
|
||||
|
||||
if (con_is_sticky(current)) {
|
||||
con_move_to_workspace(current, visible_ws, true, false, current != to_focus->parent);
|
||||
bool ignore_focus = (to_focus == NULL) || (current != to_focus->parent);
|
||||
con_move_to_workspace(current, visible_ws, true, false, ignore_focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -496,7 +496,7 @@ void init_ws_for_output(Output *output, Con *content) {
|
|||
Con *ws = create_workspace_on_output(output, content);
|
||||
|
||||
/* TODO: Set focus in main.c */
|
||||
con_focus(ws);
|
||||
con_activate(ws);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -924,7 +924,7 @@ void randr_query_outputs(void) {
|
|||
continue;
|
||||
|
||||
DLOG("Focusing primary output %s\n", output_primary_name(output));
|
||||
con_focus(con_descend_focused(output->con));
|
||||
con_activate(con_descend_focused(output->con));
|
||||
}
|
||||
|
||||
/* render_layout flushes */
|
||||
|
@ -987,7 +987,7 @@ void randr_disable_output(Output *output) {
|
|||
|
||||
if (next) {
|
||||
DLOG("now focusing next = %p\n", next);
|
||||
con_focus(next);
|
||||
con_activate(next);
|
||||
workspace_show(con_get_workspace(next));
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ DRAGGING_CB(resize_callback) {
|
|||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction) {
|
||||
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides) {
|
||||
DLOG("Find two participants for resizing container=%p in direction=%i\n", other, direction);
|
||||
Con *first = *current;
|
||||
Con *second = NULL;
|
||||
|
@ -74,8 +74,14 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
|
|||
/* get the counterpart for this resizement */
|
||||
if (dir_backwards) {
|
||||
second = TAILQ_PREV(first, nodes_head, nodes);
|
||||
if (second == NULL && both_sides == true) {
|
||||
second = TAILQ_NEXT(first, nodes);
|
||||
}
|
||||
} else {
|
||||
second = TAILQ_NEXT(first, nodes);
|
||||
if (second == NULL && both_sides == true) {
|
||||
second = TAILQ_PREV(first, nodes_head, nodes);
|
||||
}
|
||||
}
|
||||
|
||||
if (second == NULL) {
|
||||
|
|
|
@ -39,7 +39,6 @@ static TAILQ_HEAD(state_head, placeholder_state) state_head =
|
|||
static xcb_connection_t *restore_conn;
|
||||
|
||||
static struct ev_io *xcb_watcher;
|
||||
static struct ev_check *xcb_check;
|
||||
static struct ev_prepare *xcb_prepare;
|
||||
|
||||
static void restore_handle_event(int type, xcb_generic_event_t *event);
|
||||
|
@ -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) {
|
||||
xcb_flush(restore_conn);
|
||||
}
|
||||
|
||||
static void restore_xcb_check_cb(EV_P_ ev_check *w, int revents) {
|
||||
xcb_generic_event_t *event;
|
||||
|
||||
if (xcb_connection_has_error(restore_conn)) {
|
||||
|
@ -77,6 +72,8 @@ static void restore_xcb_check_cb(EV_P_ ev_check *w, int revents) {
|
|||
|
||||
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
|
||||
* because our X11 connection was killed (e.g. by a user with xkill. */
|
||||
ev_io_stop(main_loop, xcb_watcher);
|
||||
ev_check_stop(main_loop, xcb_check);
|
||||
ev_prepare_stop(main_loop, xcb_prepare);
|
||||
|
||||
placeholder_state *state;
|
||||
|
@ -107,7 +103,6 @@ void restore_connect(void) {
|
|||
*/
|
||||
xcb_disconnect(restore_conn);
|
||||
free(xcb_watcher);
|
||||
free(xcb_check);
|
||||
free(xcb_prepare);
|
||||
}
|
||||
|
||||
|
@ -124,15 +119,11 @@ void restore_connect(void) {
|
|||
}
|
||||
|
||||
xcb_watcher = scalloc(1, sizeof(struct ev_io));
|
||||
xcb_check = scalloc(1, sizeof(struct ev_check));
|
||||
xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
|
||||
|
||||
ev_io_init(xcb_watcher, restore_xcb_got_event, xcb_get_file_descriptor(restore_conn), EV_READ);
|
||||
ev_io_start(main_loop, xcb_watcher);
|
||||
|
||||
ev_check_init(xcb_check, restore_xcb_check_cb);
|
||||
ev_check_start(main_loop, xcb_check);
|
||||
|
||||
ev_prepare_init(xcb_prepare, restore_xcb_prepare_cb);
|
||||
ev_prepare_start(main_loop, xcb_prepare);
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ void scratchpad_show(Con *con) {
|
|||
/* use con_descend_tiling_focused to get the last focused
|
||||
* window inside this scratch container in order to
|
||||
* keep the focus the same within this container */
|
||||
con_focus(con_descend_tiling_focused(walk_con));
|
||||
con_activate(con_descend_tiling_focused(walk_con));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ void scratchpad_show(Con *con) {
|
|||
workspace_show(active);
|
||||
}
|
||||
|
||||
con_focus(con_descend_focused(con));
|
||||
con_activate(con_descend_focused(con));
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
50
src/tree.c
50
src/tree.c
|
@ -330,6 +330,13 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
|
|||
DLOG("parent container killed\n");
|
||||
}
|
||||
|
||||
if (ws == con) {
|
||||
DLOG("Closing a workspace container, updating EWMH atoms\n");
|
||||
ewmh_update_number_of_desktops();
|
||||
ewmh_update_desktop_names();
|
||||
ewmh_update_wm_desktop();
|
||||
}
|
||||
|
||||
con_free(con);
|
||||
|
||||
/* in the case of floating windows, we already focused another container
|
||||
|
@ -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);
|
||||
if (next->type == CT_DOCKAREA) {
|
||||
/* Instead of focusing the dockarea, we need to restore focus to the workspace */
|
||||
con_focus(con_descend_focused(output_get_content(next->parent)));
|
||||
con_activate(con_descend_focused(output_get_content(next->parent)));
|
||||
} else {
|
||||
if (!force_set_focus && con != focused)
|
||||
DLOG("not changing focus, the container was not focused before\n");
|
||||
else
|
||||
con_focus(next);
|
||||
con_activate(next);
|
||||
}
|
||||
} else {
|
||||
DLOG("not focusing because we're not killing anybody\n");
|
||||
|
@ -433,7 +440,7 @@ bool level_up(void) {
|
|||
/* Skip over floating containers and go directly to the grandparent
|
||||
* (which should always be a workspace) */
|
||||
if (focused->parent->type == CT_FLOATING_CON) {
|
||||
con_focus(focused->parent->parent);
|
||||
con_activate(focused->parent->parent);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -444,7 +451,7 @@ bool level_up(void) {
|
|||
ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n");
|
||||
return false;
|
||||
}
|
||||
con_focus(focused->parent);
|
||||
con_activate(focused->parent);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -469,7 +476,7 @@ bool level_down(void) {
|
|||
next = TAILQ_FIRST(&(next->focus_head));
|
||||
}
|
||||
|
||||
con_focus(next);
|
||||
con_activate(next);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -560,26 +567,14 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
|
|||
if (!workspace)
|
||||
return false;
|
||||
|
||||
workspace_show(workspace);
|
||||
|
||||
/* If a workspace has an active fullscreen container, one of its
|
||||
* children should always be focused. The above workspace_show()
|
||||
* should be adequate for that, so return. */
|
||||
if (con_get_fullscreen_con(workspace, CF_OUTPUT))
|
||||
return true;
|
||||
|
||||
Con *focus = con_descend_direction(workspace, direction);
|
||||
|
||||
/* special case: if there was no tiling con to focus and the workspace
|
||||
* has a floating con in the focus stack, focus the top of the focus
|
||||
* stack (which may be floating) */
|
||||
if (focus == workspace)
|
||||
Con *focus = con_descend_tiling_focused(workspace);
|
||||
if (focus == workspace) {
|
||||
focus = con_descend_focused(workspace);
|
||||
|
||||
if (focus) {
|
||||
con_focus(focus);
|
||||
x_set_warp_to(&(focus->rect));
|
||||
}
|
||||
|
||||
workspace_show(workspace);
|
||||
con_activate(focus);
|
||||
x_set_warp_to(&(focus->rect));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
con_focus(con_descend_focused(next));
|
||||
con_activate(con_descend_focused(next));
|
||||
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);
|
||||
|
||||
if (!next) {
|
||||
if (!config.force_focus_wrapping) {
|
||||
if (config.focus_wrapping != FOCUS_WRAPPING_FORCE) {
|
||||
/* If there is no next/previous container, we check if we can focus one
|
||||
* when going higher (without wrapping, though). If so, we are done, if
|
||||
* not, we wrap */
|
||||
|
@ -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
|
||||
* until we find a window */
|
||||
/* TODO: check for window, atm we only go down as far as possible */
|
||||
con_focus(con_descend_focused(next));
|
||||
con_activate(con_descend_focused(next));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
_tree_next(focused, way, orientation, true);
|
||||
_tree_next(focused, way, orientation,
|
||||
config.focus_wrapping != FOCUS_WRAPPING_OFF);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -500,7 +500,7 @@ ssize_t slurp(const char *path, char **buf) {
|
|||
size_t n = fread(*buf, 1, stbuf.st_size, f);
|
||||
fclose(f);
|
||||
if ((ssize_t)n != stbuf.st_size) {
|
||||
ELOG("File \"%s\" could not be read entirely: got %zd, want %zd\n", path, n, stbuf.st_size);
|
||||
ELOG("File \"%s\" could not be read entirely: got %zd, want %" PRIi64 "\n", path, n, (int64_t)stbuf.st_size);
|
||||
free(*buf);
|
||||
*buf = NULL;
|
||||
return -1;
|
||||
|
|
|
@ -459,6 +459,11 @@ static void _workspace_show(Con *workspace) {
|
|||
|
||||
y(free);
|
||||
|
||||
/* Avoid calling output_push_sticky_windows later with a freed container. */
|
||||
if (old == old_focus) {
|
||||
old_focus = NULL;
|
||||
}
|
||||
|
||||
ewmh_update_number_of_desktops();
|
||||
ewmh_update_desktop_names();
|
||||
ewmh_update_desktop_viewport();
|
||||
|
@ -810,9 +815,9 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
|
|||
/* 2: copy layout from workspace */
|
||||
split->layout = ws->layout;
|
||||
|
||||
Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
|
||||
|
||||
/* 3: move the existing cons of this workspace below the new con */
|
||||
Con **focus_order = get_focus_order(ws);
|
||||
|
||||
DLOG("Moving cons\n");
|
||||
while (!TAILQ_EMPTY(&(ws->nodes_head))) {
|
||||
Con *child = TAILQ_FIRST(&(ws->nodes_head));
|
||||
|
@ -820,6 +825,9 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
|
|||
con_attach(child, split, true);
|
||||
}
|
||||
|
||||
set_focus_order(split, focus_order);
|
||||
free(focus_order);
|
||||
|
||||
/* 4: switch workspace layout */
|
||||
ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
|
||||
DLOG("split->layout = %d, ws->layout = %d\n", split->layout, ws->layout);
|
||||
|
@ -830,9 +838,6 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
|
|||
|
||||
/* 6: fix the percentages */
|
||||
con_fix_percent(ws);
|
||||
|
||||
if (old_focused)
|
||||
con_focus(old_focused);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -887,9 +892,10 @@ Con *workspace_encapsulate(Con *ws) {
|
|||
new->parent = ws;
|
||||
new->layout = ws->layout;
|
||||
|
||||
Con **focus_order = get_focus_order(ws);
|
||||
|
||||
DLOG("Moving children of workspace %p / %s into container %p\n",
|
||||
ws, ws->name, new);
|
||||
|
||||
Con *child;
|
||||
while (!TAILQ_EMPTY(&(ws->nodes_head))) {
|
||||
child = TAILQ_FIRST(&(ws->nodes_head));
|
||||
|
@ -897,6 +903,9 @@ Con *workspace_encapsulate(Con *ws) {
|
|||
con_attach(child, new, true);
|
||||
}
|
||||
|
||||
set_focus_order(new, focus_order);
|
||||
free(focus_order);
|
||||
|
||||
con_attach(new, ws, true);
|
||||
|
||||
return new;
|
||||
|
|
10
src/x.c
10
src/x.c
|
@ -1227,9 +1227,13 @@ void x_set_name(Con *con, const char *name) {
|
|||
*
|
||||
*/
|
||||
void update_shmlog_atom() {
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
|
||||
A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
|
||||
strlen(shmlogname), shmlogname);
|
||||
if (*shmlogname == '\0') {
|
||||
xcb_delete_property(conn, root, A_I3_SHMLOG_PATH);
|
||||
} else {
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
|
||||
A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
|
||||
strlen(shmlogname), shmlogname);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -38,6 +38,8 @@ binmode STDERR, ':utf8';
|
|||
# subshell or situations like that.
|
||||
AnyEvent::Util::close_all_fds_except(0, 1, 2);
|
||||
|
||||
our @CLEANUP;
|
||||
|
||||
# convenience wrapper to write to the log file
|
||||
my $log;
|
||||
sub Log { say $log "@_" }
|
||||
|
@ -55,6 +57,7 @@ my %options = (
|
|||
xtrace => 0,
|
||||
coverage => 0,
|
||||
restart => 0,
|
||||
xvfb => 1,
|
||||
);
|
||||
my $keep_xserver_output = 0;
|
||||
|
||||
|
@ -64,6 +67,7 @@ my $result = GetOptions(
|
|||
"valgrind" => \$options{valgrind},
|
||||
"strace" => \$options{strace},
|
||||
"xtrace" => \$options{xtrace},
|
||||
"xvfb!" => \$options{xvfb},
|
||||
"display=s" => \@displays,
|
||||
"parallel=i" => \$parallel,
|
||||
"help|?" => \$help,
|
||||
|
@ -112,6 +116,44 @@ $ENV{PATH} = join(':',
|
|||
qx(Xephyr -help 2>&1);
|
||||
die "Xephyr was not found in your path. Please install Xephyr (xserver-xephyr on Debian)." if $?;
|
||||
|
||||
qx(xvfb-run --help 2>&1);
|
||||
if ($? && $options{xvfb}) {
|
||||
say "xvfb-run not found, not running tests under xvfb. Install the xvfb package to speed up tests";
|
||||
$options{xvfb} = 0;
|
||||
}
|
||||
|
||||
if ($options{xvfb}) {
|
||||
for (my $n = 99; $n < 120; $n++) {
|
||||
my $path = File::Temp::tmpnam($ENV{TMPDIR} // "/tmp", "i3-testsXXXXXX");
|
||||
if (!defined(POSIX::mkfifo($path, 0600))) {
|
||||
die "mkfifo: $!";
|
||||
}
|
||||
my $pid = fork // die "fork: $!";
|
||||
if ($pid == 0) {
|
||||
# Child
|
||||
|
||||
# Xvfb checks whether the parent ignores USR1 and sends USR1 to the
|
||||
# parent when ready, so that the wait call will be interrupted. We
|
||||
# can’t implement this in Perl, as Perl’s waitpid transparently
|
||||
# handles -EINTR.
|
||||
exec('/bin/sh', '-c', qq|trap "exit" INT; trap : USR1; (trap '' USR1; exec Xvfb :$n -screen 0 640x480x8 -nolisten tcp) & PID=\$!; wait; if ! kill -0 \$PID 2>/dev/null; then echo 1:\$PID > $path; else echo 0:\$PID > $path; wait \$PID; fi|);
|
||||
die "exec: $!";
|
||||
}
|
||||
chomp(my $kill = slurp($path));
|
||||
unlink($path);
|
||||
my ($code, $xvfbpid) = ($kill =~ m,^([0-1]):(.*)$,);
|
||||
next unless $code eq '0';
|
||||
|
||||
$ENV{DISPLAY} = ":$n";
|
||||
say "Running tests under Xvfb display $ENV{DISPLAY}";
|
||||
|
||||
push(@CLEANUP, sub {
|
||||
kill(15, $xvfbpid);
|
||||
});
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
@displays = split(/,/, join(',', @displays));
|
||||
@displays = map { s/ //g; $_ } @displays;
|
||||
|
||||
|
@ -379,7 +421,7 @@ sub take_job {
|
|||
|
||||
sub cleanup {
|
||||
my $exitcode = $?;
|
||||
$_->() for our @CLEANUP;
|
||||
$_->() for @CLEANUP;
|
||||
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
|
||||
available in C<latest/xtrace-for-$test.log>.
|
||||
|
||||
=item B<--xvfb>
|
||||
|
||||
=item B<--no-xvfb>
|
||||
|
||||
Enable or disable running tests under Xvfb. Enabled by default.
|
||||
|
||||
=item B<--coverage-testing>
|
||||
|
||||
Generates a test coverage report at C<latest/i3-coverage>. Exits i3 cleanly
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <sys/resource.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <libgen.h>
|
||||
|
||||
static void uds_connection_cb(EV_P_ ev_io *w, int revents);
|
||||
|
|
|
@ -87,7 +87,7 @@ sub start_xserver {
|
|||
|
||||
# First get the last used display number, then increment it by one.
|
||||
# Effectively falls back to 1 if no X server is running.
|
||||
my ($displaynum) = map { /(\d+)$/ } reverse sort glob($x_socketpath . '*');
|
||||
my ($displaynum) = reverse sort { $a <=> $b } map{ /(\d+)$/ } glob($x_socketpath . '*');
|
||||
$displaynum++;
|
||||
|
||||
say "Starting $parallel Xephyr instances, starting at :$displaynum...";
|
||||
|
@ -105,7 +105,7 @@ sub start_xserver {
|
|||
for (1 .. $parallel) {
|
||||
my $socket = fork_xserver($keep_xserver_output, $displaynum,
|
||||
'Xephyr', ":$displaynum", '-screen', '1280x800',
|
||||
'-nolisten', 'tcp');
|
||||
'-nolisten', 'tcp', '-name', "i3test");
|
||||
push(@displays, ":$displaynum");
|
||||
push(@sockets_waiting, $socket);
|
||||
$displaynum++;
|
||||
|
|
|
@ -12,6 +12,7 @@ use AnyEvent::I3;
|
|||
use List::Util qw(first);
|
||||
use Time::HiRes qw(sleep);
|
||||
use Cwd qw(abs_path);
|
||||
use POSIX ':sys_wait_h';
|
||||
use Scalar::Util qw(blessed);
|
||||
use SocketActivation;
|
||||
use i3test::Util qw(slurp);
|
||||
|
@ -37,6 +38,7 @@ our @EXPORT = qw(
|
|||
cmd
|
||||
sync_with_i3
|
||||
exit_gracefully
|
||||
exit_forcefully
|
||||
workspace_exists
|
||||
focused_ws
|
||||
get_socket_path
|
||||
|
@ -47,6 +49,8 @@ our @EXPORT = qw(
|
|||
wait_for_unmap
|
||||
$x
|
||||
kill_all_windows
|
||||
events_for
|
||||
listen_for_binding
|
||||
);
|
||||
|
||||
=head1 NAME
|
||||
|
@ -121,7 +125,7 @@ END {
|
|||
|
||||
} else {
|
||||
kill(-9, $i3_pid)
|
||||
or $tester->BAIL_OUT("could not kill i3");
|
||||
or $tester->BAIL_OUT("could not kill i3: $!");
|
||||
|
||||
waitpid $i3_pid, 0;
|
||||
}
|
||||
|
@ -131,6 +135,22 @@ sub import {
|
|||
my ($class, %args) = @_;
|
||||
my $pkg = caller;
|
||||
|
||||
$x ||= i3test::X11->new;
|
||||
# set the pointer to a predictable position in case a previous test has
|
||||
# disturbed it
|
||||
$x->warp_pointer(
|
||||
0, # src_window (None)
|
||||
$x->get_root_window(), # dst_window (None)
|
||||
0, # src_x
|
||||
0, # src_y
|
||||
0, # src_width
|
||||
0, # src_height
|
||||
0, # dst_x
|
||||
0); # dst_y
|
||||
# Synchronize with X11 to ensure the pointer has been warped before i3
|
||||
# starts up.
|
||||
$x->get_input_focus_reply($x->get_input_focus()->{sequence});
|
||||
|
||||
$i3_autostart = delete($args{i3_autostart}) // 1;
|
||||
my $i3_config = delete($args{i3_config}) // '-default';
|
||||
|
||||
|
@ -153,10 +173,6 @@ __
|
|||
strict->import;
|
||||
warnings->import;
|
||||
|
||||
$x ||= i3test::X11->new;
|
||||
# set the pointer to a predictable position in case a previous test has
|
||||
# disturbed it
|
||||
$x->root->warp_pointer(0, 0);
|
||||
$cv->recv if $i3_autostart;
|
||||
|
||||
@_ = ($class);
|
||||
|
@ -179,29 +195,11 @@ received, etc.
|
|||
sub wait_for_event {
|
||||
my ($timeout, $cb) = @_;
|
||||
|
||||
my $cv = AE::cv;
|
||||
|
||||
$x->flush;
|
||||
|
||||
# unfortunately, there is no constant for this
|
||||
my $ae_read = 0;
|
||||
|
||||
my $guard = AE::io $x->get_file_descriptor, $ae_read, sub {
|
||||
while (defined(my $event = $x->poll_for_event)) {
|
||||
if ($cb->($event)) {
|
||||
$cv->send(1);
|
||||
last;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
# Trigger timeout after $timeout seconds (can be fractional)
|
||||
my $t = AE::timer $timeout, 0, sub { warn "timeout ($timeout secs)"; $cv->send(0) };
|
||||
|
||||
my $result = $cv->recv;
|
||||
undef $t;
|
||||
undef $guard;
|
||||
return $result;
|
||||
while (defined(my $event = $x->wait_for_event)) {
|
||||
return 1 if $cb->($event);
|
||||
}
|
||||
}
|
||||
|
||||
=head2 wait_for_map($window)
|
||||
|
@ -348,6 +346,12 @@ sub open_window {
|
|||
|
||||
$window->map;
|
||||
wait_for_map($window);
|
||||
|
||||
# MapWindow is sent before i3 even starts rendering: the window is placed at
|
||||
# temporary off-screen coordinates first, and x_push_changes() sends further
|
||||
# X11 requests to set focus etc. Hence, we sync with i3 before continuing.
|
||||
sync_with_i3();
|
||||
|
||||
return $window;
|
||||
}
|
||||
|
||||
|
@ -686,6 +690,7 @@ sub sync_with_i3 {
|
|||
$_sync_window = open_window(
|
||||
rect => [ -15, -15, 10, 10 ],
|
||||
override_redirect => 1,
|
||||
dont_map => 1,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -756,7 +761,7 @@ sub exit_gracefully {
|
|||
|
||||
if (!$exited) {
|
||||
kill(9, $pid)
|
||||
or $tester->BAIL_OUT("could not kill i3");
|
||||
or $tester->BAIL_OUT("could not kill i3: $!");
|
||||
}
|
||||
|
||||
if ($socketpath =~ m,^/tmp/i3-test-socket-,) {
|
||||
|
@ -767,6 +772,47 @@ sub exit_gracefully {
|
|||
undef $i3_pid;
|
||||
}
|
||||
|
||||
=head2 exit_forcefully($pid, [ $signal ])
|
||||
|
||||
Tries to exit i3 forcefully by sending a signal (defaults to SIGTERM).
|
||||
|
||||
You only need to use this function if you want to test signal handling
|
||||
(in which case you must have launched i3 on your own with
|
||||
C<launch_with_config>).
|
||||
|
||||
use i3test i3_autostart => 0;
|
||||
my $pid = launch_with_config($config);
|
||||
# …
|
||||
exit_forcefully($pid);
|
||||
|
||||
=cut
|
||||
sub exit_forcefully {
|
||||
my ($pid, $signal) = @_;
|
||||
$signal ||= 'TERM';
|
||||
|
||||
# Send the given signal to the i3 instance and wait for up to 10s
|
||||
# for it to terminate.
|
||||
kill($signal, $pid)
|
||||
or $tester->BAIL_OUT("could not kill i3: $!");
|
||||
my $status;
|
||||
my $timeout = 10;
|
||||
do {
|
||||
$status = waitpid $pid, WNOHANG;
|
||||
|
||||
if ($status <= 0) {
|
||||
sleep(1);
|
||||
$timeout--;
|
||||
}
|
||||
} while ($status <= 0 && $timeout > 0);
|
||||
|
||||
if ($status <= 0) {
|
||||
kill('KILL', $pid)
|
||||
or $tester->BAIL_OUT("could not kill i3: $!");
|
||||
waitpid $pid, 0;
|
||||
}
|
||||
undef $i3_pid;
|
||||
}
|
||||
|
||||
=head2 get_socket_path([ $cache ])
|
||||
|
||||
Gets the socket path from the C<I3_SOCKET_PATH> atom stored on the X11 root
|
||||
|
@ -900,6 +946,86 @@ sub kill_all_windows {
|
|||
cmd '[title=".*"] kill';
|
||||
}
|
||||
|
||||
=head2 events_for($subscribecb, [ $rettype ], [ $eventcbs ])
|
||||
|
||||
Helper function which returns an array containing all events of type $rettype
|
||||
which were generated by i3 while $subscribecb was running.
|
||||
|
||||
Set $eventcbs to subscribe to multiple event types and/or perform your own event
|
||||
aggregation.
|
||||
|
||||
=cut
|
||||
sub events_for {
|
||||
my ($subscribecb, $rettype, $eventcbs) = @_;
|
||||
|
||||
my @events;
|
||||
$eventcbs //= {};
|
||||
if (defined($rettype)) {
|
||||
$eventcbs->{$rettype} = sub { push @events, shift };
|
||||
}
|
||||
my $subscribed = AnyEvent->condvar;
|
||||
my $flushed = AnyEvent->condvar;
|
||||
$eventcbs->{tick} = sub {
|
||||
my ($event) = @_;
|
||||
if ($event->{first}) {
|
||||
$subscribed->send($event);
|
||||
} else {
|
||||
$flushed->send($event);
|
||||
}
|
||||
};
|
||||
my $i3 = i3(get_socket_path(0));
|
||||
$i3->connect->recv;
|
||||
$i3->subscribe($eventcbs)->recv;
|
||||
$subscribed->recv;
|
||||
# Subscription established, run the callback.
|
||||
$subscribecb->();
|
||||
# Now generate a tick event, which we know we’ll receive (and at which point
|
||||
# all other events have been received).
|
||||
my $nonce = int(rand(255)) + 1;
|
||||
$i3->send_tick($nonce);
|
||||
|
||||
my $tick = $flushed->recv;
|
||||
$tester->is_eq($tick->{payload}, $nonce, 'tick nonce received');
|
||||
return @events;
|
||||
}
|
||||
|
||||
=head2 listen_for_binding($cb)
|
||||
|
||||
Helper function to evaluate whether sending KeyPress/KeyRelease events via XTEST
|
||||
triggers an i3 key binding or not. Expects key bindings to be configured in the
|
||||
form “bindsym <binding> nop <binding>”, e.g. “bindsym Mod4+Return nop
|
||||
Mod4+Return”.
|
||||
|
||||
is(listen_for_binding(
|
||||
sub {
|
||||
xtest_key_press(133); # Super_L
|
||||
xtest_key_press(36); # Return
|
||||
xtest_key_release(36); # Return
|
||||
xtest_key_release(133); # Super_L
|
||||
xtest_sync_with_i3;
|
||||
},
|
||||
),
|
||||
'Mod4+Return',
|
||||
'triggered the "Mod4+Return" keybinding');
|
||||
|
||||
=cut
|
||||
|
||||
sub listen_for_binding {
|
||||
my ($cb) = @_;
|
||||
my $triggered = AnyEvent->condvar;
|
||||
my @events = events_for(
|
||||
$cb,
|
||||
'binding');
|
||||
|
||||
$tester->is_eq(scalar @events, 1, 'Received precisely one event');
|
||||
$tester->is_eq($events[0]->{change}, 'run', 'change is "run"');
|
||||
# We look at the command (which is “nop <binding>”) because that is easier
|
||||
# than re-assembling the string representation of $event->{binding}.
|
||||
my $command = $events[0]->{binding}->{command};
|
||||
$command =~ s/^nop //g;
|
||||
return $command;
|
||||
}
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Michael Stapelberg <michael@i3wm.org>
|
||||
|
|
|
@ -5,6 +5,7 @@ use base 'Test::Builder::Module';
|
|||
|
||||
our @EXPORT = qw(
|
||||
is_num_children
|
||||
is_num_fullscreen
|
||||
cmp_float
|
||||
does_i3_live
|
||||
);
|
||||
|
@ -59,6 +60,25 @@ sub is_num_children {
|
|||
$tb->is_num($got_num_children, $num_children, $name);
|
||||
}
|
||||
|
||||
=head2 is_num_fullscreen($workspace, $expected, $test_name)
|
||||
|
||||
Gets the number of fullscreen containers on the given workspace and verifies that
|
||||
they match the expected amount.
|
||||
|
||||
is_num_fullscreen('1', 0, 'no fullscreen containers on workspace 1');
|
||||
|
||||
=cut
|
||||
sub is_num_fullscreen {
|
||||
my ($workspace, $num_fullscreen, $name) = @_;
|
||||
my $workspace_content = i3test::get_ws($workspace);
|
||||
my $tb = $CLASS->builder;
|
||||
|
||||
my $nodes = scalar grep { $_->{fullscreen_mode} != 0 } @{$workspace_content->{nodes}->[0]->{nodes}};
|
||||
my $cons = scalar grep { $_->{fullscreen_mode} != 0 } @{$workspace_content->{nodes}};
|
||||
my $floating = scalar grep { $_->{fullscreen_mode} != 0 } @{$workspace_content->{floating_nodes}->[0]->{nodes}};
|
||||
$tb->is_num($nodes + $cons + $floating, $num_fullscreen, $name);
|
||||
}
|
||||
|
||||
=head2 cmp_float($a, $b)
|
||||
|
||||
Compares floating point numbers C<$a> and C<$b> and returns true if they differ
|
||||
|
|
|
@ -14,13 +14,13 @@ use ExtUtils::PkgConfig;
|
|||
use Exporter ();
|
||||
our @EXPORT = qw(
|
||||
inlinec_connect
|
||||
xtest_sync_with
|
||||
xtest_sync_with_i3
|
||||
set_xkb_group
|
||||
xtest_key_press
|
||||
xtest_key_release
|
||||
xtest_button_press
|
||||
xtest_button_release
|
||||
listen_for_binding
|
||||
start_binding_capture
|
||||
binding_events
|
||||
);
|
||||
|
||||
|
@ -38,7 +38,7 @@ i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb
|
|||
# ineffective.
|
||||
my %sn_config;
|
||||
BEGIN {
|
||||
%sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest');
|
||||
%sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest xcb-util');
|
||||
}
|
||||
|
||||
use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
|
||||
|
@ -53,8 +53,12 @@ use Inline C => <<'END_OF_C_CODE';
|
|||
#include <xcb/xcb.h>
|
||||
#include <xcb/xkb.h>
|
||||
#include <xcb/xtest.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
|
||||
static xcb_connection_t *conn = NULL;
|
||||
static xcb_window_t sync_window;
|
||||
static xcb_window_t root_window;
|
||||
static xcb_atom_t i3_sync_atom;
|
||||
|
||||
bool inlinec_connect() {
|
||||
int screen;
|
||||
|
@ -89,9 +93,94 @@ bool inlinec_connect() {
|
|||
}
|
||||
free(usereply);
|
||||
|
||||
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, xcb_intern_atom(conn, 0, strlen("I3_SYNC"), "I3_SYNC"), NULL);
|
||||
i3_sync_atom = reply->atom;
|
||||
free(reply);
|
||||
|
||||
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
|
||||
root_window = root_screen->root;
|
||||
sync_window = xcb_generate_id(conn);
|
||||
xcb_create_window(conn,
|
||||
XCB_COPY_FROM_PARENT, // depth
|
||||
sync_window, // window
|
||||
root_window, // parent
|
||||
-15, // x
|
||||
-15, // y
|
||||
1, // width
|
||||
1, // height
|
||||
0, // border_width
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT, // class
|
||||
XCB_COPY_FROM_PARENT, // visual
|
||||
XCB_CW_OVERRIDE_REDIRECT, // value_mask
|
||||
(uint32_t[]){
|
||||
1, // override_redirect
|
||||
}); // value_list
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void xtest_sync_with(int window) {
|
||||
xcb_client_message_event_t ev;
|
||||
memset(&ev, '\0', sizeof(xcb_client_message_event_t));
|
||||
|
||||
const int nonce = rand() % 255;
|
||||
|
||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||
ev.window = sync_window;
|
||||
ev.type = i3_sync_atom;
|
||||
ev.format = 32;
|
||||
ev.data.data32[0] = sync_window;
|
||||
ev.data.data32[1] = nonce;
|
||||
|
||||
xcb_send_event(conn, false, (xcb_window_t)window, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev);
|
||||
xcb_flush(conn);
|
||||
|
||||
xcb_generic_event_t *event = NULL;
|
||||
while (1) {
|
||||
free(event);
|
||||
if ((event = xcb_wait_for_event(conn)) == NULL) {
|
||||
break;
|
||||
}
|
||||
if (event->response_type == 0) {
|
||||
fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Strip off the highest bit (set if the event is generated) */
|
||||
const int type = (event->response_type & 0x7F);
|
||||
switch (type) {
|
||||
case XCB_CLIENT_MESSAGE: {
|
||||
xcb_client_message_event_t *ev = (xcb_client_message_event_t *)event;
|
||||
{
|
||||
const uint32_t got = ev->data.data32[0];
|
||||
const uint32_t want = sync_window;
|
||||
if (got != want) {
|
||||
fprintf(stderr, "Ignoring ClientMessage: unknown window: got %d, want %d\n", got, want);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
{
|
||||
const uint32_t got = ev->data.data32[1];
|
||||
const uint32_t want = nonce;
|
||||
if (got != want) {
|
||||
fprintf(stderr, "Ignoring ClientMessage: unknown nonce: got %d, want %d\n", got, want);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
fprintf(stderr, "Unexpected X11 event of type %d received (XCB_CLIENT_MESSAGE = %d)\n", type, XCB_CLIENT_MESSAGE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(event);
|
||||
}
|
||||
|
||||
void xtest_sync_with_i3() {
|
||||
xtest_sync_with((int)root_window);
|
||||
}
|
||||
|
||||
// NOTE: while |group| should be a uint8_t, Inline::C will not define the
|
||||
// function unless we use an int.
|
||||
bool set_xkb_group(int group) {
|
||||
|
@ -170,86 +259,6 @@ sub import {
|
|||
|
||||
=cut
|
||||
|
||||
my $i3;
|
||||
our @binding_events;
|
||||
|
||||
=head2 start_binding_capture()
|
||||
|
||||
Captures all binding events sent by i3 in the C<@binding_events> symbol, so
|
||||
that you can verify the correct number of binding events was generated.
|
||||
|
||||
my $pid = launch_with_config($config);
|
||||
start_binding_capture;
|
||||
# …
|
||||
sync_with_i3;
|
||||
is(scalar @i3test::XTEST::binding_events, 2, 'Received exactly 2 binding events');
|
||||
|
||||
=cut
|
||||
|
||||
sub start_binding_capture {
|
||||
# Store a copy of each binding event so that we can count the expected
|
||||
# events in test cases.
|
||||
$i3 = i3(get_socket_path());
|
||||
$i3->connect()->recv;
|
||||
$i3->subscribe({
|
||||
binding => sub {
|
||||
my ($event) = @_;
|
||||
@binding_events = (@binding_events, $event);
|
||||
},
|
||||
})->recv;
|
||||
}
|
||||
|
||||
=head2 listen_for_binding($cb)
|
||||
|
||||
Helper function to evaluate whether sending KeyPress/KeyRelease events via
|
||||
XTEST triggers an i3 key binding or not (with a timeout of 0.5s). Expects key
|
||||
bindings to be configured in the form “bindsym <binding> nop <binding>”, e.g.
|
||||
“bindsym Mod4+Return nop Mod4+Return”.
|
||||
|
||||
is(listen_for_binding(
|
||||
sub {
|
||||
xtest_key_press(133); # Super_L
|
||||
xtest_key_press(36); # Return
|
||||
xtest_key_release(36); # Return
|
||||
xtest_key_release(133); # Super_L
|
||||
},
|
||||
),
|
||||
'Mod4+Return',
|
||||
'triggered the "Mod4+Return" keybinding');
|
||||
|
||||
=cut
|
||||
|
||||
sub listen_for_binding {
|
||||
my ($cb) = @_;
|
||||
my $triggered = AnyEvent->condvar;
|
||||
my $i3 = i3(get_socket_path());
|
||||
$i3->connect()->recv;
|
||||
$i3->subscribe({
|
||||
binding => sub {
|
||||
my ($event) = @_;
|
||||
return unless $event->{change} eq 'run';
|
||||
# We look at the command (which is “nop <binding>”) because that is
|
||||
# easier than re-assembling the string representation of
|
||||
# $event->{binding}.
|
||||
$triggered->send($event->{binding}->{command});
|
||||
},
|
||||
})->recv;
|
||||
|
||||
my $t;
|
||||
$t = AnyEvent->timer(
|
||||
after => 0.5,
|
||||
cb => sub {
|
||||
$triggered->send('timeout');
|
||||
}
|
||||
);
|
||||
|
||||
$cb->();
|
||||
|
||||
my $recv = $triggered->recv;
|
||||
$recv =~ s/^nop //g;
|
||||
return $recv;
|
||||
}
|
||||
|
||||
=head2 set_xkb_group($group)
|
||||
|
||||
Changes the current XKB group from the default of 1 to C<$group>, which must be
|
||||
|
@ -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.
|
||||
|
||||
=head2 xtest_sync_with($window)
|
||||
|
||||
Ensures the specified window has processed all X11 events which were triggered
|
||||
by this module, provided the window response to the i3 sync protocol.
|
||||
|
||||
=head2 xtest_sync_with_i3()
|
||||
|
||||
Ensures i3 has processed all X11 events which were triggered by this module.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Michael Stapelberg <michael@i3wm.org>
|
||||
|
|
|
@ -21,13 +21,6 @@ my $i3 = i3(get_socket_path());
|
|||
|
||||
my $tmp = fresh_workspace;
|
||||
|
||||
sub fullscreen_windows {
|
||||
my $ws = $tmp;
|
||||
$ws = shift if @_;
|
||||
|
||||
scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)}
|
||||
}
|
||||
|
||||
# get the output of this workspace
|
||||
my $tree = $i3->get_tree->recv;
|
||||
my @outputs = @{$tree->{nodes}};
|
||||
|
@ -143,11 +136,11 @@ ok(!eq_hash($new_rect, $original_rect), "Window got repositioned");
|
|||
$swindow->fullscreen(1);
|
||||
sync_with_i3;
|
||||
|
||||
is(fullscreen_windows(), 1, 'amount of fullscreen windows');
|
||||
is_num_fullscreen($tmp, 1, 'amount of fullscreen windows');
|
||||
|
||||
$window->fullscreen(0);
|
||||
sync_with_i3;
|
||||
is(fullscreen_windows(), 1, 'amount of fullscreen windows');
|
||||
is_num_fullscreen($tmp, 1, 'amount of fullscreen windows');
|
||||
|
||||
ok($swindow->mapped, 'window mapped after other fullscreen ended');
|
||||
|
||||
|
@ -160,15 +153,15 @@ ok($swindow->mapped, 'window mapped after other fullscreen ended');
|
|||
$swindow->fullscreen(0);
|
||||
sync_with_i3;
|
||||
|
||||
is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling');
|
||||
is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after disabling');
|
||||
|
||||
cmd 'fullscreen';
|
||||
|
||||
is(fullscreen_windows(), 1, 'amount of fullscreen windows after fullscreen command');
|
||||
is_num_fullscreen($tmp, 1, 'amount of fullscreen windows after fullscreen command');
|
||||
|
||||
cmd 'fullscreen';
|
||||
|
||||
is(fullscreen_windows(), 0, 'amount of fullscreen windows after fullscreen command');
|
||||
is_num_fullscreen($tmp, 0, 'amount of fullscreen windows after fullscreen command');
|
||||
|
||||
# clean up the workspace so that it will be cleaned when switching away
|
||||
cmd 'kill' for (@{get_ws_content($tmp)});
|
||||
|
@ -221,18 +214,18 @@ $swindow = open_window;
|
|||
|
||||
cmd 'fullscreen';
|
||||
|
||||
is(fullscreen_windows($tmp2), 1, 'one fullscreen window on second ws');
|
||||
is_num_fullscreen($tmp2, 1, 'one fullscreen window on second ws');
|
||||
|
||||
cmd "move workspace $tmp";
|
||||
|
||||
is(fullscreen_windows($tmp2), 0, 'no fullscreen windows on second ws');
|
||||
is(fullscreen_windows($tmp), 1, 'one fullscreen window on first ws');
|
||||
is_num_fullscreen($tmp2, 0, 'no fullscreen windows on second ws');
|
||||
is_num_fullscreen($tmp, 1, 'one fullscreen window on first ws');
|
||||
|
||||
$swindow->fullscreen(0);
|
||||
sync_with_i3;
|
||||
|
||||
# Verify that $swindow was the one that initially remained fullscreen.
|
||||
is(fullscreen_windows($tmp), 0, 'no fullscreen windows on first ws');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen windows on first ws');
|
||||
|
||||
################################################################################
|
||||
# Verify that opening a window with _NET_WM_STATE_FULLSCREEN unfullscreens any
|
||||
|
@ -245,14 +238,14 @@ $window = open_window();
|
|||
|
||||
cmd "fullscreen";
|
||||
|
||||
is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws');
|
||||
is_num_fullscreen($tmp, 1, 'one fullscreen window on ws');
|
||||
is($x->input_focus, $window->id, 'fullscreen window focused');
|
||||
|
||||
$swindow = open_window({
|
||||
fullscreen => 1
|
||||
});
|
||||
|
||||
is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws');
|
||||
is_num_fullscreen($tmp, 1, 'one fullscreen window on ws');
|
||||
is($x->input_focus, $swindow->id, 'fullscreen window focused');
|
||||
|
||||
################################################################################
|
||||
|
@ -263,19 +256,19 @@ $tmp = fresh_workspace;
|
|||
|
||||
$window = open_window;
|
||||
is($x->input_focus, $window->id, 'window focused');
|
||||
is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
|
||||
|
||||
cmd 'fullscreen enable';
|
||||
is($x->input_focus, $window->id, 'window still focused');
|
||||
is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
|
||||
|
||||
cmd 'fullscreen enable';
|
||||
is($x->input_focus, $window->id, 'window still focused');
|
||||
is(fullscreen_windows($tmp), 1, 'still one fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 1, 'still one fullscreen window on workspace');
|
||||
|
||||
$window->fullscreen(0);
|
||||
sync_with_i3;
|
||||
is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
|
||||
|
||||
################################################################################
|
||||
# Verify that command ‘fullscreen enable global’ works and is idempotent.
|
||||
|
@ -285,19 +278,19 @@ $tmp = fresh_workspace;
|
|||
|
||||
$window = open_window;
|
||||
is($x->input_focus, $window->id, 'window focused');
|
||||
is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
|
||||
|
||||
cmd 'fullscreen enable global';
|
||||
is($x->input_focus, $window->id, 'window still focused');
|
||||
is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
|
||||
|
||||
cmd 'fullscreen enable global';
|
||||
is($x->input_focus, $window->id, 'window still focused');
|
||||
is(fullscreen_windows($tmp), 1, 'still one fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 1, 'still one fullscreen window on workspace');
|
||||
|
||||
$window->fullscreen(0);
|
||||
sync_with_i3;
|
||||
is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
|
||||
|
||||
################################################################################
|
||||
# Verify that command ‘fullscreen disable’ works and is idempotent.
|
||||
|
@ -307,19 +300,19 @@ $tmp = fresh_workspace;
|
|||
|
||||
$window = open_window;
|
||||
is($x->input_focus, $window->id, 'window focused');
|
||||
is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
|
||||
|
||||
$window->fullscreen(1);
|
||||
sync_with_i3;
|
||||
is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
|
||||
|
||||
cmd 'fullscreen disable';
|
||||
is($x->input_focus, $window->id, 'window still focused');
|
||||
is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
|
||||
|
||||
cmd 'fullscreen disable';
|
||||
is($x->input_focus, $window->id, 'window still focused');
|
||||
is(fullscreen_windows($tmp), 0, 'still no fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 0, 'still no fullscreen window on workspace');
|
||||
|
||||
################################################################################
|
||||
# Verify that command ‘fullscreen toggle’ works.
|
||||
|
@ -328,15 +321,15 @@ is(fullscreen_windows($tmp), 0, 'still no fullscreen window on workspace');
|
|||
$tmp = fresh_workspace;
|
||||
|
||||
$window = open_window;
|
||||
is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
|
||||
|
||||
cmd 'fullscreen toggle';
|
||||
is($x->input_focus, $window->id, 'window still focused');
|
||||
is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
|
||||
|
||||
cmd 'fullscreen toggle';
|
||||
is($x->input_focus, $window->id, 'window still focused');
|
||||
is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
|
||||
|
||||
################################################################################
|
||||
# Verify that a window’s fullscreen is disabled when another one is enabled
|
||||
|
@ -349,15 +342,15 @@ $window = open_window;
|
|||
$other = open_window;
|
||||
|
||||
is($x->input_focus, $other->id, 'other window focused');
|
||||
is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen window on workspace');
|
||||
|
||||
cmd 'fullscreen enable';
|
||||
is($x->input_focus, $other->id, 'other window focused');
|
||||
is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
|
||||
|
||||
cmd '[id="' . $window->id . '"] fullscreen enable';
|
||||
is($x->input_focus, $window->id, 'window focused');
|
||||
is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
|
||||
is_num_fullscreen($tmp, 1, 'one fullscreen window on workspace');
|
||||
|
||||
################################################################################
|
||||
# Verify that when a global fullscreen is enabled the window is focused and
|
||||
|
|
|
@ -304,7 +304,7 @@ for ($type = 1; $type <= 2; $type++) {
|
|||
cmd 'move right';
|
||||
cmd '[id="' . $w3->id . '"] focus';
|
||||
sync_with_i3;
|
||||
my $ws = get_ws($tmp);
|
||||
$ws = get_ws($tmp);
|
||||
ok(!$ws->{urgent}, 'urgent flag not set on workspace');
|
||||
|
||||
##############################################################################
|
||||
|
|
|
@ -16,61 +16,25 @@
|
|||
|
||||
use i3test;
|
||||
|
||||
my $i3 = i3(get_socket_path());
|
||||
$i3->connect()->recv;
|
||||
|
||||
################################
|
||||
# Workspaces requests and events
|
||||
################################
|
||||
|
||||
my $old_ws = get_ws(focused_ws());
|
||||
|
||||
# Events
|
||||
|
||||
# We are switching to an empty workpspace from an empty workspace, so we expect
|
||||
# to receive "init", "focus", and "empty".
|
||||
my $init = AnyEvent->condvar;
|
||||
my $focus = AnyEvent->condvar;
|
||||
my $empty = AnyEvent->condvar;
|
||||
$i3->subscribe({
|
||||
workspace => sub {
|
||||
my ($event) = @_;
|
||||
if ($event->{change} eq 'init') {
|
||||
$init->send($event);
|
||||
} elsif ($event->{change} eq 'focus') {
|
||||
$focus->send($event);
|
||||
} elsif ($event->{change} eq 'empty') {
|
||||
$empty->send($event);
|
||||
}
|
||||
}
|
||||
})->recv;
|
||||
|
||||
cmd 'workspace 2';
|
||||
|
||||
my $t;
|
||||
$t = AnyEvent->timer(
|
||||
after => 0.5,
|
||||
cb => sub {
|
||||
$init->send(0);
|
||||
$focus->send(0);
|
||||
$empty->send(0);
|
||||
}
|
||||
);
|
||||
|
||||
my $init_event = $init->recv;
|
||||
my $focus_event = $focus->recv;
|
||||
my $empty_event = $empty->recv;
|
||||
my @events = events_for(
|
||||
sub { cmd 'workspace 2' },
|
||||
'workspace');
|
||||
|
||||
my $current_ws = get_ws(focused_ws());
|
||||
|
||||
ok($init_event, 'workspace "init" event received');
|
||||
is($init_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the initted workspace con');
|
||||
is(scalar @events, 3, 'Received 3 events');
|
||||
is($events[0]->{change}, 'init', 'First event has change = init');
|
||||
is($events[0]->{current}->{id}, $current_ws->{id}, 'the "current" property contains the initted workspace con');
|
||||
|
||||
ok($focus_event, 'workspace "focus" event received');
|
||||
is($focus_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con');
|
||||
is($focus_event->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last');
|
||||
is($events[1]->{change}, 'focus', 'Second event has change = focus');
|
||||
is($events[1]->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con');
|
||||
is($events[1]->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last');
|
||||
|
||||
ok($empty_event, 'workspace "empty" event received');
|
||||
is($empty_event->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con');
|
||||
is($events[2]->{change}, 'empty', 'Third event has change = empty');
|
||||
is($events[2]->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con');
|
||||
|
||||
done_testing;
|
||||
|
|
|
@ -157,9 +157,6 @@ isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
|
|||
cmd 'focus child';
|
||||
is($x->input_focus, $right2->id, 'bottom right window focused again');
|
||||
|
||||
cmd '[id="' . $left->id . '"] focus';
|
||||
is($x->input_focus, $right2->id, 'prevented focus change to left window');
|
||||
|
||||
cmd 'focus up';
|
||||
is($x->input_focus, $right1->id, 'allowed focus up');
|
||||
|
||||
|
@ -178,9 +175,6 @@ is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
|
|||
cmd 'focus up';
|
||||
is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
|
||||
|
||||
cmd '[id="' . $diff_ws->id . '"] focus';
|
||||
is($x->input_focus, $right2->id, 'prevented focus change to different ws');
|
||||
|
||||
################################################################################
|
||||
# Same tests when we're in non-global fullscreen mode. It should now be possible
|
||||
# to focus a container in a different workspace.
|
||||
|
@ -202,9 +196,6 @@ isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
|
|||
cmd 'focus child';
|
||||
is($x->input_focus, $right2->id, 'bottom right window focused again');
|
||||
|
||||
cmd '[id="' . $left->id . '"] focus';
|
||||
is($x->input_focus, $right2->id, 'prevented focus change to left window');
|
||||
|
||||
cmd 'focus up';
|
||||
is($x->input_focus, $right1->id, 'allowed focus up');
|
||||
|
||||
|
@ -323,6 +314,105 @@ verify_move(2, 'prevented move to workspace by name');
|
|||
cmd "move to workspace prev";
|
||||
verify_move(2, 'prevented move to workspace by position');
|
||||
|
||||
# TODO: Tests for "move to output" and "move workspace to output".
|
||||
################################################################################
|
||||
# Ensure it's possible to focus a window using the focus command despite
|
||||
# fullscreen window blocking it. Fullscreen window should lose its fullscreen
|
||||
# mode.
|
||||
################################################################################
|
||||
|
||||
# first & second tiling, focus using id
|
||||
kill_all_windows;
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
my $first = open_window;
|
||||
my $second = open_window;
|
||||
cmd 'fullscreen';
|
||||
is($x->input_focus, $second->id, 'fullscreen window focused');
|
||||
is_num_fullscreen($tmp, 1, '1 fullscreen window');
|
||||
|
||||
cmd '[id="'. $first->id .'"] focus';
|
||||
sync_with_i3;
|
||||
|
||||
is($x->input_focus, $first->id, 'correctly focused using id');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen windows');
|
||||
|
||||
# first floating, second tiling, focus using 'focus floating'
|
||||
kill_all_windows;
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
$first = open_floating_window;
|
||||
$second = open_window;
|
||||
cmd 'fullscreen';
|
||||
is($x->input_focus, $second->id, 'fullscreen window focused');
|
||||
is_num_fullscreen($tmp, 1, '1 fullscreen window');
|
||||
|
||||
cmd 'focus floating';
|
||||
sync_with_i3;
|
||||
|
||||
is($x->input_focus, $first->id, 'correctly focused using focus floating');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen windows');
|
||||
|
||||
# first tiling, second floating, focus using 'focus tiling'
|
||||
kill_all_windows;
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
$first = open_window;
|
||||
$second = open_floating_window;
|
||||
cmd 'fullscreen';
|
||||
is($x->input_focus, $second->id, 'fullscreen window focused');
|
||||
is_num_fullscreen($tmp, 1, '1 fullscreen window');
|
||||
|
||||
cmd 'focus tiling';
|
||||
sync_with_i3;
|
||||
|
||||
is($x->input_focus, $first->id, 'correctly focused using focus tiling');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen windows');
|
||||
|
||||
################################################################################
|
||||
# When the fullscreen window is in an other workspace it should maintain its
|
||||
# fullscreen mode since it's not blocking the window to be focused.
|
||||
################################################################################
|
||||
|
||||
kill_all_windows;
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
$first = open_window;
|
||||
|
||||
$tmp2 = fresh_workspace;
|
||||
$second = open_window;
|
||||
cmd 'fullscreen';
|
||||
is($x->input_focus, $second->id, 'fullscreen window focused');
|
||||
is_num_fullscreen($tmp2, 1, '1 fullscreen window');
|
||||
|
||||
cmd '[id="'. $first->id .'"] focus';
|
||||
sync_with_i3;
|
||||
|
||||
is($x->input_focus, $first->id, 'correctly focused using focus id');
|
||||
is_num_fullscreen($tmp, 0, 'no fullscreen windows on first workspace');
|
||||
is_num_fullscreen($tmp2, 1, 'still one fullscreen window on second workspace');
|
||||
|
||||
################################################################################
|
||||
# But a global window in another workspace is blocking the window to be focused.
|
||||
# Ensure that it loses its fullscreen mode.
|
||||
################################################################################
|
||||
|
||||
kill_all_windows;
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
$first = open_window;
|
||||
|
||||
$tmp2 = fresh_workspace;
|
||||
$second = open_window;
|
||||
cmd 'fullscreen global';
|
||||
is($x->input_focus, $second->id, 'global window focused');
|
||||
is_num_fullscreen($tmp2, 1, '1 fullscreen window');
|
||||
|
||||
cmd '[id="'. $first->id .'"] focus';
|
||||
sync_with_i3;
|
||||
|
||||
is($x->input_focus, $first->id, 'correctly focused using focus id');
|
||||
is_num_fullscreen($tmp2, 0, 'no fullscreen windows');
|
||||
|
||||
|
||||
# TODO: Tests for "move to output" and "move workspace to output".
|
||||
done_testing;
|
||||
|
|
|
@ -21,18 +21,44 @@ use i3test i3_autostart => 0;
|
|||
sub open_special {
|
||||
my %args = @_;
|
||||
$args{name} //= 'special window';
|
||||
$args{wm_class} //= 'special';
|
||||
|
||||
# We use dont_map because i3 will not map the window on the current
|
||||
# workspace. Thus, open_window would time out in wait_for_map (2 seconds).
|
||||
my $window = open_window(
|
||||
%args,
|
||||
wm_class => 'special',
|
||||
dont_map => 1,
|
||||
);
|
||||
$window->map;
|
||||
return $window;
|
||||
}
|
||||
|
||||
sub test_workspace_assignment {
|
||||
my $target_ws = "@_";
|
||||
|
||||
# initialize the target workspace, then go to a fresh one
|
||||
ok(!($target_ws ~~ @{get_workspace_names()}), "$target_ws does not exist yet");
|
||||
cmd "workspace $target_ws";
|
||||
cmp_ok(@{get_ws_content($target_ws)}, '==', 0, "no containers on $target_ws yet");
|
||||
cmd 'open';
|
||||
cmp_ok(@{get_ws_content($target_ws)}, '==', 1, "one container on $target_ws");
|
||||
my $tmp = fresh_workspace;
|
||||
|
||||
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
|
||||
ok($target_ws ~~ @{get_workspace_names()}, "$target_ws does not exist yet");
|
||||
|
||||
# We use sync_with_i3 instead of wait_for_map here because i3 will not actually
|
||||
# map the window -- it will be assigned to a different workspace and will only
|
||||
# be mapped once you switch to that workspace
|
||||
my $window = open_special;
|
||||
sync_with_i3;
|
||||
|
||||
ok(@{get_ws_content($tmp)} == 0, 'still no containers');
|
||||
ok(@{get_ws_content($target_ws)} == 2, "two containers on $target_ws");
|
||||
|
||||
return $window
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# start a window and see that it does not get assigned with an empty config
|
||||
#####################################################################
|
||||
|
@ -87,33 +113,67 @@ $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.
|
||||
# start a window and see that it gets assigned to a formerly unused
|
||||
# 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);
|
||||
|
||||
# initialize the target workspace, then go to a fresh one
|
||||
ok(!("targetws" ~~ @{get_workspace_names()}), 'targetws does not exist yet');
|
||||
cmd 'workspace targetws';
|
||||
cmp_ok(@{get_ws_content('targetws')}, '==', 0, 'no containers on targetws yet');
|
||||
cmd 'open';
|
||||
cmp_ok(@{get_ws_content('targetws')}, '==', 1, 'one container on targetws');
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
|
||||
ok("targetws" ~~ @{get_workspace_names()}, 'targetws does not exist yet');
|
||||
|
||||
|
||||
# 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');
|
||||
test_workspace_assignment("targetws");
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
|
@ -143,8 +203,127 @@ my $content = get_ws($tmp);
|
|||
ok(@{$content->{nodes}} == 0, 'no tiling cons');
|
||||
ok(@{$content->{floating_nodes}} == 1, 'one floating con');
|
||||
|
||||
$window->destroy;
|
||||
kill_all_windows;
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# test assignments to named outputs
|
||||
#####################################################################
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
|
||||
|
||||
workspace ws-0 output fake-0
|
||||
workspace ws-1 output fake-1
|
||||
workspace ws-2 output fake-2
|
||||
workspace ws-3 output fake-3
|
||||
|
||||
assign [class="special-0"] → output fake-0
|
||||
assign [class="special-1"] → output fake-1
|
||||
assign [class="special-2"] → output fake-2
|
||||
assign [class="special-3"] → output fake-3
|
||||
assign [class="special-4"] → output invalid
|
||||
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
sub open_in_output {
|
||||
my ($num, $expected_count) = @_;
|
||||
my $ws = "ws-$num";
|
||||
my $class = "special-$num";
|
||||
my $output = "fake-$num";
|
||||
|
||||
is_num_children($ws, $expected_count - 1,
|
||||
"before: " . ($expected_count - 1) . " containers on output $output");
|
||||
$window = open_special(wm_class => $class);
|
||||
sync_with_i3;
|
||||
is_num_children($ws, $expected_count,
|
||||
"after: $expected_count containers on output $output");
|
||||
}
|
||||
|
||||
cmd "workspace ws-0";
|
||||
open_in_output(0, 1);
|
||||
my $focused = $x->input_focus;
|
||||
|
||||
open_in_output(1, 1);
|
||||
is($x->input_focus, $focused, 'focus remains on output fake-0');
|
||||
|
||||
open_in_output(2, 1);
|
||||
is($x->input_focus, $focused, 'focus remains on output fake-0');
|
||||
|
||||
for my $i (1 .. 5){
|
||||
open_in_output(3, $i);
|
||||
is($x->input_focus, $focused, 'focus remains on output fake-0');
|
||||
}
|
||||
|
||||
# Check invalid output
|
||||
$tmp = fresh_workspace;
|
||||
open_special(wm_class => "special-4");
|
||||
sync_with_i3;
|
||||
is_num_children($tmp, 1, 'window assigned to invalid output opened in current workspace');
|
||||
open_special(wm_class => "special-3");
|
||||
sync_with_i3;
|
||||
is_num_children($tmp, 1, 'but window assigned to valid output did not');
|
||||
|
||||
kill_all_windows;
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# Test assignments to outputs with relative names
|
||||
#####################################################################
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
|
||||
|
||||
workspace left-top output fake-0
|
||||
workspace right-top output fake-1
|
||||
workspace right-bottom output fake-2
|
||||
workspace left-bottom output fake-3
|
||||
|
||||
assign [class="current"] → output current
|
||||
assign [class="left"] → output left
|
||||
assign [class="right"] → output right
|
||||
assign [class="up"] → output up
|
||||
assign [class="down"] → output down
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
cmd 'workspace left-top';
|
||||
|
||||
is_num_children('left-top', 0, 'no childreon on left-top');
|
||||
for my $i (1 .. 5){
|
||||
open_special(wm_class => 'current');
|
||||
}
|
||||
sync_with_i3;
|
||||
is_num_children('left-top', 5, 'windows opened in current workspace');
|
||||
|
||||
is_num_children('right-top', 0, 'no children on right-top');
|
||||
open_special(wm_class => 'right');
|
||||
sync_with_i3;
|
||||
is_num_children('right-top', 1, 'one child on right-top');
|
||||
|
||||
is_num_children('left-bottom', 0, 'no children on left-bottom');
|
||||
open_special(wm_class => 'down');
|
||||
sync_with_i3;
|
||||
is_num_children('left-bottom', 1, 'one child on left-bottom');
|
||||
|
||||
cmd 'workspace right-bottom';
|
||||
|
||||
open_special(wm_class => 'up');
|
||||
sync_with_i3;
|
||||
is_num_children('right-top', 2, 'two children on right-top');
|
||||
|
||||
open_special(wm_class => 'left');
|
||||
sync_with_i3;
|
||||
is_num_children('left-bottom', 2, 'two children on left-bottom');
|
||||
|
||||
kill_all_windows;
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
|
@ -181,7 +360,7 @@ $tmp = fresh_workspace;
|
|||
|
||||
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
|
||||
my @docked = get_dock_clients;
|
||||
is(@docked, 0, 'one dock client yet');
|
||||
is(@docked, 0, 'no dock client yet');
|
||||
|
||||
$window = open_special(
|
||||
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
|
||||
|
|
|
@ -375,7 +375,74 @@ ok(@content == 2, 'two containers opened');
|
|||
isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
|
||||
isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# 16: Check that the command 'layout toggle split' works regardless
|
||||
# of what layout we're using.
|
||||
#####################################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
workspace_layout default
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
my @layouts = ('splith', 'splitv', 'tabbed', 'stacked');
|
||||
my $first_layout;
|
||||
|
||||
foreach $first_layout (@layouts) {
|
||||
cmd 'layout ' . $first_layout;
|
||||
$first = open_window;
|
||||
$second = open_window;
|
||||
cmd 'layout toggle split';
|
||||
@content = @{get_ws_content($tmp)};
|
||||
if ($first_layout eq 'splith') {
|
||||
is($content[0]->{layout}, 'splitv', 'layout toggles to splitv');
|
||||
} else {
|
||||
is($content[0]->{layout}, 'splith', 'layout toggles to splith');
|
||||
}
|
||||
|
||||
cmd '[id="' . $first->id . '"] kill';
|
||||
cmd '[id="' . $second->id . '"] kill';
|
||||
sync_with_i3;
|
||||
}
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# 17: Check about setting a new layout.
|
||||
#####################################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
workspace_layout default
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
my $second_layout;
|
||||
|
||||
foreach $first_layout (@layouts) {
|
||||
foreach $second_layout (@layouts) {
|
||||
cmd 'layout ' . $first_layout;
|
||||
$first = open_window;
|
||||
$second = open_window;
|
||||
cmd 'layout ' . $second_layout;
|
||||
@content = @{get_ws_content($tmp)};
|
||||
is($content[0]->{layout}, $second_layout, 'layout changes to ' . $second_layout);
|
||||
|
||||
cmd '[id="' . $first->id . '"] kill';
|
||||
cmd '[id="' . $second->id . '"] kill';
|
||||
sync_with_i3;
|
||||
}
|
||||
}
|
||||
|
||||
done_testing;
|
||||
|
|
|
@ -95,7 +95,19 @@ EOT
|
|||
|
||||
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;
|
||||
|
||||
|
|
|
@ -429,7 +429,7 @@ does_i3_live;
|
|||
################################################################################
|
||||
|
||||
clear_scratchpad;
|
||||
my $ws = fresh_workspace;
|
||||
$ws = fresh_workspace;
|
||||
|
||||
open_window;
|
||||
my $scratch = get_focused($ws);
|
||||
|
|
|
@ -190,7 +190,7 @@ exit_gracefully($pid);
|
|||
# 7: check floating_maximum_size with cmd_size
|
||||
################################################################################
|
||||
|
||||
my $config = <<EOT;
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
|
@ -201,12 +201,12 @@ EOT
|
|||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
my $window = open_floating_window(rect => [ 0, 0, 90, 80 ]);
|
||||
$window = open_floating_window(rect => [ 0, 0, 90, 80 ]);
|
||||
cmd 'border none';
|
||||
|
||||
cmd 'resize set 101 91';
|
||||
sync_with_i3;
|
||||
my $rect = $window->rect;
|
||||
$rect = $window->rect;
|
||||
is($rect->{width}, 100, 'width did not exceed maximum width');
|
||||
is($rect->{height}, 90, 'height did not exceed maximum height');
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ use i3test;
|
|||
sub send_net_active_window {
|
||||
my ($id, $source) = @_;
|
||||
|
||||
$source = ($source eq 'pager' ? 2 : 0);
|
||||
$source = (((defined $source) && ($source eq 'pager')) ? 2 : 0);
|
||||
|
||||
my $msg = pack "CCSLLLLLLL",
|
||||
X11::XCB::CLIENT_MESSAGE, # response_type
|
||||
|
@ -137,7 +137,7 @@ is($x->input_focus, $win3->id, 'window 3 still focused');
|
|||
# is received.
|
||||
################################################################################
|
||||
|
||||
my $scratch = open_window;
|
||||
$scratch = open_window;
|
||||
|
||||
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
Loading…
Reference in New Issue