i3bar, i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any
new dependencies.
- i3-migrate-config-to-v4 is implemented in Perl, but it has no dependencies
- besides Perl 5.10.
+ i3-migrate-config-to-v4 and i3-dmenu-desktop are implemented in Perl, but have
+ no dependencies besides Perl 5.10.
+
+ i3-save-tree is also implemented in Perl and needs AnyEvent::I3 and JSON::XS.
+ While i3-save-tree is not required for running i3 itself, it is strongly
+ recommended to provide it in distribution packages.
[ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION}
[ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
mkdir i3-${VERSION}
- cp i3-migrate-config-to-v4 generate-command-parser.pl i3-sensible-* i3-dmenu-desktop i3.config.keycodes DEPENDS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3-with-shmlog.xsession.desktop i3.applications.desktop pseudo-doc.doxygen common.mk Makefile i3-${VERSION}
+ cp i3-migrate-config-to-v4 i3-save-tree generate-command-parser.pl i3-sensible-* i3-dmenu-desktop i3.config.keycodes DEPENDS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3-with-shmlog.xsession.desktop i3.applications.desktop pseudo-doc.doxygen common.mk Makefile i3-${VERSION}
cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar i3-dump-log yajl-fallback include man parser-specs testcases i3-${VERSION}
# Only copy toplevel documentation (important stuff)
mkdir i3-${VERSION}/docs
--- /dev/null
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+# © 2012 Michael Stapelberg
+# Licensed under BSD license, see http://code.i3wm.org/i3/tree/LICENSE
+#
+# Append this line to your i3 config file:
+# exec_always ~/per-workspace-layout.pl
+#
+# Then, change the %layouts hash like you want your workspaces to be set up.
+# This script requires i3 >= v4.4 for the extended workspace event.
+
+use strict;
+use warnings;
+use AnyEvent;
+use AnyEvent::I3;
+use v5.10;
+
+my %layouts = (
+ '4' => 'tabbed',
+ '5' => 'stacked',
+);
+
+my $i3 = i3();
+
+die "Could not connect to i3: $!" unless $i3->connect->recv();
+
+die "Could not subscribe to the workspace event: $!" unless
+ $i3->subscribe({
+ workspace => sub {
+ my ($msg) = @_;
+ return unless $msg->{change} eq 'focus';
+ die "Your version of i3 is too old. You need >= v4.4"
+ unless exists($msg->{current});
+ my $ws = $msg->{current};
+
+ # If the workspace already has children, don’t change the layout.
+ return unless scalar @{$ws->{nodes}} == 0;
+
+ my $name = $ws->{name};
+ my $con_id = $ws->{id};
+
+ return unless exists $layouts{$name};
+
+ $i3->command(qq|[con_id="$con_id"] layout | . $layouts{$name});
+ },
+ _error => sub {
+ my ($msg) = @_;
+ say "AnyEvent::I3 error: $msg";
+ say "Exiting.";
+ exit 1;
+ },
+ })->recv->{success};
+
+# Run forever.
+AnyEvent->condvar->recv
-i3-wm (4.6.1-1) unstable; urgency=low
+i3-wm (4.7.3-1) unstable; urgency=low
- * NOT YET RELEASED.
+ * NOT YET RELEASED
- -- Michael Stapelberg <stapelberg@debian.org> Wed, 07 Aug 2013 20:53:26 +0200
+ -- Michael Stapelberg <stapelberg@debian.org> Thu, 23 Jan 2014 23:11:48 +0100
+
+i3-wm (4.7.2-1) unstable; urgency=low
+
+ * New upstream release. (Closes: #736396)
+
+ -- Michael Stapelberg <stapelberg@debian.org> Thu, 23 Jan 2014 23:03:03 +0100
+
+i3-wm (4.7.1-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org> Tue, 21 Jan 2014 19:29:34 +0100
+
+i3-wm (4.7-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org> Sun, 22 Dec 2013 21:19:02 +0100
i3-wm (4.6-1) unstable; urgency=low
Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, x11-utils
Provides: x-window-manager
Suggests: rxvt-unicode | x-terminal-emulator
-Recommends: xfonts-base
+Recommends: xfonts-base, libanyevent-i3-perl, libjson-xs-perl
Description: improved dynamic tiling window manager
Key features of i3 are good documentation, reasonable defaults (changeable in
a simple configuration file) and good multi-monitor support. The user
--- /dev/null
+usr/share/man/man1/i3.1.gz usr/share/man/man1/i3-with-shmlog.1.gz
$(MAKE) -C docs
override_dh_installchangelogs:
- dh_installchangelogs RELEASE-NOTES-4.6
+ dh_installchangelogs RELEASE-NOTES-*
override_dh_install:
$(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install
</div>\r
{disable-javascript%<div id="footnotes"><hr /></div>}\r
<div id="footer" lang="de">\r
-© 2009-2012 Michael Stapelberg, <a href="/impress.html">Impressum</a>
+© 2009-2014 Michael Stapelberg, <a href="http://i3wm.org/impress.html">Impressum</a>
</div>\r
</body>\r
</html>\r
src/resize.c::
Contains the functions to resize containers.
+src/restore_layout.c::
+Everything for restored containers that is not pure state parsing (which can be
+found in load_layout.c).
+
src/sighandler.c::
Handles +SIGSEGV+, +SIGABRT+ and +SIGFPE+ by showing a dialog that i3 crashed.
You can chose to let it dump core, to restart it in-place or to restart it
Instance of the block, if set
x, y::
X11 root window coordinates where the click occured
-button:
+button::
X11 button ID (for example 1 to 3 for left/middle/right mouse button)
*Example*:
IPC interface (interprocess communication)
==========================================
Michael Stapelberg <michael@i3wm.org>
-October 2012
+February 2014
This document describes how to interface with i3 from a separate process. This
is useful for example to remote-control i3 (to write test cases for example) or
=== COMMAND reply
-The reply consists of a single serialized map. At the moment, the only
-property is +success (bool)+, but this will be expanded in future versions.
+The reply consists of a list of serialized maps for each command that was
+parsed. Each has the property +success (bool)+ and may also include a
+human-readable error message in the property +error (string)+.
*Example:*
-------------------
-{ "success": true }
+[{ "success": true }]
-------------------
=== WORKSPACES reply
The name of this output (as seen in +xrandr(1)+). Encoded in UTF-8.
active (boolean)::
Whether this output is currently active (has a valid mode).
-current_workspace (integer)::
- The current workspace which is visible on this output. +null+ if the
- output is not active.
+current_workspace (string)::
+ The name of the current workspace that is visible on this output. +null+ if
+ the output is not active.
rect (map)::
The rectangle of this output (equals the rect of the output it
is on), consists of x, y, width, height.
{
"name": "LVDS1",
"active": true,
- "current_workspace": 4,
+ "current_workspace": "4",
"rect": {
"x": 0,
"y": 0,
{
"name": "VGA1",
"active": true,
- "current_workspace": 1,
+ "current_workspace": "1",
"rect": {
"x": 1280,
"y": 0,
The internal name of this container. For all containers which are part
of the tree structure down to the workspace contents, this is set to a
nice human-readable name of the container.
+ For containers that have an X11 window, the content is the title
+ (_NET_WM_NAME property) of that window.
For all other containers, the content is not defined (yet).
+type (string)::
+ Type of this container. Can be one of "root", "output", "con",
+ "floating_con", "workspace" or "dockarea".
border (string)::
Can be either "normal", "none" or "1pixel", dependending on the
container’s border style.
Sent whenever i3 changes its binding mode.
window (3)::
Sent when a client's window is successfully reparented (that is when i3
- has finished fitting it into a container).
+ has finished fitting it into a container), when a window received input
+ focus or when a window title has been updated.
barconfig_update (4)::
Sent when the hidden_state or mode field in the barconfig of any bar
instance was updated.
"change": "focus",
"current": {
"id": 28489712,
- "type":4,
+ "type": "workspace",
...
}
"old": {
"id": 28489715,
- "type": 4,
+ "type": "workspace",
...
}
}
=== window event
This event consists of a single serialized map containing a property
-+change (string)+ which currently can indicate only that a new window
-has been successfully reparented (the value will be "new").
++change (string)+ which indicates the type of the change ("focus", "new",
+"title").
Additionally a +container (object)+ field will be present, which consists
-of the window's parent container. Be aware that the container will hold
-the initial name of the newly reparented window (e.g. if you run urxvt
-with a shell that changes the title, you will still at this point get the
-window title as "urxvt").
+of the window's parent container. Be aware that for the "new" event, the
+container will hold the initial name of the newly reparented window (e.g.
+if you run urxvt with a shell that changes the title, you will still at
+this point get the window title as "urxvt").
*Example:*
---------------------------
"change": "new",
"container": {
"id": 35569536,
- "type": 2,
+ "type": "con",
...
}
}
C::
i3 includes a headerfile +i3/ipc.h+ which provides you all constants.
- However, there is no library yet.
-Ruby::
- http://github.com/badboy/i3-ipc
-Perl::
- https://metacpan.org/module/AnyEvent::I3
-Python::
- * https://github.com/whitelynx/i3ipc
- * https://github.com/ziberna/i3-py (includes higher-level features)
+
+ https://github.com/acrisci/i3ipc-glib
Go::
* https://github.com/proxypoke/i3ipc
+JavaScript::
+ * https://github.com/acrisci/i3ipc-gjs
+Lua::
+ * https:/github.com/acrisci/i3ipc-lua
+Perl::
+ * https://metacpan.org/module/AnyEvent::I3
+Python::
+ * https://github.com/acrisci/i3ipc-python
+ * https://github.com/whitelynx/i3ipc (not maintained)
+ * https://github.com/ziberna/i3-py (not maintained)
+Ruby::
+ http://github.com/badboy/i3-ipc
You can toggle floating mode for a window by pressing +$mod+Shift+Space+. By
dragging the window’s titlebar with your mouse you can move the window
around. By grabbing the borders and moving them you can resize the window. You
-can also do that by using the <<floating_modifier>>.
+can also do that by using the <<floating_modifier>>. Another way to resize
+floating windows using the mouse is to right-click on the titlebar and drag.
For resizing floating windows with your keyboard, see <<resizingconfig>>.
A client which is not the focused one of its container.
client.urgent::
A client which has its urgency hint activated.
+client.placeholder::
+ Background and text color are used to draw placeholder window contents
+ (when restoring layouts). Border and indicator are ignored.
You can also specify the color to be used to paint the background of the client
windows. This color will be used to paint the window on top of which the client
client.focused_inactive #333333 #5f676a #ffffff #484e50
client.unfocused #333333 #222222 #888888 #292d2e
client.urgent #2f343a #900000 #ffffff #900000
+client.placeholder #000000 #0c0c0c #ffffff #000000
---------------------------------------------------------
Note that for the window decorations, the color around the child window is the
/* The "<=" operator is intentional: We also handle the terminating 0-byte
* explicitly by looking for an 'end' token. */
- while ((walk - input) <= len) {
+ while ((size_t)(walk - input) <= len) {
/* Skip whitespace before every token, newlines are relevant since they
* separate configuration directives. */
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
char *head, *tail, *result;
tail = strchr(path, '/');
- head = strndup(path, tail ? tail - path : strlen(path));
+ head = strndup(path, tail ? (size_t)(tail - path) : strlen(path));
int res = glob(head, GLOB_TILDE, NULL, &globbuf);
free(head);
-#ifndef I3_XCB_H
-#define I3_XCB_H
+#pragma once
/* from X11/keysymdef.h */
#define XCB_NUM_LOCK 0xff7f
#define xmacro(atom) xcb_atom_t A_ ## atom;
#include "atoms.xmacro"
#undef xmacro
-
-#endif
-#ifndef I3_INPUT
-#define I3_INPUT
+#pragma once
#include <err.h>
while (0)
extern xcb_window_t root;
-
-#endif
return 1;
}
-yajl_callbacks reply_callbacks = {
- NULL,
- &reply_boolean_cb,
- NULL,
- NULL,
- NULL,
- &reply_string_cb,
- &reply_start_map_cb,
- &reply_map_key_cb,
- &reply_end_map_cb,
- NULL,
- NULL
+static yajl_callbacks reply_callbacks = {
+ .yajl_boolean = reply_boolean_cb,
+ .yajl_string = reply_string_cb,
+ .yajl_start_map = reply_start_map_cb,
+ .yajl_map_key = reply_map_key_cb,
+ .yajl_end_map = reply_end_map_cb,
};
int main(int argc, char *argv[]) {
socket_path = getenv("I3SOCK");
int o, option_index = 0;
- int message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
+ uint32_t message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
char *payload = NULL;
bool quiet = false;
-#ifndef I3_NAGBAR
-#define I3_NAGBAR
+#pragma once
#include <err.h>
#undef xmacro
extern xcb_window_t root;
-
-#endif
uint32_t top_end_x;
uint32_t bottom_start_x;
uint32_t bottom_end_x;
- } __attribute__((__packed__)) strut_partial = {0,};
+ } __attribute__((__packed__)) strut_partial;
+ memset(&strut_partial, 0, sizeof(strut_partial));
strut_partial.top = font.height + 6;
strut_partial.top_start_x = 0;
--- /dev/null
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+#
+# © 2013-2014 Michael Stapelberg
+#
+# Requires perl ≥ v5.10, AnyEvent::I3 and JSON::XS
+
+use strict;
+use warnings qw(FATAL utf8);
+use Data::Dumper;
+use IPC::Open2;
+use POSIX qw(locale_h);
+use File::Find;
+use File::Basename qw(basename);
+use File::Temp qw(tempfile);
+use Getopt::Long;
+use Pod::Usage;
+use AnyEvent::I3;
+use JSON::XS;
+use List::Util qw(first);
+use v5.10;
+use utf8;
+use open ':encoding(UTF-8)';
+
+binmode STDOUT, ':utf8';
+binmode STDERR, ':utf8';
+
+my $workspace;
+my $output;
+my $result = GetOptions(
+ 'workspace=s' => \$workspace,
+ 'output=s' => \$output,
+ 'version' => sub {
+ say "i3-save-tree 0.1 © 2013 Michael Stapelberg";
+ exit 0;
+ },
+ 'help' => sub {
+ pod2usage(-exitval => 0);
+ });
+
+die "Could not parse command line options" unless $result;
+
+if (!defined($workspace) && !defined($output)) {
+ die "One of --workspace or --output need to be specified";
+}
+
+unless (defined($workspace) ^ defined($output)) {
+ die "Only one of --workspace or --output can be specified";
+}
+
+my $i3 = i3();
+if (!$i3->connect->recv) {
+ die "Could not connect to i3";
+}
+
+sub filter_containers {
+ my ($tree, $pred) = @_;
+
+ $_ = $tree;
+ return $tree if $pred->();
+
+ for my $child (@{$tree->{nodes}}, @{$tree->{floating_nodes}}) {
+ my $result = filter_containers($child, $pred);
+ return $result if defined($result);
+ }
+
+ return undef;
+}
+
+sub leaf_node {
+ my ($tree) = @_;
+
+ return $tree->{type} eq 'con' &&
+ @{$tree->{nodes}} == 0 &&
+ @{$tree->{floating_nodes}} == 0;
+}
+
+my %allowed_keys = map { ($_, 1) } qw(
+ type
+ fullscreen_mode
+ layout
+ border
+ current_border_width
+ floating
+ percent
+ nodes
+ floating_nodes
+ name
+ geometry
+ window_properties
+);
+
+sub strip_containers {
+ my ($tree) = @_;
+
+ # layout is not relevant for a leaf container
+ delete $tree->{layout} if leaf_node($tree);
+
+ # fullscreen_mode conveys no state at all, it can either be 0 or 1 and the
+ # default is _always_ 0, so skip noop entries.
+ delete $tree->{fullscreen_mode} if $tree->{fullscreen_mode} == 0;
+
+ # names for non-leafs are auto-generated and useful only for i3 debugging
+ delete $tree->{name} unless leaf_node($tree);
+
+ delete $tree->{geometry} if zero_rect($tree->{geometry});
+
+ delete $tree->{current_border_width} if $tree->{current_border_width} == -1;
+
+ for my $key (keys %$tree) {
+ next if exists($allowed_keys{$key});
+
+ delete $tree->{$key};
+ }
+
+ for my $key (qw(nodes floating_nodes)) {
+ $tree->{$key} = [ map { strip_containers($_) } @{$tree->{$key}} ];
+ }
+
+ return $tree;
+}
+
+my $json_xs = JSON::XS->new->pretty(1)->allow_nonref->space_before(0)->canonical(1);
+
+sub zero_rect {
+ my ($rect) = @_;
+ return $rect->{x} == 0 &&
+ $rect->{y} == 0 &&
+ $rect->{width} == 0 &&
+ $rect->{height} == 0;
+}
+
+# Dumps the containers in JSON, but with comments to explain the user what she
+# needs to fix.
+sub dump_containers {
+ my ($tree, $ws, $last) = @_;
+
+ $ws //= "";
+
+ say $ws . '{';
+
+ $ws .= (' ' x 4);
+
+ if (!leaf_node($tree)) {
+ my $desc = $tree->{layout} . ' split container';
+ if ($tree->{type} ne 'con') {
+ $desc = $tree->{type};
+ }
+ say "$ws// $desc with " . @{$tree->{nodes}} . " children";
+ }
+
+ # Turn “window_properties” into “swallows” expressions, but only for leaf
+ # nodes. It only makes sense for leaf nodes to swallow anything.
+ if (leaf_node($tree)) {
+ my $swallows = {};
+ for my $property (keys %{$tree->{window_properties}}) {
+ $swallows->{$property} = '^' . quotemeta($tree->{window_properties}->{$property}) . '$';
+ }
+ $tree->{swallows} = [ $swallows ];
+ }
+ delete $tree->{window_properties};
+
+ my @keys = sort keys %$tree;
+ for (0 .. (@keys-1)) {
+ my $key = $keys[$_];
+ # Those are handled recursively, not printed.
+ next if $key eq 'nodes' || $key eq 'floating_nodes';
+
+ # JSON::XS’s encode appends a newline
+ chomp(my $val = $json_xs->encode($tree->{$key}));
+
+ # Fix indentation. Keep in mind we are producing output to be
+ # read/modified by a human.
+ $val =~ s/^/$ws/mg;
+ $val =~ s/^\s+//;
+
+ # Comment out all swallows criteria, they are just suggestions.
+ if ($key eq 'swallows') {
+ $val =~ s,^(\s*)\s{3}",\1// ",gm;
+ }
+
+ # Append a comma unless this is the last value.
+ # Ugly, but necessary so that we can print all values before recursing.
+ my $comma = ($_ == (@keys-1) &&
+ @{$tree->{nodes}} == 0 &&
+ @{$tree->{floating_nodes}} == 0 ? '' : ',');
+ say qq#$ws"$key": $val$comma#;
+ }
+
+ for my $key (qw(nodes floating_nodes)) {
+ my $num = scalar @{$tree->{$key}};
+ next if !$num;
+
+ say qq#$ws"$key": [#;
+ for (0 .. ($num-1)) {
+ dump_containers(
+ $tree->{$key}->[$_],
+ $ws . (' ' x 4),
+ ($_ == ($num-1)));
+ }
+ say qq#$ws]#;
+ }
+
+ $ws =~ s/\s{4}$//;
+
+ say $ws . ($last ? '}' : '},');
+}
+
+my $tree = $i3->get_tree->recv;
+
+my $dump;
+if (defined($workspace)) {
+ $dump = filter_containers($tree, sub {
+ $_->{type} eq 'workspace' && $_->{name} eq $workspace
+ });
+} else {
+ $dump = filter_containers($tree, sub {
+ $_->{type} eq 'output' && $_->{name} eq $output
+ });
+ # Get the output’s content container (living beneath dockarea containers).
+ $dump = first { $_->{type} eq 'con' } @{$dump->{nodes}};
+}
+
+$dump = strip_containers($dump);
+
+say "// vim:ts=4:sw=4:et";
+for my $key (qw(nodes floating_nodes)) {
+ for (0 .. (@{$dump->{$key}} - 1)) {
+ dump_containers($dump->{$key}->[$_], undef, 1);
+ # Newlines separate containers so that one can use { and } in vim to
+ # jump out of the current container.
+ say '';
+ }
+}
+
+=encoding utf-8
+
+=head1 NAME
+
+ i3-save-tree - save (parts of) the layout tree for restoring
+
+=head1 SYNOPSIS
+
+ i3-save-tree [--workspace=name] [--output=name]
+
+=head1 DESCRIPTION
+
+Dumps a workspace (or an entire output) to stdout. The data is supposed to be
+edited a bit by a human, then later fed to i3 via the append_layout command.
+
+The append_layout command will create placeholder windows, arranged in the
+layout the input file specifies. Each container should have a swallows
+specification. When a window is mapped (made visible on the screen) that
+matches the specification, i3 will put it into that place and kill the
+placeholder.
+
+=head1 OPTIONS
+
+=over
+
+=item B<--workspace=name>
+
+Specifies the workspace that should be dumped, e.g. 1. Either this or --output
+need to be specified.
+
+=item B<--output=name>
+
+Specifies the output that should be dumped, e.g. LVDS-1. Either this or
+--workspace need to be specified.
+
+=back
+
+=head1 VERSION
+
+Version 0.1
+
+=head1 AUTHOR
+
+Michael Stapelberg, C<< <michael at i3wm.org> >>
+
+=head1 LICENSE AND COPYRIGHT
+
+Copyright 2013 Michael Stapelberg.
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the BSD license.
+
+=cut
* child.c: Getting Input for the statusline
*
*/
-#ifndef CHILD_H_
-#define CHILD_H_
+#pragma once
#include <stdbool.h>
*
*/
void send_block_clicked(int button, const char *name, const char *instance, int x, int y);
-
-#endif
* © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
*/
-#ifndef COMMON_H_
-#define COMMON_H_
+#pragma once
#include <stdbool.h>
#include <xcb/xcb.h>
#include "config.h"
#include "libi3.h"
#include "parse_json_header.h"
-
-#endif
* config.c: Parses the configuration (received from i3).
*
*/
-#ifndef CONFIG_H_
-#define CONFIG_H_
+#pragma once
#include "common.h"
POS_BOT
} position_t;
+/* Bar display mode (hide unless modifier is pressed or show in dock mode or always hide in invisible mode) */
+typedef enum { M_DOCK = 0, M_HIDE = 1, M_INVISIBLE = 2 } bar_display_mode_t;
+
typedef struct config_t {
int modifier;
position_t position;
int num_outputs;
char **outputs;
- /* Bar display mode (hide unless modifier is pressed or show in dock mode or always hide in invisible mode) */
- enum { M_DOCK = 0, M_HIDE = 1, M_INVISIBLE = 2 } hide_on_modifier;
+ bar_display_mode_t hide_on_modifier;
/* The current hidden_state of the bar, which indicates whether it is hidden or shown */
enum { S_HIDE = 0, S_SHOW = 1 } hidden_state;
*
*/
void free_colors(struct xcb_color_strings_t *colors);
-
-#endif
* ipc.c: Communicating with i3
*
*/
-#ifndef IPC_H_
-#define IPC_H_
+#pragma once
#include <stdint.h>
*
*/
void subscribe_events(void);
-
-#endif
* mode.c: Handle mode-event and show current binding mode in the bar
*
*/
-#ifndef MODE_H_
-#define MODE_H_
+#pragma once
#include <xcb/xproto.h>
*
*/
void parse_mode_json(char *json);
-
-#endif
* outputs.c: Maintaining the output-list
*
*/
-#ifndef OUTPUTS_H_
-#define OUTPUTS_H_
+#pragma once
#include <xcb/xcb.h>
SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
};
-
-#endif
* protocol version and features.
*
*/
-#ifndef PARSE_JSON_HEADER_H_
-#define PARSE_JSON_HEADER_H_
+#pragma once
#include <stdint.h>
*
*/
void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed);
-
-#endif
* © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
*/
-#ifndef TRAYCLIENT_H_
-#define TRAYCLIENT_H_
+#pragma once
#include "common.h"
TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */
};
-
-#endif
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
-#ifndef UTIL_H_
-#define UTIL_H_
+#pragma once
#include "queue.h"
} \
} while (0)
-#endif
-
/* Securely fee tail-queues */
#define FREE_TAILQ(l, type) do { \
type *walk = TAILQ_FIRST(l); \
* workspaces.c: Maintaining the workspace-lists
*
*/
-#ifndef WORKSPACES_H_
-#define WORKSPACES_H_
+#pragma once
#include <xcb/xproto.h>
TAILQ_ENTRY(i3_ws) tailq; /* Pointer for the TAILQ-Macro */
};
-
-#endif
* xcb.c: Communicating with X
*
*/
-#ifndef XCB_H_
-#define XCB_H_
+#pragma once
#include <stdint.h>
//#include "outputs.h"
*
*/
void set_current_mode(struct mode *mode);
-
-#endif
#include "common.h"
/* Global variables for child_*() */
-i3bar_child child = { 0 };
+i3bar_child child;
/* stdin- and sigchild-watchers */
ev_io *stdin_io;
ev_child *child_sig;
/* JSON parser for stdin */
-yajl_callbacks callbacks;
yajl_handle parser;
/* JSON generator for stdout */
* `draw_bars' is called, the error message text will be drawn on the bar in
* the space allocated for the statusline.
*/
-
-/* forward function declaration is needed to add __attribute__ mechanism which
- * helps the compiler understand we are defining a printf wrapper */
-static void set_statusline_error(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
-
-static void set_statusline_error(const char *format, ...) {
+__attribute__ ((format (printf, 1, 2))) static void set_statusline_error(const char *format, ...) {
clear_status_blocks();
char *message;
/*
* Helper function to read stdin
*
+ * Returns NULL on EOF.
+ *
*/
static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
int fd = watcher->fd;
exit(EXIT_FAILURE);
}
if (n == 0) {
- /* end of file, kill the watcher */
ELOG("stdin: received EOF\n");
- cleanup();
*ret_buffer_len = -1;
return NULL;
}
if (exit_status == 126)
set_statusline_error("status_command is not executable (exit %d)", exit_status);
else if (exit_status == 127)
- set_statusline_error("status_command not found (exit %d)", exit_status);
+ set_statusline_error("status_command not found or is missing a library dependency (exit %d)", exit_status);
else
set_statusline_error("status_command process exited unexpectedly (exit %d)", exit_status);
/*
* Start a child-process with the specified command and reroute stdin.
* We actually start a $SHELL to execute the command so we don't have to care
- * about arguments and such
+ * about arguments and such.
+ *
+ * If `command' is NULL, such as in the case when no `status_command' is given
+ * in the bar config, no child will be started.
*
*/
void start_child(char *command) {
+ if (command == NULL)
+ return;
+
/* Allocate a yajl parser which will be used to parse stdin. */
- memset(&callbacks, '\0', sizeof(yajl_callbacks));
- callbacks.yajl_map_key = stdin_map_key;
- callbacks.yajl_boolean = stdin_boolean;
- callbacks.yajl_string = stdin_string;
- callbacks.yajl_integer = stdin_integer;
- callbacks.yajl_start_array = stdin_start_array;
- callbacks.yajl_end_array = stdin_end_array;
- callbacks.yajl_start_map = stdin_start_map;
- callbacks.yajl_end_map = stdin_end_map;
+ static yajl_callbacks callbacks = {
+ .yajl_boolean = stdin_boolean,
+ .yajl_integer = stdin_integer,
+ .yajl_string = stdin_string,
+ .yajl_start_map = stdin_start_map,
+ .yajl_map_key = stdin_map_key,
+ .yajl_end_map = stdin_end_map,
+ .yajl_start_array = stdin_start_array,
+ .yajl_end_array = stdin_end_array,
+ };
#if YAJL_MAJOR < 2
yajl_parser_config parse_conf = { 0, 0 };
gen = yajl_gen_alloc(NULL);
#endif
- if (command != NULL) {
- int pipe_in[2]; /* pipe we read from */
- int pipe_out[2]; /* pipe we write to */
+ int pipe_in[2]; /* pipe we read from */
+ int pipe_out[2]; /* pipe we write to */
- if (pipe(pipe_in) == -1)
- err(EXIT_FAILURE, "pipe(pipe_in)");
- if (pipe(pipe_out) == -1)
- err(EXIT_FAILURE, "pipe(pipe_out)");
+ if (pipe(pipe_in) == -1)
+ err(EXIT_FAILURE, "pipe(pipe_in)");
+ if (pipe(pipe_out) == -1)
+ err(EXIT_FAILURE, "pipe(pipe_out)");
- child.pid = fork();
- switch (child.pid) {
- case -1:
- ELOG("Couldn't fork(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- case 0:
- /* Child-process. Reroute streams and start shell */
+ child.pid = fork();
+ switch (child.pid) {
+ case -1:
+ ELOG("Couldn't fork(): %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ case 0:
+ /* Child-process. Reroute streams and start shell */
- close(pipe_in[0]);
- close(pipe_out[1]);
+ close(pipe_in[0]);
+ close(pipe_out[1]);
- dup2(pipe_in[1], STDOUT_FILENO);
- dup2(pipe_out[0], STDIN_FILENO);
+ dup2(pipe_in[1], STDOUT_FILENO);
+ dup2(pipe_out[0], STDIN_FILENO);
- setpgid(child.pid, 0);
- execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char*) NULL);
- return;
- default:
- /* Parent-process. Reroute streams */
+ setpgid(child.pid, 0);
+ execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char*) NULL);
+ return;
+ default:
+ /* Parent-process. Reroute streams */
- close(pipe_in[1]);
- close(pipe_out[0]);
+ close(pipe_in[1]);
+ close(pipe_out[0]);
- dup2(pipe_in[0], STDIN_FILENO);
- child_stdin = pipe_out[1];
+ dup2(pipe_in[0], STDIN_FILENO);
+ child_stdin = pipe_out[1];
- break;
- }
+ break;
}
/* We set O_NONBLOCK because blocking is evil in event-driven software */
}
if (!strcmp(cur_key, "status_command")) {
- /* We cannot directly start the child here, because start_child() also
- * needs to be run when no command was specified (to setup stdin).
- * Therefore we save the command in 'config' and access it later in
- * got_bar_config() */
DLOG("command = %.*s\n", len, val);
sasprintf(&config.command, "%.*s", len, val);
return 1;
/* A datastructure to pass all these callbacks to yajl */
static yajl_callbacks outputs_callbacks = {
- &config_null_cb,
- &config_boolean_cb,
- NULL,
- NULL,
- NULL,
- &config_string_cb,
- NULL,
- &config_map_key_cb,
- NULL,
- NULL,
- NULL
+ .yajl_null = config_null_cb,
+ .yajl_boolean = config_boolean_cb,
+ .yajl_string = config_string_cb,
+ .yajl_map_key = config_map_key_cb,
};
/*
/* Resolve color strings to colorpixels and save them, then free the strings. */
init_colors(&(config.colors));
- /* The name of this function is actually misleading. Even if no command is
- * specified, this function initiates the watchers to listen on stdin and
- * react accordingly */
start_child(config.command);
FREE(config.command);
}
/* update the configuration with the received settings */
DLOG("Received bar config update \"%s\"\n", event);
- int old_mode = config.hide_on_modifier;
+ bar_display_mode_t old_mode = config.hide_on_modifier;
parse_config_json(event);
if (old_mode != config.hide_on_modifier) {
reconfig_windows(true);
socket_path = expand_path(optarg);
break;
case 'v':
- printf("i3bar version " I3_VERSION " © 2010-2011 Axel Wagner and contributors\n");
+ printf("i3bar version " I3_VERSION " © 2010-2014 Axel Wagner and contributors\n");
exit(EXIT_SUCCESS);
break;
case 'b':
}
/* A datastructure to pass all these callbacks to yajl */
-yajl_callbacks mode_callbacks = {
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- &mode_string_cb,
- NULL,
- &mode_map_key_cb,
- NULL,
- NULL,
- NULL
+static yajl_callbacks mode_callbacks = {
+ .yajl_string = mode_string_cb,
+ .yajl_map_key = mode_map_key_cb,
};
/*
}
/* A datastructure to pass all these callbacks to yajl */
-yajl_callbacks outputs_callbacks = {
- &outputs_null_cb,
- &outputs_boolean_cb,
- &outputs_integer_cb,
- NULL,
- NULL,
- &outputs_string_cb,
- &outputs_start_map_cb,
- &outputs_map_key_cb,
- &outputs_end_map_cb,
- NULL,
- NULL
+static yajl_callbacks outputs_callbacks = {
+ .yajl_null = outputs_null_cb,
+ .yajl_boolean = outputs_boolean_cb,
+ .yajl_integer = outputs_integer_cb,
+ .yajl_string = outputs_string_cb,
+ .yajl_start_map = outputs_start_map_cb,
+ .yajl_map_key = outputs_map_key_cb,
+ .yajl_end_map = outputs_end_map_cb,
};
/*
return 1;
}
-static yajl_callbacks version_callbacks = {
- NULL, /* null */
- &header_boolean, /* boolean */
- &header_integer,
- NULL, /* double */
- NULL, /* number */
- NULL, /* string */
- NULL, /* start_map */
- &header_map_key,
- NULL, /* end_map */
- NULL, /* start_array */
- NULL /* end_array */
-};
-
static void child_init(i3bar_child *child) {
child->version = 0;
child->stop_signal = SIGSTOP;
*
*/
void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed) {
+ static yajl_callbacks version_callbacks = {
+ .yajl_boolean = header_boolean,
+ .yajl_integer = header_integer,
+ .yajl_map_key = &header_map_key,
+ };
+
child_init(child);
current_key = NO_KEY;
}
/* A datastructure to pass all these callbacks to yajl */
-yajl_callbacks workspaces_callbacks = {
- NULL,
- &workspaces_boolean_cb,
- &workspaces_integer_cb,
- NULL,
- NULL,
- &workspaces_string_cb,
- &workspaces_start_map_cb,
- &workspaces_map_key_cb,
- NULL,
- NULL,
- NULL
+static yajl_callbacks workspaces_callbacks = {
+ .yajl_boolean = workspaces_boolean_cb,
+ .yajl_integer = workspaces_integer_cb,
+ .yajl_string = workspaces_string_cb,
+ .yajl_start_map = workspaces_start_map_cb,
+ .yajl_map_key = workspaces_map_key_cb,
};
/*
const size_t len = namelen + strlen("workspace \"\"") + 1;
char *buffer = scalloc(len+num_quotes);
strncpy(buffer, "workspace \"", strlen("workspace \""));
- int inpos, outpos;
+ size_t inpos, outpos;
for (inpos = 0, outpos = strlen("workspace \"");
inpos < namelen;
inpos++, outpos++) {
uint32_t top_end_x;
uint32_t bottom_start_x;
uint32_t bottom_end_x;
- } __attribute__((__packed__)) strut_partial = {0,};
+ } __attribute__((__packed__)) strut_partial;
+ memset(&strut_partial, 0, sizeof(strut_partial));
+
switch (config.position) {
case POS_NONE:
break;
outputs_walk->bargc,
MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0,
MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3,
- MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height + 2);
+ MIN(outputs_walk->rect.w - traypx - 4, (int)statusline_width), font.height + 2);
}
if (!config.disable_ws) {
#include "scratchpad.h"
#include "commands.h"
#include "commands_parser.h"
+#include "bindings.h"
#include "config_directives.h"
#include "config_parser.h"
#include "fake_outputs.h"
#include "display_version.h"
+#include "restore_layout.h"
+#include "main.h"
#endif
* assignments.c: Assignments for specific windows (for_window).
*
*/
-#ifndef I3_ASSIGNMENTS_H
-#define I3_ASSIGNMENTS_H
+#pragma once
/**
* Checks the list of assignments for the given window and runs all matching
*
*/
Assignment *assignment_for(i3Window *window, int type);
-
-#endif
xmacro(_NET_WM_NAME)
xmacro(_NET_WM_STATE_FULLSCREEN)
xmacro(_NET_WM_STATE_DEMANDS_ATTENTION)
+xmacro(_NET_WM_STATE_MODAL)
xmacro(_NET_WM_STATE)
xmacro(_NET_WM_WINDOW_TYPE)
xmacro(_NET_WM_WINDOW_TYPE_DOCK)
xmacro(I3_PID)
xmacro(_NET_REQUEST_FRAME_EXTENTS)
xmacro(_NET_FRAME_EXTENTS)
+xmacro(_MOTIF_WM_HINTS)
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2014 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * bindings.h: Functions for configuring, finding, and running bindings.
+ *
+ */
+#pragma once
+
+/**
+ * The name of the default mode.
+ *
+ */
+const char *DEFAULT_BINDING_MODE;
+
+/**
+ * Adds a binding from config parameters given as strings and returns a
+ * pointer to the binding structure. Returns NULL if the input code could not
+ * be parsed.
+ *
+ */
+Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
+ const char *release, const char *command, const char *mode);
+
+/**
+ * Grab the bound keys (tell X to send us keypress events for those keycodes)
+ *
+ */
+void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
+
+/**
+ * Returns a pointer to the keyboard Binding with the specified modifiers and
+ * keycode or NULL if no such binding exists.
+ *
+ */
+Binding *get_keyboard_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode);
+
+/**
+ * Translates keysymbols to keycodes for all bindings which use keysyms.
+ *
+ */
+void translate_keysyms(void);
* click.c: Button press (mouse click) events.
*
*/
-#ifndef I3_CLICK_H
-#define I3_CLICK_H
+#pragma once
/**
* The button press X callback. This function determines whether the floating
*
*/
int handle_button_press(xcb_button_press_event_t *event);
-
-#endif
* cmdparse.y: the parser for commands you send to i3 (or bind on keys)
*
*/
-#ifndef I3_CMDPARSE_H
-#define I3_CMDPARSE_H
+#pragma once
char *parse_cmd(const char *new);
-
-#endif
* commands.c: all command functions (see commands_parser.c)
*
*/
-#ifndef I3_COMMANDS_H
-#define I3_COMMANDS_H
+#pragma once
#include "commands_parser.h"
*
*/
void cmd_debuglog(I3_CMD, char *argument);
-
-#endif
* commands.c: all command functions (see commands_parser.c)
*
*/
-#ifndef I3_COMMANDS_PARSER_H
-#define I3_COMMANDS_PARSER_H
+#pragma once
#include <yajl/yajl_gen.h>
};
struct CommandResult *parse_command(const char *input);
-
-#endif
* …).
*
*/
-#ifndef I3_CON_H
-#define I3_CON_H
+#pragma once
/**
* Create a new container (and attach it to the given parent, if not NULL).
* Returns the first fullscreen node below this node.
*
*/
-Con *con_get_fullscreen_con(Con *con, int fullscreen_mode);
+Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode);
/**
* Returns true if the container is internal, such as __i3_scratch
* container).
*
*/
-int con_orientation(Con *con);
+orientation_t con_orientation(Con *con);
/**
* Returns the container which will be focused next when the given container
*
*/
char *con_get_tree_representation(Con *con);
-
-#endif
* bindings mode).
*
*/
-#ifndef I3_CONFIG_H
-#define I3_CONFIG_H
+#pragma once
#include <stdbool.h>
#include "queue.h"
struct Colortriple focused_inactive;
struct Colortriple unfocused;
struct Colortriple urgent;
+ struct Colortriple placeholder;
} client;
struct config_bar {
struct Colortriple focused;
*/
void load_configuration(xcb_connection_t *conn, const char *override_configfile, bool reload);
-/**
- * Translates keysymbols to keycodes for all bindings which use keysyms.
- *
- */
-void translate_keysyms(void);
-
/**
* Ungrabs all keys, to be called before re-grabbing the keys because of a
* mapping_notify event or a configuration file reload
*/
void ungrab_all_keys(xcb_connection_t *conn);
-/**
- * Grab the bound keys (tell X to send us keypress events for those keycodes)
- *
- */
-void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
-
/**
* Switches the key bindings to the given mode, if the mode exists
*
*
*/void update_barconfig();
-/**
- * Returns a pointer to the Binding with the specified modifiers and keycode
- * or NULL if no such binding exists.
- *
- */
-Binding *get_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode);
-
/**
* Kills the configerror i3-nagbar process, if any.
*
*
*/
void kill_configerror_nagbar(bool wait_for_it);
-
-#endif
* config_directives.h: all config storing functions (see config_parser.c)
*
*/
-#ifndef I3_CONFIG_DIRECTIVES_H
-#define I3_CONFIG_DIRECTIVES_H
+#pragma once
#include "config_parser.h"
+/**
+ * A utility function to convert a string of modifiers to the corresponding bit
+ * mask.
+ */
+uint32_t modifiers_from_str(const char *str);
+
/** The beginning of the prototype for every cfg_ function. */
#define I3_CFG Match *current_match, struct ConfigResult *result
CFGFUN(bar_binding_mode_indicator, const char *value);
CFGFUN(bar_workspace_buttons, const char *value);
CFGFUN(bar_finish);
-
-#endif
* config_parser.h: config parser-related definitions
*
*/
-#ifndef I3_CONFIG_PARSER_H
-#define I3_CONFIG_PARSER_H
+#pragma once
#include <yajl/yajl_gen.h>
*
*/
void parse_file(const char *f);
-
-#endif
* include/data.h: This file defines all data structures used by i3
*
*/
-#ifndef I3_DATA_H
-#define I3_DATA_H
+#pragma once
#define SN_API_NOT_YET_FROZEN 1
#include <libsn/sn-launcher.h>
*
*/
struct Binding {
+ /* The type of input this binding is for. (Mouse bindings are not yet
+ * implemented. All bindings are currently assumed to be keyboard bindings.) */
+ enum {
+ /* Created with "bindsym", "bindcode", and "bind" */
+ B_KEYBOARD = 0,
+ /* Created with "bindmouse" (not yet implemented). */
+ B_MOUSE = 1,
+ } input_type;
+
/** If true, the binding should be executed upon a KeyRelease event, not a
* KeyPress (the default). */
enum {
struct regex *class;
struct regex *instance;
struct regex *mark;
- struct regex *role;
+ struct regex *window_role;
enum {
U_DONTCHECK = -1,
U_LATEST = 0,
TAILQ_ENTRY(Assignment) assignments;
};
+/** Fullscreen modes. Used by Con.fullscreen_mode. */
+typedef enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode_t;
+
/**
* A 'Con' represents everything from the X11 root window down to a single X11 window.
*
TAILQ_HEAD(swallow_head, Match) swallow_head;
- enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode;
+ fullscreen_mode_t fullscreen_mode;
/* layout is the layout of this container: one of split[v|h], stacked or
* tabbed. Special containers in the tree (above workspaces) have special
* layouts like dockarea or output.
/* Depth of the container window */
uint16_t depth;
};
-
-#endif
* events. This code is from xcb-util.
*
*/
-#ifndef I3_DEBUG_H
-#define I3_DEBUG_H
+#pragma once
int handle_event(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e);
-
-#endif
* display_version.c: displays the running i3 version, runs as part of
* i3 --moreversion.
*/
-#ifndef I3_DISPLAY_VERSION_H
-#define I3_DISPLAY_VERSION_H
+#pragma once
/**
* Connects to i3 to find out the currently running version. Useful since it
*
*/
void display_running_version(void);
-
-#endif
* ewmh.c: Get/set certain EWMH properties easily.
*
*/
-#ifndef I3_EWMH_C
-#define I3_EWMH_C
+#pragma once
/**
* Updates _NET_CURRENT_DESKTOP with the current desktop number.
*
*/
void ewmh_update_workarea(void);
-
-#endif
* which don’t support multi-monitor in a useful way) and for our testsuite.
*
*/
-#ifndef I3_FAKE_OUTPUTS_H
-#define I3_FAKE_OUTPUTS_H
+#pragma once
/**
* Creates outputs according to the given specification.
*
*/
void fake_outputs_init(const char *output_spec);
-
-#endif
* floating.c: Floating windows.
*
*/
-#ifndef I3_FLOATING_H
-#define I3_FLOATING_H
+#pragma once
#include "tree.h"
*
*/
void floating_fix_coordinates(Con *con, Rect *old_rect, Rect *new_rect);
-
-#endif
* …).
*
*/
-#ifndef I3_HANDLERS_H
-#define I3_HANDLERS_H
+#pragma once
#include <xcb/randr.h>
xcb_window_t window, xcb_atom_t atom,
xcb_get_property_reply_t *property);
#endif
-
-#endif
* i3.h: global variables that are used all over i3.
*
*/
-#ifndef I3_I3_H
-#define I3_I3_H
+#pragma once
#include <sys/time.h>
#include <sys/resource.h>
extern xcb_window_t root;
extern struct ev_loop *main_loop;
extern bool only_check_config;
-
-#endif
* for the IPC interface to i3 (see docs/ipc for more information).
*
*/
-#ifndef I3_I3_IPC_H
-#define I3_I3_IPC_H
+#pragma once
#include <stdint.h>
/** Bar config update will be triggered to update the bar config */
#define I3_IPC_EVENT_BARCONFIG_UPDATE (I3_IPC_EVENT_MASK | 4)
-
-#endif
* ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).
*
*/
-#ifndef I3_IPC_H
-#define I3_IPC_H
+#pragma once
#include <ev.h>
#include <stdbool.h>
*/
void ipc_send_workspace_focus_event(Con *current, Con *old);
-#endif
+/**
+ * For the window events we send, along the usual "change" field,
+ * also the window container, in "container".
+ */
+void ipc_send_window_event(const char *property, Con *con);
* key_press.c: key press handler
*
*/
-#ifndef I3_KEY_PRESS_H
-#define I3_KEY_PRESS_H
+#pragma once
extern pid_t command_error_nagbar_pid;
*
*/
void kill_commanderror_nagbar(bool wait_for_it);
-
-#endif
* as i3-msg, i3-config-wizard, …
*
*/
-#ifndef I3_LIBI3_H
-#define I3_LIBI3_H
+#pragma once
#include <stdbool.h>
#include <stdarg.h>
* Returned value must be freed by the caller.
*/
char *get_exe_path(const char *argv0);
-
-#endif
* restart.
*
*/
-#ifndef I3_LOAD_LAYOUT_H
-#define I3_LOAD_LAYOUT_H
+#pragma once
-void tree_append_json(const char *filename);
-
-#endif
+void tree_append_json(const char *filename, char **errormsg);
* log.c: Logging functions.
*
*/
-#ifndef I3_LOG_H
-#define I3_LOG_H
+#pragma once
#include <stdarg.h>
#include <stdbool.h>
* failures. This function is invoked automatically when exiting.
*/
void purge_zerobyte_logfile(void);
-
-#endif
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * main.c: Initialization, main loop
+ *
+ */
+#pragma once
+
+/**
+ * Enable or disable the main X11 event handling function.
+ * This is used by drag_pointer() which has its own, modal event handler, which
+ * takes precedence over the normal event handler.
+ *
+ */
+void main_set_x11_cb(bool enable);
* manage.c: Initially managing new windows (or existing ones on restart).
*
*/
-#ifndef I3_MANAGE_H
-#define I3_MANAGE_H
+#pragma once
#include "data.h"
uint32_t border_width);
#endif
-#endif
* match_matches_window() to find the windows affected by this command.
*
*/
-#ifndef I3_MATCH_H
-#define I3_MATCH_H
+#pragma once
/*
* Initializes the Match data structure. This function is necessary because the
*
*/
void match_free(Match *match);
-
-#endif
* move.c: Moving containers into some direction.
*
*/
-#ifndef I3_MOVE_H
-#define I3_MOVE_H
+#pragma once
/**
* Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT,
*
*/
void tree_move(int direction);
-
-#endif
* output.c: Output (monitor) related functions.
*
*/
-#ifndef I3_OUTPUT_H
-#define I3_OUTPUT_H
+#pragma once
/**
* Returns the output container below the given output container.
*
*/
Con *output_get_content(Con *output);
-
-#endif
* @(#)queue.h 8.5 (Berkeley) 8/20/94
*/
-#ifndef _SYS_QUEUE_H_
-#define _SYS_QUEUE_H_
+#pragma once
/*
* This file defines five types of data structures: singly-linked lists,
_Q_INVALIDATE((elm)->field.cqe_prev); \
_Q_INVALIDATE((elm)->field.cqe_next); \
} while (0)
-
-#endif /* !_SYS_QUEUE_H_ */
* (take your time to read it completely, it answers all questions).
*
*/
-#ifndef I3_RANDR_H
-#define I3_RANDR_H
+#pragma once
#include "data.h"
#include <xcb/randr.h>
* if there is no output which contains these coordinates.
*
*/
-Output *get_output_containing(int x, int y);
+Output *get_output_containing(unsigned int x, unsigned int y);
/*
* In contained_by_output, we check if any active output contains part of the container.
*
*/
Output *get_output_next_wrap(direction_t direction, Output *current);
-
-#endif
* regex.c: Interface to libPCRE (perl compatible regular expressions).
*
*/
-#ifndef I3_REGEX_H
-#define I3_REGEX_H
+#pragma once
/**
* Creates a new 'regex' struct containing the given pattern and a PCRE
*
*/
bool regex_matches(struct regex *regex, const char *input);
-
-#endif
* various rects. Needs to be pushed to X11 (see x.c) to be visible.
*
*/
-#ifndef I3_RENDER_H
-#define I3_RENDER_H
+#pragma once
/**
* "Renders" the given container (and its children), meaning that all rects are
* Returns the height for the decorations
*/
int render_deco_height(void);
-
-#endif
* resize.c: Interactive resizing.
*
*/
-#ifndef I3_RESIZE_H
-#define I3_RESIZE_H
+#pragma once
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction);
int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
-
-#endif
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * restore_layout.c: Everything for restored containers that is not pure state
+ * parsing (which can be found in load_layout.c).
+ *
+ */
+#pragma once
+
+/**
+ * Opens a separate connection to X11 for placeholder windows when restoring
+ * layouts. This is done as a safety measure (users can xkill a placeholder
+ * window without killing their window manager) and for better isolation, both
+ * on the wire to X11 and thus also in the code.
+ *
+ */
+void restore_connect(void);
+
+/**
+ * Open placeholder windows for all children of parent. The placeholder window
+ * will vanish as soon as a real window is swallowed by the container. Until
+ * then, it exposes the criteria that must be fulfilled for a window to be
+ * swallowed by this container.
+ *
+ */
+void restore_open_placeholder_windows(Con *con);
+
+/**
+ * Kill the placeholder window, if placeholder refers to a placeholder window.
+ * This function is called when manage.c puts a window into an existing
+ * container. In order not to leak resources, we need to destroy the window and
+ * all associated X11 objects (pixmap/gc).
+ *
+ */
+bool restore_kill_placeholder(xcb_window_t placeholder);
* scratchpad.c: Scratchpad functions (TODO: more description)
*
*/
-#ifndef I3_SCRATCHPAD_H
-#define I3_SCRATCHPAD_H
+#pragma once
/**
* Moves the specified window to the __i3_scratch workspace, making it floating
*
*/
void scratchpad_fix_resolution(void);
-
-#endif
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-#ifndef foosddaemonhfoo
-#define foosddaemonhfoo
+#pragma once
/***
Copyright 2010 Lennart Poettering
#ifdef __cplusplus
}
#endif
-
-#endif
* default (ringbuffer for storing the debug log).
*
*/
-#ifndef I3_I3_SHMLOG_H
-#define I3_I3_SHMLOG_H
+#pragma once
#include <stdint.h>
#include <pthread.h>
* tail -f) in an efficient way. */
pthread_cond_t condvar;
} i3_shmlog_header;
-
-#endif
* to restart inplace).
*
*/
-#ifndef I3_SIGHANDLER_H
-#define I3_SIGHANDLER_H
+#pragma once
/**
* Setup signal handlers to safely handle SIGSEGV and SIGFPE
*
*/
void setup_signal_handler(void);
-
-#endif
* the appropriate workspace.
*
*/
-#ifndef I3_STARTUP_H
-#define I3_STARTUP_H
+#pragma once
#define SN_API_NOT_YET_FROZEN 1
#include <libsn/sn-monitor.h>
*
*/
char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply);
-
-#endif
* tree.c: Everything that primarily modifies the layout tree data structure.
*
*/
-#ifndef I3_TREE_H
-#define I3_TREE_H
+#pragma once
extern Con *croot;
/* TODO: i am not sure yet how much access to the focused container should
*
*/
void tree_flatten(Con *child);
-
-#endif
* also libi3).
*
*/
-#ifndef I3_UTIL_H
-#define I3_UTIL_H
+#pragma once
#include <err.h>
*
*/
void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it);
-
-#endif
* window.c: Updates window attributes (X11 hints/properties).
*
*/
-#ifndef I3_WINDOW_H
-#define I3_WINDOW_H
+#pragma once
/**
* Updates the WM_CLASS (consisting of the class and instance) for the
*/
void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *urgency_hint);
-#endif
+/**
+ * Updates the MOTIF_WM_HINTS. The container's border style should be set to
+ * `motif_border_style' if border style is not BS_NORMAL.
+ *
+ * i3 only uses this hint when it specifies a window should have no
+ * title bar, or no decorations at all, which is how most window managers
+ * handle it.
+ *
+ * The EWMH spec intended to replace Motif hints with _NET_WM_WINDOW_TYPE, but
+ * it is still in use by popular widget toolkits such as GTK+ and Java AWT.
+ *
+ */
+void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
* workspaces.
*
*/
-#ifndef I3_WORKSPACE_H
-#define I3_WORKSPACE_H
+#pragma once
#include "data.h"
#include "tree.h"
* The container inherits the layout from the workspace.
*/
Con *workspace_encapsulate(Con *ws);
-#endif
* render.c). Basically a big state machine.
*
*/
-#ifndef I3_X_H
-#define I3_X_H
+#pragma once
/** Stores the X11 window ID of the currently focused window */
extern xcb_window_t focused_id;
*
*/
void x_mask_event_mask(uint32_t mask);
-
-#endif
* xcb.c: Helper functions for easier usage of XCB
*
*/
-#ifndef I3_XCB_H
-#define I3_XCB_H
+#pragma once
#include "data.h"
#include "xcursor.h"
*
*/
xcb_visualid_t get_visualid_by_depth(uint16_t depth);
-
-#endif
* older versions.
*
*/
-#ifndef I3_XCB_COMPAT_H
-#define I3_XCB_COMPAT_H
+#pragma once
#define xcb_icccm_get_wm_protocols_reply_t xcb_get_wm_protocols_reply_t
#define xcb_icccm_get_wm_protocols xcb_get_wm_protocols
#define XCB_ICCCM_WM_STATE_NORMAL XCB_WM_STATE_NORMAL
#define XCB_ICCCM_WM_STATE_WITHDRAWN XCB_WM_STATE_WITHDRAWN
#define xcb_icccm_get_wm_size_hints_from_reply xcb_get_wm_size_hints_from_reply
+#define xcb_icccm_get_wm_size_hints_reply xcb_get_wm_size_hints_reply
+#define xcb_icccm_get_wm_normal_hints xcb_get_wm_normal_hints
#define xcb_icccm_get_wm_normal_hints_reply xcb_get_wm_normal_hints_reply
#define xcb_icccm_get_wm_normal_hints_unchecked xcb_get_wm_normal_hints_unchecked
#define XCB_ICCCM_SIZE_HINT_P_MIN_SIZE XCB_SIZE_HINT_P_MIN_SIZE
+#define XCB_ICCCM_SIZE_HINT_P_MAX_SIZE XCB_SIZE_HINT_P_MAX_SIZE
#define XCB_ICCCM_SIZE_HINT_P_RESIZE_INC XCB_SIZE_HINT_P_RESIZE_INC
#define XCB_ICCCM_SIZE_HINT_BASE_SIZE XCB_SIZE_HINT_BASE_SIZE
#define XCB_ICCCM_SIZE_HINT_P_ASPECT XCB_SIZE_HINT_P_ASPECT
#define XCB_ATOM_ATOM ATOM
#define XCB_ATOM_WM_NORMAL_HINTS WM_NORMAL_HINTS
#define XCB_ATOM_STRING STRING
-
-#endif
* xcursor.c: libXcursor support for themed cursors.
*
*/
-#ifndef I3_XCURSOR_CURSOR_H
-#define I3_XCURSOR_CURSOR_H
+#pragma once
#include <xcb/xcb_cursor.h>
*
*/
void xcursor_set_root_cursor(int cursor_id);
-
-#endif
* driver which does not support RandR in 2011 *sigh*.
*
*/
-#ifndef I3_XINERAMA_H
-#define I3_XINERAMA_H
+#pragma once
#include "data.h"
*
*/
void xinerama_init(void);
-
-#endif
* yajl_utils.h
*
*/
-#ifndef I3_YAJL_UTILS_H
-#define I3_YAJL_UTILS_H
+#pragma once
#include <yajl/yajl_gen.h>
#include <yajl/yajl_parse.h>
#define yalloc(callbacks, client) yajl_alloc(callbacks, NULL, NULL, client)
typedef unsigned int ylength;
#endif
-
-#endif
#endif
ssize_t linksize;
- while ((linksize = readlink(exepath, destpath, destpath_size)) == destpath_size) {
+ while ((linksize = readlink(exepath, destpath, destpath_size)) == (ssize_t)destpath_size) {
destpath_size = destpath_size * 2;
destpath = srealloc(destpath, destpath_size);
}
.type = message_type
};
- int sent_bytes = 0;
+ size_t sent_bytes = 0;
int n = 0;
/* This first loop is basically unnecessary. No operating system has
-------------------------------------------------------------
# i3 config file (v4)
-# font for window titles. ISO 10646 = Unicode
+# font for window titles. also used by the bar (unless a different font is
+# used in the bar {} block. ISO 10646 = Unicode
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# use Mouse+Mod1 to drag floating windows to their wanted position
continue;
bool skip = false;
- for (int c = 0; c < window->nr_assignments; c++) {
+ for (uint32_t c = 0; c < window->nr_assignments; c++) {
if (window->ran_assignments[c] != current)
continue;
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2014 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * bindings.c: Functions for configuring, finding and, running bindings.
+ */
+#include "all.h"
+
+/*
+ * The name of the default mode.
+ *
+ */
+const char *DEFAULT_BINDING_MODE = "default";
+
+/*
+ * Returns the mode specified by `name` or creates a new mode and adds it to
+ * the list of modes.
+ *
+ */
+static struct Mode *mode_from_name(const char *name) {
+ struct Mode *mode;
+
+ /* Try to find the mode in the list of modes and return it */
+ SLIST_FOREACH(mode, &modes, modes) {
+ if (strcmp(mode->name, name) == 0)
+ return mode;
+ }
+
+ /* If the mode was not found, create a new one */
+ mode = scalloc(sizeof(struct Mode));
+ mode->name = sstrdup(name);
+ mode->bindings = scalloc(sizeof(struct bindings_head));
+ TAILQ_INIT(mode->bindings);
+ SLIST_INSERT_HEAD(&modes, mode, modes);
+
+ return mode;
+}
+
+/*
+ * Adds a binding from config parameters given as strings and returns a
+ * pointer to the binding structure. Returns NULL if the input code could not
+ * be parsed.
+ *
+ */
+Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
+ const char *release, const char *command, const char *modename) {
+ Binding *new_binding = scalloc(sizeof(Binding));
+ DLOG("bindtype %s, modifiers %s, input code %s, release %s\n", bindtype, modifiers, input_code, release);
+ new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
+ if (strcmp(bindtype, "bindsym") == 0) {
+ new_binding->symbol = sstrdup(input_code);
+ } else {
+ // TODO: strtol with proper error handling
+ new_binding->keycode = atoi(input_code);
+ if (new_binding->keycode == 0) {
+ ELOG("Could not parse \"%s\" as an input code, ignoring this binding.\n", input_code);
+ FREE(new_binding);
+ return NULL;
+ }
+ }
+ new_binding->mods = modifiers_from_str(modifiers);
+ new_binding->command = sstrdup(command);
+ new_binding->input_type = B_KEYBOARD;
+
+ struct Mode *mode = mode_from_name(modename);
+ TAILQ_INSERT_TAIL(mode->bindings, new_binding, bindings);
+
+ return new_binding;
+}
+
+static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) {
+ if (bind->input_type != B_KEYBOARD)
+ return;
+
+ DLOG("Grabbing %d with modifiers %d (with mod_mask_lock %d)\n", keycode, bind->mods, bind->mods | XCB_MOD_MASK_LOCK);
+ /* Grab the key in all combinations */
+ #define GRAB_KEY(modifier) \
+ do { \
+ xcb_grab_key(conn, 0, root, modifier, keycode, \
+ XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
+ } while (0)
+ int mods = bind->mods;
+ if ((bind->mods & BIND_MODE_SWITCH) != 0) {
+ mods &= ~BIND_MODE_SWITCH;
+ if (mods == 0)
+ mods = XCB_MOD_MASK_ANY;
+ }
+ GRAB_KEY(mods);
+ GRAB_KEY(mods | xcb_numlock_mask);
+ GRAB_KEY(mods | XCB_MOD_MASK_LOCK);
+ GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
+}
+
+
+/*
+ * Grab the bound keys (tell X to send us keypress events for those keycodes)
+ *
+ */
+void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
+ Binding *bind;
+ TAILQ_FOREACH(bind, bindings, bindings) {
+ if (bind->input_type != B_KEYBOARD ||
+ (bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) ||
+ (!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0))
+ continue;
+
+ /* The easy case: the user specified a keycode directly. */
+ if (bind->keycode > 0) {
+ grab_keycode_for_binding(conn, bind, bind->keycode);
+ continue;
+ }
+
+ xcb_keycode_t *walk = bind->translated_to;
+ for (uint32_t i = 0; i < bind->number_keycodes; i++)
+ grab_keycode_for_binding(conn, bind, *walk++);
+ }
+}
+
+/*
+ * Returns a pointer to the keyboard Binding with the specified modifiers and
+ * keycode or NULL if no such binding exists.
+ *
+ */
+Binding *get_keyboard_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode) {
+ Binding *bind;
+
+ if (!key_release) {
+ /* On a KeyPress event, we first reset all
+ * B_UPON_KEYRELEASE_IGNORE_MODS bindings back to B_UPON_KEYRELEASE */
+ TAILQ_FOREACH(bind, bindings, bindings) {
+ if (bind->input_type != B_KEYBOARD)
+ continue;
+ if (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS)
+ bind->release = B_UPON_KEYRELEASE;
+ }
+ }
+
+ TAILQ_FOREACH(bind, bindings, bindings) {
+ /* First compare the modifiers (unless this is a
+ * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease
+ * event) */
+ if (bind->input_type != B_KEYBOARD)
+ continue;
+ if (bind->mods != modifiers &&
+ (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS ||
+ !key_release))
+ continue;
+
+ /* If a symbol was specified by the user, we need to look in
+ * the array of translated keycodes for the event’s keycode */
+ if (bind->symbol != NULL) {
+ if (memmem(bind->translated_to,
+ bind->number_keycodes * sizeof(xcb_keycode_t),
+ &keycode, sizeof(xcb_keycode_t)) == NULL)
+ continue;
+ } else {
+ /* This case is easier: The user specified a keycode */
+ if (bind->keycode != keycode)
+ continue;
+ }
+
+ /* If this keybinding is a KeyRelease binding, it matches the key which
+ * the user pressed. We therefore mark it as
+ * B_UPON_KEYRELEASE_IGNORE_MODS for later, so that the user can
+ * release the modifiers before the actual key and the KeyRelease will
+ * still be matched. */
+ if (bind->release == B_UPON_KEYRELEASE && !key_release)
+ bind->release = B_UPON_KEYRELEASE_IGNORE_MODS;
+
+ /* Check if the binding is for a KeyPress or a KeyRelease event */
+ if ((bind->release == B_UPON_KEYPRESS && key_release) ||
+ (bind->release >= B_UPON_KEYRELEASE && !key_release))
+ continue;
+
+ break;
+ }
+
+ return (bind == TAILQ_END(bindings) ? NULL : bind);
+}
+
+/*
+ * Translates keysymbols to keycodes for all bindings which use keysyms.
+ *
+ */
+void translate_keysyms(void) {
+ Binding *bind;
+ xcb_keysym_t keysym;
+ int col;
+ xcb_keycode_t i, min_keycode, max_keycode;
+
+ min_keycode = xcb_get_setup(conn)->min_keycode;
+ max_keycode = xcb_get_setup(conn)->max_keycode;
+
+ TAILQ_FOREACH(bind, bindings, bindings) {
+ if (bind->input_type != B_KEYBOARD || bind->keycode > 0)
+ continue;
+
+ /* We need to translate the symbol to a keycode */
+ keysym = XStringToKeysym(bind->symbol);
+ if (keysym == NoSymbol) {
+ ELOG("Could not translate string to key symbol: \"%s\"\n",
+ bind->symbol);
+ continue;
+ }
+
+ /* Base column we use for looking up key symbols. We always consider
+ * the base column and the corresponding shift column, so without
+ * mode_switch, we look in 0 and 1, with mode_switch we look in 2 and
+ * 3. */
+ col = (bind->mods & BIND_MODE_SWITCH ? 2 : 0);
+
+ FREE(bind->translated_to);
+ bind->number_keycodes = 0;
+
+ for (i = min_keycode; i && i <= max_keycode; i++) {
+ if ((xcb_key_symbols_get_keysym(keysyms, i, col) != keysym) &&
+ (xcb_key_symbols_get_keysym(keysyms, i, col+1) != keysym))
+ continue;
+ bind->number_keycodes++;
+ bind->translated_to = srealloc(bind->translated_to,
+ (sizeof(xcb_keycode_t) *
+ bind->number_keycodes));
+ bind->translated_to[bind->number_keycodes-1] = i;
+ }
+
+ DLOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol,
+ bind->number_keycodes);
+ }
+}
second = tmp;
}
- /* We modify the X/Y position in the event so that the divider line is at
- * the actual position of the border, not at the position of the click. */
const orientation_t orientation = ((border == BORDER_LEFT || border == BORDER_RIGHT) ? HORIZ : VERT);
- if (orientation == HORIZ)
- event->root_x = second->rect.x;
- else event->root_y = second->rect.y;
resize_graphical_handler(first, second, orientation, event);
return tiling_resize_for_border(con, BORDER_TOP, event);
}
- if (event->event_x >= 0 && event->event_x <= bsr.x &&
- event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height)
+ if (event->event_x >= 0 && event->event_x <= (int32_t)bsr.x &&
+ event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height))
return tiling_resize_for_border(con, BORDER_LEFT, event);
- if (event->event_x >= (con->window_rect.x + con->window_rect.width) &&
- event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height)
+ if (event->event_x >= (int32_t)(con->window_rect.x + con->window_rect.width) &&
+ event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height))
return tiling_resize_for_border(con, BORDER_RIGHT, event);
- if (event->event_y >= (con->window_rect.y + con->window_rect.height))
+ if (event->event_y >= (int32_t)(con->window_rect.y + con->window_rect.height))
return tiling_resize_for_border(con, BORDER_BOTTOM, event);
return false;
return 1;
}
- /* 5: resize (floating) if this was a click on the left/right/bottom
- * border. also try resizing (tiling) if it was a click on the top
- * border, but continue if that does not work */
+ /* 5: resize (floating) if this was a (left or right) click on the
+ * left/right/bottom border, or a right click on the decoration.
+ * also try resizing (tiling) if it was a click on the top */
if (mod_pressed && event->detail == 3) {
DLOG("floating resize due to floatingmodifier\n");
floating_resize_window(floatingcon, proportional, event);
goto done;
}
+ if (dest == CLICK_DECORATION && event->detail == 3) {
+ DLOG("floating resize due to decoration right click\n");
+ floating_resize_window(floatingcon, proportional, event);
+ return 1;
+ }
+
if (dest == CLICK_BORDER) {
DLOG("floating resize due to border click\n");
floating_resize_window(floatingcon, proportional, event);
*/
int handle_button_press(xcb_button_press_event_t *event) {
Con *con;
- DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event);
+ DLOG("Button %d pressed on window 0x%08x (child 0x%08x) at (%d, %d) (root %d, %d)\n",
+ event->state, event->event, event->child, event->event_x, event->event_y,
+ event->root_x, event->root_y);
last_timestamp = event->time;
return 0;
}
+ if (event->child != XCB_NONE) {
+ DLOG("event->child not XCB_NONE, so this is an event which originated from a click into the application, but the application did not handle it.\n");
+ return route_click(con, event, mod_pressed, CLICK_INSIDE);
+ }
+
/* Check if the click was on the decoration of a child */
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
}
if (strcmp(ctype, "window_role") == 0) {
- current_match->role = regex_new(cvalue);
+ current_match->window_role = regex_new(cvalue);
return;
}
*/
void cmd_append_layout(I3_CMD, char *path) {
LOG("Appending layout \"%s\"\n", path);
- tree_append_json(path);
+ Con *parent = focused;
+ char *errormsg = NULL;
+ tree_append_json(path, &errormsg);
+ if (errormsg != NULL) {
+ yerror(errormsg);
+ free(errormsg);
+ /* Note that we continue executing since tree_append_json() has
+ * side-effects — user-provided layouts can be partly valid, partly
+ * invalid, leading to half of the placeholder containers being
+ * created. */
+ } else {
+ ysuccess(true);
+ }
+
+ // XXX: This is a bit of a kludge. Theoretically, render_con(parent,
+ // false); should be enough, but when sending 'workspace 4; append_layout
+ // /tmp/foo.json', the needs_tree_render == true of the workspace command
+ // is not executed yet and will be batched with append_layout’s
+ // needs_tree_render after the parser finished. We should check if that is
+ // necessary at all.
+ render_con(croot, false);
+
+ restore_open_placeholder_windows(parent);
cmd_output->needs_tree_render = true;
- // XXX: default reply for now, make this a better reply
- ysuccess(true);
}
/*
/* The "<=" operator is intentional: We also handle the terminating 0-byte
* explicitly by looking for an 'end' token. */
- while ((walk - input) <= len) {
+ while ((size_t)(walk - input) <= len) {
/* skip whitespace and newlines before every token */
while ((*walk == ' ' || *walk == '\t' ||
*walk == '\r' || *walk == '\n') && *walk != '\0')
* Returns the first fullscreen node below this node.
*
*/
-Con *con_get_fullscreen_con(Con *con, int fullscreen_mode) {
+Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) {
Con *current, *child;
/* TODO: is breadth-first-search really appropriate? (check as soon as
* container).
*
*/
-int con_orientation(Con *con) {
+orientation_t con_orientation(Con *con) {
switch (con->layout) {
case L_SPLITV:
/* stacking containers behave like they are in vertical orientation */
xcb_ungrab_key(conn, XCB_GRAB_ANY, root, XCB_BUTTON_MASK_ANY);
}
-static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) {
- DLOG("Grabbing %d with modifiers %d (with mod_mask_lock %d)\n", keycode, bind->mods, bind->mods | XCB_MOD_MASK_LOCK);
- /* Grab the key in all combinations */
- #define GRAB_KEY(modifier) \
- do { \
- xcb_grab_key(conn, 0, root, modifier, keycode, \
- XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
- } while (0)
- int mods = bind->mods;
- if ((bind->mods & BIND_MODE_SWITCH) != 0) {
- mods &= ~BIND_MODE_SWITCH;
- if (mods == 0)
- mods = XCB_MOD_MASK_ANY;
- }
- GRAB_KEY(mods);
- GRAB_KEY(mods | xcb_numlock_mask);
- GRAB_KEY(mods | XCB_MOD_MASK_LOCK);
- GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
-}
-
-/*
- * Returns a pointer to the Binding with the specified modifiers and keycode
- * or NULL if no such binding exists.
- *
- */
-Binding *get_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode) {
- Binding *bind;
-
- if (!key_release) {
- /* On a KeyPress event, we first reset all
- * B_UPON_KEYRELEASE_IGNORE_MODS bindings back to B_UPON_KEYRELEASE */
- TAILQ_FOREACH(bind, bindings, bindings) {
- if (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS)
- bind->release = B_UPON_KEYRELEASE;
- }
- }
-
- TAILQ_FOREACH(bind, bindings, bindings) {
- /* First compare the modifiers (unless this is a
- * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease
- * event) */
- if (bind->mods != modifiers &&
- (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS ||
- !key_release))
- continue;
-
- /* If a symbol was specified by the user, we need to look in
- * the array of translated keycodes for the event’s keycode */
- if (bind->symbol != NULL) {
- if (memmem(bind->translated_to,
- bind->number_keycodes * sizeof(xcb_keycode_t),
- &keycode, sizeof(xcb_keycode_t)) == NULL)
- continue;
- } else {
- /* This case is easier: The user specified a keycode */
- if (bind->keycode != keycode)
- continue;
- }
-
- /* If this keybinding is a KeyRelease binding, it matches the key which
- * the user pressed. We therefore mark it as
- * B_UPON_KEYRELEASE_IGNORE_MODS for later, so that the user can
- * release the modifiers before the actual key and the KeyRelease will
- * still be matched. */
- if (bind->release == B_UPON_KEYRELEASE && !key_release)
- bind->release = B_UPON_KEYRELEASE_IGNORE_MODS;
-
- /* Check if the binding is for a KeyPress or a KeyRelease event */
- if ((bind->release == B_UPON_KEYPRESS && key_release) ||
- (bind->release >= B_UPON_KEYRELEASE && !key_release))
- continue;
-
- break;
- }
-
- return (bind == TAILQ_END(bindings) ? NULL : bind);
-}
-
-/*
- * Translates keysymbols to keycodes for all bindings which use keysyms.
- *
- */
-void translate_keysyms(void) {
- Binding *bind;
- xcb_keysym_t keysym;
- int col;
- xcb_keycode_t i,
- min_keycode = xcb_get_setup(conn)->min_keycode,
- max_keycode = xcb_get_setup(conn)->max_keycode;
-
- TAILQ_FOREACH(bind, bindings, bindings) {
- if (bind->keycode > 0)
- continue;
-
- /* We need to translate the symbol to a keycode */
- keysym = XStringToKeysym(bind->symbol);
- if (keysym == NoSymbol) {
- ELOG("Could not translate string to key symbol: \"%s\"\n",
- bind->symbol);
- continue;
- }
-
- /* Base column we use for looking up key symbols. We always consider
- * the base column and the corresponding shift column, so without
- * mode_switch, we look in 0 and 1, with mode_switch we look in 2 and
- * 3. */
- col = (bind->mods & BIND_MODE_SWITCH ? 2 : 0);
-
- FREE(bind->translated_to);
- bind->number_keycodes = 0;
-
- for (i = min_keycode; i && i <= max_keycode; i++) {
- if ((xcb_key_symbols_get_keysym(keysyms, i, col) != keysym) &&
- (xcb_key_symbols_get_keysym(keysyms, i, col+1) != keysym))
- continue;
- bind->number_keycodes++;
- bind->translated_to = srealloc(bind->translated_to,
- (sizeof(xcb_keycode_t) *
- bind->number_keycodes));
- bind->translated_to[bind->number_keycodes-1] = i;
- }
-
- DLOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol,
- bind->number_keycodes);
- }
-}
-
-/*
- * Grab the bound keys (tell X to send us keypress events for those keycodes)
- *
- */
-void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
- Binding *bind;
- TAILQ_FOREACH(bind, bindings, bindings) {
- if ((bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) ||
- (!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0))
- continue;
-
- /* The easy case: the user specified a keycode directly. */
- if (bind->keycode > 0) {
- grab_keycode_for_binding(conn, bind, bind->keycode);
- continue;
- }
-
- xcb_keycode_t *walk = bind->translated_to;
- for (int i = 0; i < bind->number_keycodes; i++)
- grab_keycode_for_binding(conn, bind, *walk++);
- }
-}
-
/*
* Switches the key bindings to the given mode, if the mode exists
*
INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888", "#292d2e");
INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff", "#900000");
+ /* border and indicator color are ignored for placeholder contents */
+ INIT_COLOR(config.client.placeholder, "#000000", "#0c0c0c", "#ffffff", "#000000");
+
/* the last argument (indicator color) is ignored for bar colors */
INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff", "#000000");
INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888", "#000000");
}
if (strcmp(ctype, "window_role") == 0) {
- current_match->role = regex_new(cvalue);
+ current_match->window_role = regex_new(cvalue);
return;
}
strcasecmp(str, "active") == 0);
}
-static uint32_t modifiers_from_str(const char *str) {
+/*
+ * A utility function to convert a string of modifiers to the corresponding bit
+ * mask.
+ */
+uint32_t modifiers_from_str(const char *str) {
/* It might be better to use strtok() here, but the simpler strstr() should
* do for now. */
uint32_t result = 0;
font_pattern = sstrdup(font);
}
-// TODO: refactor with mode_binding
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command) {
- Binding *new_binding = scalloc(sizeof(Binding));
- DLOG("bindtype %s, modifiers %s, key %s, release %s\n", bindtype, modifiers, key, release);
- new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
- if (strcmp(bindtype, "bindsym") == 0) {
- new_binding->symbol = sstrdup(key);
- } else {
- // TODO: strtol with proper error handling
- new_binding->keycode = atoi(key);
- if (new_binding->keycode == 0) {
- ELOG("Could not parse \"%s\" as a keycode, ignoring this binding.\n", key);
- return;
- }
- }
- new_binding->mods = modifiers_from_str(modifiers);
- new_binding->command = sstrdup(command);
- TAILQ_INSERT_TAIL(bindings, new_binding, bindings);
+ configure_binding(bindtype, modifiers, key, release, command, DEFAULT_BINDING_MODE);
}
* Mode handling
******************************************************************************/
-static struct bindings_head *current_bindings;
+static char *current_mode;
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command) {
- Binding *new_binding = scalloc(sizeof(Binding));
- DLOG("bindtype %s, modifiers %s, key %s, release %s\n", bindtype, modifiers, key, release);
- new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
- if (strcmp(bindtype, "bindsym") == 0) {
- new_binding->symbol = sstrdup(key);
- } else {
- // TODO: strtol with proper error handling
- new_binding->keycode = atoi(key);
- if (new_binding->keycode == 0) {
- ELOG("Could not parse \"%s\" as a keycode, ignoring this binding.\n", key);
- return;
- }
- }
- new_binding->mods = modifiers_from_str(modifiers);
- new_binding->command = sstrdup(command);
- TAILQ_INSERT_TAIL(current_bindings, new_binding, bindings);
+ configure_binding(bindtype, modifiers, key, release, command, current_mode);
}
CFGFUN(enter_mode, const char *modename) {
- if (strcasecmp(modename, "default") == 0) {
- ELOG("You cannot use the name \"default\" for your mode\n");
+ if (strcasecmp(modename, DEFAULT_BINDING_MODE) == 0) {
+ ELOG("You cannot use the name %s for your mode\n", DEFAULT_BINDING_MODE);
exit(1);
}
DLOG("\t now in mode %s\n", modename);
- struct Mode *mode = scalloc(sizeof(struct Mode));
- mode->name = sstrdup(modename);
- mode->bindings = scalloc(sizeof(struct bindings_head));
- TAILQ_INIT(mode->bindings);
- current_bindings = mode->bindings;
- SLIST_INSERT_HEAD(&modes, mode, modes);
+ FREE(current_mode);
+ current_mode = sstrdup(modename);
}
CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command) {
return NULL;
}
-static const long get_long(const char *identifier) {
+static long get_long(const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier == NULL)
break;
/* The "<=" operator is intentional: We also handle the terminating 0-byte
* explicitly by looking for an 'end' token. */
- while ((walk - input) <= len) {
+ while ((size_t)(walk - input) <= len) {
/* Skip whitespace before every token, newlines are relevant since they
* separate configuration directives. */
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
y(map_close);
/* Skip the rest of this line, but continue parsing. */
- while ((walk - input) <= len && *walk != '\n')
+ while ((size_t)(walk - input) <= len && *walk != '\n')
walk++;
free(position);
}
static yajl_callbacks version_callbacks = {
- NULL, /* null */
- NULL, /* boolean */
- NULL, /* integer */
- NULL, /* double */
- NULL, /* number */
- &version_string,
- NULL, /* start_map */
- &version_map_key,
- NULL, /* end_map */
- NULL, /* start_array */
- NULL /* end_array */
+ .yajl_string = version_string,
+ .yajl_map_key = version_map_key,
};
/*
sasprintf(&exepath, "/proc/%d/exe", getpid());
- while ((linksize = readlink(exepath, destpath, destpath_size)) == destpath_size) {
+ while ((linksize = readlink(exepath, destpath, destpath_size)) == (ssize_t)destpath_size) {
destpath_size = destpath_size * 2;
destpath = srealloc(destpath, destpath_size);
}
free(exepath);
sasprintf(&exepath, "/proc/%s/exe", pid_from_atom);
- while ((linksize = readlink(exepath, destpath, destpath_size)) == destpath_size) {
+ while ((linksize = readlink(exepath, destpath, destpath_size)) == (ssize_t)destpath_size) {
destpath_size = destpath_size * 2;
destpath = srealloc(destpath, destpath_size);
}
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
+ if (STARTS_WITH(ws->name, "__"))
+ continue;
+
if (ws == focused_ws) {
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
A__NET_CURRENT_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &idx);
* Looks in outputs for the Output whose start coordinates are x, y
*
*/
-static Output *get_screen_at(int x, int y) {
+static Output *get_screen_at(unsigned int x, unsigned int y) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->rect.x == x && output->rect.y == y)
* a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */
border_t corner = 0;
- if (event->event_x <= (con->rect.width / 2))
+ if (event->event_x <= (int16_t)(con->rect.width / 2))
corner |= BORDER_LEFT;
else corner |= BORDER_RIGHT;
int cursor = 0;
- if (event->event_y <= (con->rect.height / 2)) {
+ if (event->event_y <= (int16_t)(con->rect.height / 2)) {
corner |= BORDER_TOP;
cursor = (corner & BORDER_LEFT) ?
XCURSOR_CURSOR_TOP_LEFT_CORNER : XCURSOR_CURSOR_TOP_RIGHT_CORNER;
con->scratchpad_state = SCRATCHPAD_CHANGED;
}
+/* As endorsed by “ASSOCIATING CUSTOM DATA WITH A WATCHER” in ev(3) */
+struct drag_x11_cb {
+ ev_check check;
+
+ /* Whether this modal event loop should be exited and with which result. */
+ drag_result_t result;
+
+ /* The container that is being dragged or resized, or NULL if this is a
+ * drag of the resize handle. */
+ Con *con;
+
+ /* The dimensions of con when the loop was started. */
+ Rect old_rect;
+
+ /* The callback to invoke after every pointer movement. */
+ callback_t callback;
+
+ /* User data pointer for callback. */
+ const void *extra;
+};
+
+static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
+ struct drag_x11_cb *dragloop = (struct drag_x11_cb*)w;
+ xcb_motion_notify_event_t *last_motion_notify = NULL;
+ xcb_generic_event_t *event;
+
+ while ((event = xcb_poll_for_event(conn)) != NULL) {
+ if (event->response_type == 0) {
+ xcb_generic_error_t *error = (xcb_generic_error_t*)event;
+ DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n",
+ error->sequence, error->error_code);
+ free(event);
+ continue;
+ }
+
+ /* Strip off the highest bit (set if the event is generated) */
+ int type = (event->response_type & 0x7F);
+
+ switch (type) {
+ case XCB_BUTTON_RELEASE:
+ dragloop->result = DRAG_SUCCESS;
+ break;
+
+ case XCB_KEY_PRESS:
+ DLOG("A key was pressed during drag, reverting changes.");
+ dragloop->result = DRAG_REVERT;
+ handle_event(type, event);
+ break;
+
+ case XCB_UNMAP_NOTIFY: {
+ xcb_unmap_notify_event_t *unmap_event = (xcb_unmap_notify_event_t*)event;
+ Con *con = con_by_window_id(unmap_event->window);
+
+ if (con != NULL) {
+ DLOG("UnmapNotify for window 0x%08x (container %p)\n", unmap_event->window, con);
+
+ if (con_get_workspace(con) == con_get_workspace(focused)) {
+ DLOG("UnmapNotify for a managed window on the current workspace, aborting\n");
+ dragloop->result = DRAG_ABORT;
+ }
+ }
+
+ handle_event(type, event);
+ break;
+ }
+
+ case XCB_MOTION_NOTIFY:
+ /* motion_notify events are saved for later */
+ FREE(last_motion_notify);
+ last_motion_notify = (xcb_motion_notify_event_t*)event;
+ break;
+
+ default:
+ DLOG("Passing to original handler\n");
+ handle_event(type, event);
+ break;
+ }
+
+ if (last_motion_notify != (xcb_motion_notify_event_t*)event)
+ free(event);
+
+ if (dragloop->result != DRAGGING)
+ return;
+ }
+
+ if (last_motion_notify == NULL)
+ return;
+
+ dragloop->callback(
+ dragloop->con,
+ &(dragloop->old_rect),
+ last_motion_notify->root_x,
+ last_motion_notify->root_y,
+ dragloop->extra);
+ free(last_motion_notify);
+}
+
+
/*
* This function grabs your pointer and keyboard and lets you drag stuff around
* (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
confine_to, border_t border, int cursor, callback_t callback, const void *extra)
{
- uint32_t new_x, new_y;
- Rect old_rect = { 0, 0, 0, 0 };
- if (con != NULL)
- memcpy(&old_rect, &(con->rect), sizeof(Rect));
-
xcb_cursor_t xcursor = (cursor && xcursor_supported) ?
xcursor_get_cursor(cursor) : XCB_NONE;
/* Grab the pointer */
xcb_grab_pointer_cookie_t cookie;
xcb_grab_pointer_reply_t *reply;
+ xcb_generic_error_t *error;
+
cookie = xcb_grab_pointer(conn,
false, /* get all pointer events specified by the following mask */
root, /* grab the root window */
xcursor, /* possibly display a special cursor */
XCB_CURRENT_TIME);
- if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) {
- ELOG("Could not grab pointer\n");
+ if ((reply = xcb_grab_pointer_reply(conn, cookie, &error)) == NULL) {
+ ELOG("Could not grab pointer (error_code = %d)\n", error->error_code);
+ free(error);
return DRAG_ABORT;
}
XCB_GRAB_MODE_ASYNC /* keyboard mode */
);
- if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, NULL)) == NULL) {
- ELOG("Could not grab keyboard\n");
+ if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, &error)) == NULL) {
+ ELOG("Could not grab keyboard (error_code = %d)\n", error->error_code);
+ free(error);
+ xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
return DRAG_ABORT;
}
free(keyb_reply);
/* Go into our own event loop */
- xcb_flush(conn);
-
- xcb_generic_event_t *inside_event, *last_motion_notify = NULL;
- Con *inside_con = NULL;
-
- drag_result_t drag_result = DRAGGING;
- /* I’ve always wanted to have my own eventhandler… */
- while (drag_result == DRAGGING && (inside_event = xcb_wait_for_event(conn))) {
- /* We now handle all events we can get using xcb_poll_for_event */
- do {
- /* skip x11 errors */
- if (inside_event->response_type == 0) {
- free(inside_event);
- continue;
- }
- /* Strip off the highest bit (set if the event is generated) */
- int type = (inside_event->response_type & 0x7F);
-
- switch (type) {
- case XCB_BUTTON_RELEASE:
- drag_result = DRAG_SUCCESS;
- break;
-
- case XCB_MOTION_NOTIFY:
- /* motion_notify events are saved for later */
- FREE(last_motion_notify);
- last_motion_notify = inside_event;
- break;
-
- case XCB_UNMAP_NOTIFY:
- inside_con = con_by_window_id(((xcb_unmap_notify_event_t*)inside_event)->window);
-
- if (inside_con != NULL) {
- DLOG("UnmapNotify for window 0x%08x (container %p)\n", ((xcb_unmap_notify_event_t*)inside_event)->window, inside_con);
-
- if (con_get_workspace(inside_con) == con_get_workspace(focused)) {
- DLOG("UnmapNotify for a managed window on the current workspace, aborting\n");
- drag_result = DRAG_ABORT;
- }
- }
-
- handle_event(type, inside_event);
- break;
-
- case XCB_KEY_PRESS:
- /* Cancel the drag if a key was pressed */
- DLOG("A key was pressed during drag, reverting changes.");
- drag_result = DRAG_REVERT;
-
- handle_event(type, inside_event);
- break;
-
- default:
- DLOG("Passing to original handler\n");
- /* Use original handler */
- handle_event(type, inside_event);
- break;
- }
- if (last_motion_notify != inside_event)
- free(inside_event);
- } while ((inside_event = xcb_poll_for_event(conn)) != NULL);
-
- if (last_motion_notify == NULL || drag_result != DRAGGING)
- continue;
-
- new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
- new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y;
-
- callback(con, &old_rect, new_x, new_y, extra);
- FREE(last_motion_notify);
- }
+ struct drag_x11_cb loop = {
+ .result = DRAGGING,
+ .con = con,
+ .callback = callback,
+ .extra = extra,
+ };
+ if (con)
+ loop.old_rect = con->rect;
+ ev_check_init(&loop.check, xcb_drag_check_cb);
+ main_set_x11_cb(false);
+ ev_check_start(main_loop, &loop.check);
+
+ while (loop.result == DRAGGING)
+ ev_run(main_loop, EVRUN_ONCE);
+
+ ev_check_stop(main_loop, &loop.check);
+ main_set_x11_cb(true);
xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
-
xcb_flush(conn);
- return drag_result;
+ return loop.result;
}
/*
handle_unmap_notify_event(&unmap);
}
+static bool window_name_changed(i3Window *window, char *old_name) {
+ if ((old_name == NULL) && (window->name == NULL))
+ return false;
+
+ /* Either the old or the new one is NULL, but not both. */
+ if ((old_name == NULL) ^ (window->name == NULL))
+ return true;
+
+ return (strcmp(old_name, i3string_as_utf8(window->name)) != 0);
+}
+
/*
* Called when a window changes its title
*
if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
return false;
+ char *old_name = (con->window->name != NULL ? sstrdup(i3string_as_utf8(con->window->name)) : NULL);
+
window_update_name(con->window, prop, false);
x_push_changes(croot);
+ if (window_name_changed(con->window, old_name))
+ ipc_send_window_event("title", con);
+
+ FREE(old_name);
+
return true;
}
if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
return false;
+ char *old_name = (con->window->name != NULL ? sstrdup(i3string_as_utf8(con->window->name)) : NULL);
+
window_update_name_legacy(con->window, prop, false);
x_push_changes(croot);
+ if (window_name_changed(con->window, old_name))
+ ipc_send_window_event("title", con);
+
+ FREE(old_name);
+
return true;
}
struct property_handler_t *handler = NULL;
xcb_get_property_reply_t *propr = NULL;
- for (int c = 0; c < sizeof(property_handlers) / sizeof(struct property_handler_t); c++) {
+ for (size_t c = 0; c < sizeof(property_handlers) / sizeof(struct property_handler_t); c++) {
if (property_handlers[c].atom != atom)
continue;
$(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(PREFIX)/bin/
+ $(INSTALL) -m 0755 i3-save-tree $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) -m 0755 i3-dmenu-desktop $(DESTDIR)$(PREFIX)/bin/
test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes
y(integer, (long int)con);
ystr("type");
- y(integer, con->type);
+ switch (con->type) {
+ case CT_ROOT:
+ ystr("root");
+ break;
+ case CT_OUTPUT:
+ ystr("output");
+ break;
+ case CT_CON:
+ ystr("con");
+ break;
+ case CT_FLOATING_CON:
+ ystr("floating_con");
+ break;
+ case CT_WORKSPACE:
+ ystr("workspace");
+ break;
+ case CT_DOCKAREA:
+ ystr("dockarea");
+ break;
+ default:
+ DLOG("About to dump unknown container type=%d. This is a bug.\n", con->type);
+ assert(false);
+ break;
+ }
/* provided for backwards compatibility only. */
ystr("orientation");
y(integer, con->window->id);
else y(null);
+ if (con->window && !inplace_restart) {
+ /* Window properties are useless to preserve when restarting because
+ * they will be queried again anyway. However, for i3-save-tree(1),
+ * they are very useful and save i3-save-tree dealing with X11. */
+ ystr("window_properties");
+ y(map_open);
+
+#define DUMP_PROPERTY(key, prop_name) do { \
+ if (con->window->prop_name != NULL) { \
+ ystr(key); \
+ ystr(con->window->prop_name); \
+ } \
+} while (0)
+
+ DUMP_PROPERTY("class", class_class);
+ DUMP_PROPERTY("instance", class_instance);
+ DUMP_PROPERTY("window_role", role);
+
+ if (con->window->name != NULL) {
+ ystr("title");
+ ystr(i3string_as_utf8(con->window->name));
+ }
+
+ y(map_close);
+ }
+
ystr("nodes");
y(array_open);
Con *node;
y(array_open);
Match *match;
TAILQ_FOREACH(match, &(con->swallow_head), matches) {
+ y(map_open);
if (match->dock != -1) {
- y(map_open);
ystr("dock");
y(integer, match->dock);
ystr("insert_where");
y(integer, match->insert_where);
- y(map_close);
}
- /* TODO: the other swallow keys */
+#define DUMP_REGEX(re_name) do { \
+ if (match->re_name != NULL) { \
+ ystr(# re_name); \
+ ystr(match->re_name->pattern); \
+ } \
+} while (0)
+
+ DUMP_REGEX(class);
+ DUMP_REGEX(instance);
+ DUMP_REGEX(window_role);
+ DUMP_REGEX(title);
+
+#undef DUMP_REGEX
+ y(map_close);
}
if (inplace_restart) {
*/
IPC_HANDLER(subscribe) {
yajl_handle p;
- yajl_callbacks callbacks;
yajl_status stat;
ipc_client *current, *client = NULL;
}
/* Setup the JSON parser */
- memset(&callbacks, 0, sizeof(yajl_callbacks));
- callbacks.yajl_string = add_subscription;
+ static yajl_callbacks callbacks = {
+ .yajl_string = add_subscription,
+ };
p = yalloc(&callbacks, (void*)client);
stat = yajl_parse(p, (const unsigned char*)message, message_size);
y(free);
setlocale(LC_NUMERIC, "");
}
+
+/**
+ * For the window events we send, along the usual "change" field,
+ * also the window container, in "container".
+ */
+void ipc_send_window_event(const char *property, Con *con) {
+ DLOG("Issue IPC window %s event for X11 window 0x%08x\n", property, con->window->id);
+
+ setlocale(LC_NUMERIC, "C");
+ yajl_gen gen = ygenalloc();
+
+ y(map_open);
+
+ ystr("change");
+ ystr(property);
+
+ ystr("container");
+ dump_node(gen, con, false);
+
+ y(map_close);
+
+ const unsigned char *payload;
+ ylength length;
+ y(get_buf, &payload, &length);
+
+ ipc_send_event("window", I3_IPC_EVENT_WINDOW, (const char *)payload);
+ y(free);
+ setlocale(LC_NUMERIC, "");
+}
}
static yajl_callbacks command_error_callbacks = {
- NULL,
- &json_boolean,
- NULL,
- NULL,
- NULL,
- NULL,
- &json_start_map,
- &json_map_key,
- &json_end_map,
- NULL,
- NULL
+ .yajl_boolean = json_boolean,
+ .yajl_start_map = json_start_map,
+ .yajl_map_key = json_map_key,
+ .yajl_end_map = json_end_map,
};
/*
DLOG("(checked mode_switch, state %d)\n", state_filtered);
/* Find the binding */
- Binding *bind = get_binding(state_filtered, key_release, event->detail);
+ Binding *bind = get_keyboard_binding(state_filtered, key_release, event->detail);
/* No match? Then the user has Mode_switch enabled but does not have a
* specific keybinding. Fall back to the default keybindings (without
if (bind == NULL) {
state_filtered &= ~(BIND_MODE_SWITCH);
DLOG("no match, new state_filtered = %d\n", state_filtered);
- if ((bind = get_binding(state_filtered, key_release, event->detail)) == NULL) {
+ if ((bind = get_keyboard_binding(state_filtered, key_release, event->detail)) == NULL) {
/* This is not a real error since we can have release and
* non-release keybindings. On a KeyPress event for which there is
* only a !release-binding, but no release-binding, the
static int json_end_map(void *ctx) {
LOG("end of map\n");
if (!parsing_swallows && !parsing_rect && !parsing_window_rect && !parsing_geometry) {
+ /* Set a few default values to simplify manually crafted layout files. */
+ if (json_node->layout == L_DEFAULT) {
+ DLOG("Setting layout = L_SPLITH\n");
+ json_node->layout = L_SPLITH;
+ }
+
+ /* Sanity check: swallow criteria don’t make any sense on a split
+ * container. */
+ if (con_is_split(json_node) > 0 && !TAILQ_EMPTY(&(json_node->swallow_head))) {
+ DLOG("sanity check: removing swallows specification from split container\n");
+ while (!TAILQ_EMPTY(&(json_node->swallow_head))) {
+ Match *match = TAILQ_FIRST(&(json_node->swallow_head));
+ TAILQ_REMOVE(&(json_node->swallow_head), match, matches);
+ match_free(match);
+ }
+ }
+
LOG("attaching\n");
con_attach(json_node, json_node->parent, true);
LOG("Creating window\n");
static int json_end_array(void *ctx) {
LOG("end of array\n");
- parsing_swallows = false;
+ if (!parsing_swallows && !parsing_focus) {
+ con_fix_percent(json_node);
+ }
+ if (parsing_swallows) {
+ parsing_swallows = false;
+ }
if (parsing_focus) {
/* Clear the list of focus mappings */
struct focus_mapping *mapping;
#endif
LOG("string: %.*s for key %s\n", (int)len, val, last_key);
if (parsing_swallows) {
- /* TODO: the other swallowing keys */
+ char *sval;
+ sasprintf(&sval, "%.*s", len, val);
if (strcasecmp(last_key, "class") == 0) {
- current_swallow->class = scalloc((len+1) * sizeof(char));
- memcpy(current_swallow->class, val, len);
+ current_swallow->class = regex_new(sval);
+ } else if (strcasecmp(last_key, "instance") == 0) {
+ current_swallow->instance = regex_new(sval);
+ } else if (strcasecmp(last_key, "window_role") == 0) {
+ current_swallow->window_role = regex_new(sval);
+ } else if (strcasecmp(last_key, "title") == 0) {
+ current_swallow->title = regex_new(sval);
+ } else {
+ ELOG("swallow key %s unknown\n", last_key);
}
- LOG("unhandled yet: swallow\n");
+ free(sval);
} else {
if (strcasecmp(last_key, "name") == 0) {
json_node->name = scalloc((len+1) * sizeof(char));
json_node->border_style = BS_NORMAL;
else LOG("Unhandled \"border\": %s\n", buf);
free(buf);
+ } else if (strcasecmp(last_key, "type") == 0) {
+ char *buf = NULL;
+ sasprintf(&buf, "%.*s", (int)len, val);
+ if (strcasecmp(buf, "root") == 0)
+ json_node->type = CT_ROOT;
+ else if (strcasecmp(buf, "output") == 0)
+ json_node->type = CT_OUTPUT;
+ else if (strcasecmp(buf, "con") == 0)
+ json_node->type = CT_CON;
+ else if (strcasecmp(buf, "floating_con") == 0)
+ json_node->type = CT_FLOATING_CON;
+ else if (strcasecmp(buf, "workspace") == 0)
+ json_node->type = CT_WORKSPACE;
+ else if (strcasecmp(buf, "dockarea") == 0)
+ json_node->type = CT_DOCKAREA;
+ else LOG("Unhandled \"type\": %s\n", buf);
+ free(buf);
} else if (strcasecmp(last_key, "layout") == 0) {
char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val);
static int json_int(void *ctx, long val) {
LOG("int %ld for key %s\n", val, last_key);
#endif
+ /* For backwards compatibility with i3 < 4.8 */
if (strcasecmp(last_key, "type") == 0)
json_node->type = val;
r->width = val;
else if (strcasecmp(last_key, "height") == 0)
r->height = val;
- else printf("WARNING: unknown key %s in rect\n", last_key);
- printf("rect now: (%d, %d, %d, %d)\n",
+ else ELOG("WARNING: unknown key %s in rect\n", last_key);
+ DLOG("rect now: (%d, %d, %d, %d)\n",
r->x, r->y, r->width, r->height);
}
if (parsing_swallows) {
return 1;
}
-void tree_append_json(const char *filename) {
- /* TODO: percent of other windows are not correctly fixed at the moment */
+void tree_append_json(const char *filename, char **errormsg) {
FILE *f;
if ((f = fopen(filename, "r")) == NULL) {
LOG("Cannot open file \"%s\"\n", filename);
LOG("read %d bytes\n", n);
yajl_gen g;
yajl_handle hand;
- yajl_callbacks callbacks;
- memset(&callbacks, '\0', sizeof(yajl_callbacks));
- callbacks.yajl_start_map = json_start_map;
- callbacks.yajl_end_map = json_end_map;
- callbacks.yajl_end_array = json_end_array;
- callbacks.yajl_string = json_string;
- callbacks.yajl_map_key = json_key;
- callbacks.yajl_integer = json_int;
- callbacks.yajl_double = json_double;
- callbacks.yajl_boolean = json_bool;
+ static yajl_callbacks callbacks = {
+ .yajl_boolean = json_bool,
+ .yajl_integer = json_int,
+ .yajl_double = json_double,
+ .yajl_string = json_string,
+ .yajl_start_map = json_start_map,
+ .yajl_map_key = json_key,
+ .yajl_end_map = json_end_map,
+ .yajl_end_array = json_end_array,
+ };
#if YAJL_MAJOR >= 2
g = yajl_gen_alloc(NULL);
hand = yajl_alloc(&callbacks, NULL, (void*)g);
g = yajl_gen_alloc(NULL, NULL);
hand = yajl_alloc(&callbacks, NULL, NULL, (void*)g);
#endif
+ /* Allowing comments allows for more user-friendly layout files. */
+ yajl_config(hand, yajl_allow_comments, true);
+ /* Allow multiple values, i.e. multiple nodes to attach */
+ yajl_config(hand, yajl_allow_multiple_values, true);
yajl_status stat;
json_node = focused;
to_focus = NULL;
+ parsing_swallows = false;
parsing_rect = false;
parsing_window_rect = false;
parsing_geometry = false;
+ parsing_focus = false;
setlocale(LC_NUMERIC, "C");
stat = yajl_parse(hand, (const unsigned char*)buf, n);
if (stat != yajl_status_ok)
{
- unsigned char * str = yajl_get_error(hand, 1, (const unsigned char*)buf, n);
- fprintf(stderr, "%s\n", (const char *) str);
+ unsigned char *str = yajl_get_error(hand, 1, (const unsigned char*)buf, n);
+ ELOG("JSON parsing error: %s\n", str);
+ if (errormsg != NULL)
+ *errormsg = sstrdup((const char*)str);
yajl_free_error(hand, str);
}
+ /* In case not all containers were restored, we need to fix the
+ * percentages, otherwise i3 will crash immediately when rendering the
+ * next time. */
+ con_fix_percent(focused);
+
setlocale(LC_NUMERIC, "");
#if YAJL_MAJOR >= 2
yajl_complete_parse(hand);
/* If there is no space for the current message in the ringbuffer, we
* need to wrap and write to the beginning again. */
- if (len >= (logbuffer_size - (logwalk - logbuffer))) {
+ if (len >= (size_t)(logbuffer_size - (logwalk - logbuffer))) {
loglastwrap = logwalk;
logwalk = logbuffer + sizeof(i3_shmlog_header);
store_log_markers();
/** 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
+ * temporarily for drag_pointer(). */
+static struct ev_check *xcb_check;
+
static int xkb_event_base;
int xkb_current_group;
}
}
+/*
+ * Enable or disable the main X11 event handling function.
+ * This is used by drag_pointer() which has its own, modal event handler, which
+ * takes precedence over the normal event handler.
+ *
+ */
+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);
+ /* 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);
+ } else {
+ ev_check_stop(main_loop, xcb_check);
+ }
+}
/*
* When using xmodmap to change the keyboard mapping, this event
only_check_config = true;
break;
case 'v':
- printf("i3 version " I3_VERSION " © 2009-2013 Michael Stapelberg and contributors\n");
+ printf("i3 version " I3_VERSION " © 2009-2014 Michael Stapelberg and contributors\n");
exit(EXIT_SUCCESS);
break;
case 'm':
- printf("Binary i3 version: " I3_VERSION " © 2009-2013 Michael Stapelberg and contributors\n");
+ printf("Binary i3 version: " I3_VERSION " © 2009-2014 Michael Stapelberg and contributors\n");
display_running_version();
exit(EXIT_SUCCESS);
break;
}
}
+ restore_connect();
+
/* Setup NetWM atoms */
#define xmacro(name) \
do { \
x_set_i3_atoms();
ewmh_update_workarea();
+ /* Set the _NET_CURRENT_DESKTOP property. */
+ ewmh_update_current_desktop();
+
struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io));
struct ev_io *xkb = scalloc(sizeof(struct ev_io));
- struct ev_check *xcb_check = scalloc(sizeof(struct ev_check));
+ xcb_check = scalloc(sizeof(struct ev_check));
struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare));
ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
xcb_aux_sync(conn);
}
-/*
- * The following function sends a new window event, which consists
- * of fields "change" and "container", the latter containing a dump
- * of the window's container.
- *
- */
-static void ipc_send_window_new_event(Con *con) {
- setlocale(LC_NUMERIC, "C");
- yajl_gen gen = ygenalloc();
-
- y(map_open);
-
- ystr("change");
- ystr("new");
-
- ystr("container");
- dump_node(gen, con, false);
-
- y(map_close);
-
- const unsigned char *payload;
- ylength length;
- y(get_buf, &payload, &length);
-
- ipc_send_event("window", I3_IPC_EVENT_WINDOW, (const char *)payload);
- y(free);
- setlocale(LC_NUMERIC, "");
-}
-
/*
* Do some sanity checks and then reparent the window.
*
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
utf8_title_cookie, title_cookie,
class_cookie, leader_cookie, transient_cookie,
- role_cookie, startup_id_cookie, wm_hints_cookie;
+ role_cookie, startup_id_cookie, wm_hints_cookie,
+ wm_normal_hints_cookie, motif_wm_hints_cookie;
geomc = xcb_get_geometry(conn, d);
role_cookie = GET_PROPERTY(A_WM_WINDOW_ROLE, 128);
startup_id_cookie = GET_PROPERTY(A__NET_STARTUP_ID, 512);
wm_hints_cookie = xcb_icccm_get_wm_hints(conn, window);
- /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */
+ wm_normal_hints_cookie = xcb_icccm_get_wm_normal_hints(conn, window);
+ motif_wm_hints_cookie = GET_PROPERTY(A__MOTIF_WM_HINTS, 5 * sizeof(uint64_t));
DLOG("Managing window 0x%08x\n", window);
window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL), true);
bool urgency_hint;
window_update_hints(cwindow, xcb_get_property_reply(conn, wm_hints_cookie, NULL), &urgency_hint);
+ border_style_t motif_border_style = BS_NORMAL;
+ window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
+ xcb_size_hints_t wm_size_hints;
+ xcb_icccm_get_wm_size_hints_reply(conn, wm_normal_hints_cookie, &wm_size_hints, NULL);
+ xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
+ xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL);
xcb_get_property_reply_t *startup_id_reply;
startup_id_reply = xcb_get_property_reply(conn, startup_id_cookie, NULL);
/* Where to start searching for a container that swallows the new one? */
Con *search_at = croot;
- xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
- if (xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_DOCK)) {
+ if (xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_DOCK)) {
LOG("This window is of type dock\n");
Output *output = get_output_containing(geom->x, geom->y);
if (output != NULL) {
cwindow->dock = W_DOCK_BOTTOM;
} else {
DLOG("Ignoring invalid reserved edges (_NET_WM_STRUT_PARTIAL), using position as fallback:\n");
- if (geom->y < (search_at->rect.height / 2)) {
+ if (geom->y < (int16_t)(search_at->rect.height / 2)) {
DLOG("geom->y = %d < rect.height / 2 = %d, it is a top dock client\n",
geom->y, (search_at->rect.height / 2));
cwindow->dock = W_DOCK_TOP;
if (match != NULL && match->insert_where == M_BELOW) {
nc = tree_open_con(nc, cwindow);
}
+
+ /* If M_BELOW is not used, the container is replaced. This happens with
+ * "swallows" criteria that are used for stored layouts, in which case
+ * we need to remove that criterion, because they should only be valid
+ * once. */
+ if (match != NULL && match->insert_where != M_BELOW) {
+ DLOG("Removing match %p from container %p\n", match, nc);
+ TAILQ_REMOVE(&(nc->swallow_head), match, matches);
+ }
}
DLOG("new container = %p\n", nc);
+ if (nc->window != NULL && nc->window != cwindow) {
+ if (!restore_kill_placeholder(nc->window->id)) {
+ DLOG("Uh?! Container without a placeholder, but with a window, has swallowed this to-be-managed window?!\n");
+ }
+ }
nc->window = cwindow;
x_reinit(nc);
if (fs == NULL)
fs = con_get_fullscreen_con(croot, CF_GLOBAL);
- xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL);
if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_FULLSCREEN)) {
fs = NULL;
con_toggle_fullscreen(nc, CF_OUTPUT);
}
- FREE(state_reply);
+ bool set_focus = false;
if (fs == NULL) {
DLOG("Not in fullscreen mode, focusing\n");
if (workspace_is_visible(ws) && current_output == target_output) {
if (!match || !match->restart_mode) {
- con_focus(nc);
+ set_focus = true;
} else DLOG("not focusing, matched with restart_mode == true\n");
} else DLOG("workspace not visible, not focusing\n");
} else DLOG("dock, not focusing\n");
/* set floating if necessary */
bool want_floating = false;
- if (xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_DIALOG) ||
- xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_UTILITY) ||
- xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_TOOLBAR) ||
- xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_SPLASH)) {
+ if (xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_DIALOG) ||
+ xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_UTILITY) ||
+ xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_TOOLBAR) ||
+ xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_SPLASH) ||
+ xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_MODAL) ||
+ (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE &&
+ wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE &&
+ wm_size_hints.min_height == wm_size_hints.max_height &&
+ wm_size_hints.min_width == wm_size_hints.max_width)) {
LOG("This window is a dialog window, setting floating\n");
want_floating = true;
}
- FREE(reply);
+ FREE(state_reply);
+ FREE(type_reply);
if (cwindow->transient_for != XCB_NONE ||
(cwindow->leader != XCB_NONE &&
transient_win->transient_for != XCB_NONE) {
if (transient_win->transient_for == fs->window->id) {
LOG("This floating window belongs to the fullscreen window (popup_during_fullscreen == smart)\n");
- con_focus(nc);
+ set_focus = true;
break;
}
Con *next_transient = con_by_window_id(transient_win->transient_for);
floating_enable(nc, true);
}
+ if (motif_border_style != BS_NORMAL) {
+ DLOG("MOTIF_WM_HINTS specifies decorations (border_style = %d)\n", motif_border_style);
+ con_set_border_style(nc, motif_border_style, config.default_border_width);
+ }
+
/* to avoid getting an UnmapNotify event due to reparenting, we temporarily
* declare no interest in any state change event of this window */
values[0] = XCB_NONE;
tree_render();
/* Send an event about window creation */
- ipc_send_window_new_event(nc);
+ ipc_send_window_event("new", nc);
+
+ /* Defer setting focus after the 'new' event has been sent to ensure the
+ * proper window event sequence. */
+ if (set_focus) {
+ con_focus(nc);
+ tree_render();
+ }
/* Windows might get managed with the urgency hint already set (Pidgin is
* known to do that), so check for that and handle the hint accordingly.
match->application == NULL &&
match->class == NULL &&
match->instance == NULL &&
- match->role == NULL &&
+ match->window_role == NULL &&
match->urgent == U_DONTCHECK &&
match->id == XCB_NONE &&
match->con_id == NULL &&
DUPLICATE_REGEX(application);
DUPLICATE_REGEX(class);
DUPLICATE_REGEX(instance);
- DUPLICATE_REGEX(role);
+ DUPLICATE_REGEX(window_role);
}
/*
}
}
- if (match->role != NULL) {
+ if (match->window_role != NULL) {
if (window->role != NULL &&
- regex_matches(match->role, window->role)) {
+ regex_matches(match->window_role, window->role)) {
LOG("window_role matches (%s)\n", window->role);
} else {
return false;
regex_free(match->class);
regex_free(match->instance);
regex_free(match->mark);
- regex_free(match->role);
+ regex_free(match->window_role);
/* Second step: free the regex helper struct itself */
FREE(match->title);
FREE(match->class);
FREE(match->instance);
FREE(match->mark);
- FREE(match->role);
+ FREE(match->window_role);
}
*
*/
void tree_move(int direction) {
+ position_t position;
+ Con *target;
+
DLOG("Moving in direction %d\n", direction);
+
/* 1: get the first parent with the same orientation */
Con *con = focused;
TAILQ_PREV(con, nodes_head, nodes) :
TAILQ_NEXT(con, nodes)))) {
if (!con_is_leaf(swap)) {
- insert_con_into(con, con_descend_focused(swap), AFTER);
+ DLOG("Moving into our bordering branch\n");
+ target = con_descend_direction(swap, direction);
+ position = (con_orientation(target->parent) != o ||
+ direction == D_UP ||
+ direction == D_LEFT ?
+ AFTER : BEFORE);
+ insert_con_into(con, target, position);
goto end;
}
if (direction == D_LEFT || direction == D_UP)
}
DLOG("above = %p\n", above);
- Con *next;
- position_t position;
- if (direction == D_UP || direction == D_LEFT) {
- position = BEFORE;
- next = TAILQ_PREV(above, nodes_head, nodes);
- } else {
- position = AFTER;
- next = TAILQ_NEXT(above, nodes);
- }
- /* special case: there is a split container in the direction we are moving
- * to, so descend and append */
- if (next && !con_is_leaf(next))
- insert_con_into(con, con_descend_focused(next), AFTER);
- else
+ Con *next = (direction == D_UP || direction == D_LEFT ?
+ TAILQ_PREV(above, nodes_head, nodes) :
+ TAILQ_NEXT(above, nodes));
+
+ if (next && !con_is_leaf(next)) {
+ DLOG("Moving into the bordering branch of our adjacent container\n");
+ target = con_descend_direction(next, direction);
+ position = (con_orientation(target->parent) != o ||
+ direction == D_UP ||
+ direction == D_LEFT ?
+ AFTER : BEFORE);
+ insert_con_into(con, target, position);
+ } else {
+ DLOG("Moving into container above\n");
+ position = (direction == D_UP || direction == D_LEFT ? BEFORE : AFTER);
insert_con_into(con, above, position);
+ }
end:
/* We need to call con_focus() to fix the focus stack "above" the container
* if there is no output which contains these coordinates.
*
*/
-Output *get_output_containing(int x, int y) {
+Output *get_output_containing(unsigned int x, unsigned int y) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
DLOG("resize handler\n");
- uint32_t new_position;
-
/* TODO: previously, we were getting a rect containing all screens. why? */
Con *output = con_get_output(first);
DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width);
xcb_window_t grabwin = create_window(conn, output->rect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
XCB_WINDOW_CLASS_INPUT_ONLY, XCURSOR_CURSOR_POINTER, true, mask, values);
+ /* Keep track of the coordinate orthogonal to motion so we can determine
+ * the length of the resize afterward. */
+ uint32_t initial_position, new_position;
+
+ /* Configure the resizebar and snap the pointer. The resizebar runs along
+ * the rect of the second con and follows the motion of the pointer. */
Rect helprect;
if (orientation == HORIZ) {
- helprect.x = event->root_x;
- helprect.y = output->rect.y;
+ helprect.x = second->rect.x;
+ helprect.y = second->rect.y;
helprect.width = 2;
- helprect.height = output->rect.height;
- new_position = event->root_x;
+ helprect.height = second->rect.height;
+ initial_position = second->rect.x;
+ xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
+ second->rect.x, event->root_y);
} else {
- helprect.x = output->rect.x;
- helprect.y = event->root_y;
- helprect.width = output->rect.width;
+ helprect.x = second->rect.x;
+ helprect.y = second->rect.y;
+ helprect.width = second->rect.width;
helprect.height = 2;
- new_position = event->root_y;
+ initial_position = second->rect.y;
+ xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
+ event->root_x, second->rect.y);
}
mask = XCB_CW_BACK_PIXEL;
xcb_flush(conn);
+ /* `new_position' will be updated by the `resize_callback'. */
+ new_position = initial_position;
+
const struct callback_params params = { orientation, output, helpwin, &new_position };
+ /* `drag_pointer' blocks until the drag is completed. */
drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, ¶ms);
xcb_destroy_window(conn, helpwin);
if (drag_result == DRAG_REVERT)
return 0;
- int pixels;
- if (orientation == HORIZ)
- pixels = (new_position - event->root_x);
- else pixels = (new_position - event->root_y);
+ int pixels = (new_position - initial_position);
DLOG("Done, pixels = %d\n", pixels);
--- /dev/null
+#undef I3__FILE__
+#define I3__FILE__ "restore_layout.c"
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * restore_layout.c: Everything for restored containers that is not pure state
+ * parsing (which can be found in load_layout.c).
+ *
+ *
+ */
+#include "all.h"
+
+typedef struct placeholder_state {
+ /** The X11 placeholder window. */
+ xcb_window_t window;
+ /** The container to which this placeholder window belongs. */
+ Con *con;
+
+ /** Current size of the placeholder window (to detect size changes). */
+ Rect rect;
+
+ /** The pixmap to render on (back buffer). */
+ xcb_pixmap_t pixmap;
+ /** The graphics context for “pixmap”. */
+ xcb_gcontext_t gc;
+
+ TAILQ_ENTRY(placeholder_state) state;
+} placeholder_state;
+
+static TAILQ_HEAD(state_head, placeholder_state) state_head =
+ TAILQ_HEAD_INITIALIZER(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);
+
+/* Documentation for these functions can be found in src/main.c, starting at xcb_got_event */
+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)) {
+ DLOG("restore X11 connection has an error, reconnecting\n");
+ restore_connect();
+ return;
+ }
+
+ while ((event = xcb_poll_for_event(restore_conn)) != NULL) {
+ if (event->response_type == 0) {
+ xcb_generic_error_t *error = (xcb_generic_error_t*)event;
+ DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n",
+ error->sequence, error->error_code);
+ free(event);
+ continue;
+ }
+
+ /* Strip off the highest bit (set if the event is generated) */
+ int type = (event->response_type & 0x7F);
+
+ restore_handle_event(type, event);
+
+ free(event);
+ }
+}
+
+/*
+ * Opens a separate connection to X11 for placeholder windows when restoring
+ * layouts. This is done as a safety measure (users can xkill a placeholder
+ * window without killing their window manager) and for better isolation, both
+ * on the wire to X11 and thus also in the code.
+ *
+ */
+void restore_connect(void) {
+ if (restore_conn != NULL) {
+ /* 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;
+ while (!TAILQ_EMPTY(&state_head)) {
+ state = TAILQ_FIRST(&state_head);
+ TAILQ_REMOVE(&state_head, state, state);
+ free(state);
+ }
+
+ free(restore_conn);
+ free(xcb_watcher);
+ free(xcb_check);
+ free(xcb_prepare);
+ }
+
+ int screen;
+ restore_conn = xcb_connect(NULL, &screen);
+ if (restore_conn == NULL || xcb_connection_has_error(restore_conn))
+ errx(EXIT_FAILURE, "Cannot open display\n");
+
+ xcb_watcher = scalloc(sizeof(struct ev_io));
+ xcb_check = scalloc(sizeof(struct ev_check));
+ xcb_prepare = scalloc(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);
+}
+
+static void update_placeholder_contents(placeholder_state *state) {
+ xcb_change_gc(restore_conn, state->gc, XCB_GC_FOREGROUND,
+ (uint32_t[]) { config.client.placeholder.background });
+ xcb_poly_fill_rectangle(restore_conn, state->pixmap, state->gc, 1,
+ (xcb_rectangle_t[]) { { 0, 0, state->rect.width, state->rect.height } });
+
+ // TODO: make i3font functions per-connection, at least these two for now…?
+ xcb_flush(restore_conn);
+ xcb_aux_sync(restore_conn);
+
+ set_font_colors(state->gc, config.client.placeholder.text, config.client.placeholder.background);
+
+ Match *swallows;
+ int n = 0;
+ TAILQ_FOREACH(swallows, &(state->con->swallow_head), matches) {
+ char *serialized = NULL;
+
+#define APPEND_REGEX(re_name) do { \
+ if (swallows->re_name != NULL) { \
+ sasprintf(&serialized, "%s%s" #re_name "=\"%s\"", \
+ (serialized ? serialized : "["), \
+ (serialized ? " " : ""), \
+ swallows->re_name->pattern); \
+ } \
+} while (0)
+
+ APPEND_REGEX(class);
+ APPEND_REGEX(instance);
+ APPEND_REGEX(window_role);
+ APPEND_REGEX(title);
+
+ if (serialized == NULL) {
+ DLOG("This swallows specification is not serializable?!\n");
+ continue;
+ }
+
+ sasprintf(&serialized, "%s]", serialized);
+ DLOG("con %p (placeholder 0x%08x) line %d: %s\n", state->con, state->window, n, serialized);
+
+ i3String *str = i3string_from_utf8(serialized);
+ draw_text(str, state->pixmap, state->gc, 2, (n * (config.font.height + 2)) + 2, state->rect.width - 2);
+ i3string_free(str);
+ n++;
+ free(serialized);
+ }
+
+ // TODO: render the watch symbol in a bigger font
+ i3String *line = i3string_from_utf8("⌚");
+ int text_width = predict_text_width(line);
+ int x = (state->rect.width / 2) - (text_width / 2);
+ int y = (state->rect.height / 2) - (config.font.height / 2);
+ draw_text(line, state->pixmap, state->gc, x, y, text_width);
+ i3string_free(line);
+ xcb_flush(conn);
+ xcb_aux_sync(conn);
+}
+
+static void open_placeholder_window(Con *con) {
+ if (con_is_leaf(con)) {
+ xcb_window_t placeholder = create_window(
+ restore_conn,
+ con->rect,
+ XCB_COPY_FROM_PARENT,
+ XCB_COPY_FROM_PARENT,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ XCURSOR_CURSOR_POINTER,
+ true,
+ XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
+ (uint32_t[]){
+ config.client.placeholder.background,
+ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY,
+ });
+ /* Set the same name as was stored in the layout file. While perhaps
+ * slightly confusing in the first instant, this brings additional
+ * clarity to which placeholder is waiting for which actual window. */
+ xcb_change_property(restore_conn, XCB_PROP_MODE_REPLACE, placeholder,
+ A__NET_WM_NAME, A_UTF8_STRING, 8, strlen(con->name), con->name);
+ DLOG("Created placeholder window 0x%08x for leaf container %p / %s\n",
+ placeholder, con, con->name);
+
+ placeholder_state *state = scalloc(sizeof(placeholder_state));
+ state->window = placeholder;
+ state->con = con;
+ state->rect = con->rect;
+ state->pixmap = xcb_generate_id(restore_conn);
+ xcb_create_pixmap(restore_conn, root_depth, state->pixmap,
+ state->window, state->rect.width, state->rect.height);
+ state->gc = xcb_generate_id(restore_conn);
+ xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){ 0 });
+ update_placeholder_contents(state);
+ TAILQ_INSERT_TAIL(&state_head, state, state);
+
+ /* create temporary id swallow to match the placeholder */
+ Match *temp_id = smalloc(sizeof(Match));
+ match_init(temp_id);
+ temp_id->id = placeholder;
+ TAILQ_INSERT_TAIL(&(con->swallow_head), temp_id, matches);
+ }
+
+ Con *child;
+ TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+ open_placeholder_window(child);
+ }
+ TAILQ_FOREACH(child, &(con->floating_head), floating_windows) {
+ open_placeholder_window(child);
+ }
+}
+
+/*
+ * Open placeholder windows for all children of parent. The placeholder window
+ * will vanish as soon as a real window is swallowed by the container. Until
+ * then, it exposes the criteria that must be fulfilled for a window to be
+ * swallowed by this container.
+ *
+ */
+void restore_open_placeholder_windows(Con *parent) {
+ Con *child;
+ TAILQ_FOREACH(child, &(parent->nodes_head), nodes) {
+ open_placeholder_window(child);
+ }
+ TAILQ_FOREACH(child, &(parent->floating_head), floating_windows) {
+ open_placeholder_window(child);
+ }
+
+ xcb_flush(restore_conn);
+}
+
+/*
+ * Kill the placeholder window, if placeholder refers to a placeholder window.
+ * This function is called when manage.c puts a window into an existing
+ * container. In order not to leak resources, we need to destroy the window and
+ * all associated X11 objects (pixmap/gc).
+ *
+ */
+bool restore_kill_placeholder(xcb_window_t placeholder) {
+ placeholder_state *state;
+ TAILQ_FOREACH(state, &state_head, state) {
+ if (state->window != placeholder)
+ continue;
+
+ xcb_destroy_window(restore_conn, state->window);
+ xcb_free_pixmap(restore_conn, state->pixmap);
+ xcb_free_gc(restore_conn, state->gc);
+ TAILQ_REMOVE(&state_head, state, state);
+ free(state);
+ DLOG("placeholder window 0x%08x destroyed.\n", placeholder);
+ return true;
+ }
+
+ DLOG("0x%08x is not a placeholder window, ignoring.\n", placeholder);
+ return false;
+}
+
+static void expose_event(xcb_expose_event_t *event) {
+ placeholder_state *state;
+ TAILQ_FOREACH(state, &state_head, state) {
+ if (state->window != event->window)
+ continue;
+
+ DLOG("refreshing window 0x%08x contents (con %p)\n", state->window, state->con);
+
+ /* Since we render to our pixmap on every change anyways, expose events
+ * only tell us that the X server lost (parts of) the window contents. We
+ * can handle that by copying the appropriate part from our pixmap to the
+ * window. */
+ xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc,
+ event->x, event->y, event->x, event->y,
+ event->width, event->height);
+ xcb_flush(restore_conn);
+ return;
+ }
+
+ ELOG("Received ExposeEvent for unknown window 0x%08x\n", event->window);
+}
+
+/*
+ * Window size has changed. Update the width/height, then recreate the back
+ * buffer pixmap and the accompanying graphics context and force an immediate
+ * re-rendering.
+ *
+ */
+static void configure_notify(xcb_configure_notify_event_t *event) {
+ placeholder_state *state;
+ TAILQ_FOREACH(state, &state_head, state) {
+ if (state->window != event->window)
+ continue;
+
+ DLOG("ConfigureNotify: window 0x%08x has now width=%d, height=%d (con %p)\n",
+ state->window, event->width, event->height, state->con);
+
+ state->rect.width = event->width;
+ state->rect.height = event->height;
+
+ xcb_free_pixmap(restore_conn, state->pixmap);
+ xcb_free_gc(restore_conn, state->gc);
+
+ state->pixmap = xcb_generate_id(restore_conn);
+ xcb_create_pixmap(restore_conn, root_depth, state->pixmap,
+ state->window, state->rect.width, state->rect.height);
+ state->gc = xcb_generate_id(restore_conn);
+ xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){ 0 });
+
+ update_placeholder_contents(state);
+ xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc,
+ 0, 0, 0, 0, state->rect.width, state->rect.height);
+ xcb_flush(restore_conn);
+ return;
+ }
+
+ ELOG("Received ConfigureNotify for unknown window 0x%08x\n", event->window);
+}
+
+static void restore_handle_event(int type, xcb_generic_event_t *event) {
+ switch (type) {
+ case XCB_EXPOSE:
+ expose_event((xcb_expose_event_t*)event);
+ break;
+ case XCB_CONFIGURE_NOTIFY:
+ configure_notify((xcb_configure_notify_event_t*)event);
+ break;
+ default:
+ DLOG("Received unhandled X11 event of type %d\n", type);
+ break;
+ }
+}
};
focused = croot;
- tree_append_json(globbed);
+ tree_append_json(globbed, NULL);
printf("appended tree, using new root\n");
croot = TAILQ_FIRST(&(croot->nodes_head));
FREE(con->window->class_class);
FREE(con->window->class_instance);
i3string_free(con->window->name);
- free(con->window);
+ FREE(con->window->ran_assignments);
+ FREE(con->window);
}
Con *ws = con_get_workspace(con);
*
*/
void tree_split(Con *con, orientation_t orientation) {
- if (con->type == CT_FLOATING_CON) {
+ if (con_is_floating(con)) {
DLOG("Floating containers can't be split.\n");
return;
}
char *head, *tail, *result;
tail = strchr(path, '/');
- head = strndup(path, tail ? tail - path : strlen(path));
+ head = strndup(path, tail ? (size_t)(tail - path) : strlen(path));
int res = glob(head, GLOB_TILDE, NULL, &globbuf);
free(head);
return NULL;
}
- int written = 0;
+ size_t written = 0;
while (written < length) {
int n = write(fd, payload + written, length - written);
/* TODO: correct error-handling */
}
written += n;
#if YAJL_MAJOR >= 2
- printf("written: %d of %zd\n", written, length);
+ DLOG("written: %zd of %zd\n", written, length);
#else
- printf("written: %d of %d\n", written, length);
+ DLOG("written: %d of %d\n", written, length);
#endif
}
close(fd);
FREE(win->class_class);
win->class_instance = sstrdup(new_class);
- if ((strlen(new_class) + 1) < xcb_get_property_value_length(prop))
+ if ((strlen(new_class) + 1) < (size_t)xcb_get_property_value_length(prop))
win->class_class = sstrdup(new_class + strlen(new_class) + 1);
else win->class_class = NULL;
LOG("WM_CLASS changed to %s (instance), %s (class)\n",
free(prop);
}
+
+/*
+ * Updates the MOTIF_WM_HINTS. The container's border style should be set to
+ * `motif_border_style' if border style is not BS_NORMAL.
+ *
+ * i3 only uses this hint when it specifies a window should have no
+ * title bar, or no decorations at all, which is how most window managers
+ * handle it.
+ *
+ * The EWMH spec intended to replace Motif hints with _NET_WM_WINDOW_TYPE, but
+ * it is still in use by popular widget toolkits such as GTK+ and Java AWT.
+ *
+ */
+void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
+ /* This implementation simply mirrors Gnome's Metacity. Official
+ * documentation of this hint is nowhere to be found.
+ * For more information see:
+ * https://people.gnome.org/~tthurman/docs/metacity/xprops_8h-source.html
+ * http://stackoverflow.com/questions/13787553/detect-if-a-x11-window-has-decorations
+ */
+#define MWM_HINTS_DECORATIONS (1 << 1)
+#define MWM_DECOR_ALL (1 << 0)
+#define MWM_DECOR_BORDER (1 << 1)
+#define MWM_DECOR_TITLE (1 << 3)
+
+ if (motif_border_style != NULL)
+ *motif_border_style = BS_NORMAL;
+
+ if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+ FREE(prop);
+ return;
+ }
+
+ /* The property consists of an array of 5 uint64_t's. The first value is a bit
+ * mask of what properties the hint will specify. We are only interested in
+ * MWM_HINTS_DECORATIONS because it indicates that the second value of the
+ * array tells us which decorations the window should have, each flag being
+ * a particular decoration. */
+ uint64_t *motif_hints = (uint64_t *)xcb_get_property_value(prop);
+
+ if (motif_border_style != NULL && motif_hints[0] & MWM_HINTS_DECORATIONS) {
+ if (motif_hints[1] & MWM_DECOR_ALL || motif_hints[1] & MWM_DECOR_TITLE)
+ *motif_border_style = BS_NORMAL;
+ else if (motif_hints[1] & MWM_DECOR_BORDER)
+ *motif_border_style = BS_PIXEL;
+ else
+ *motif_border_style = BS_NONE;
+ }
+
+ FREE(prop);
+
+#undef MWM_HINTS_DECORATIONS
+#undef MWM_DECOR_ALL
+#undef MWM_DECOR_BORDER
+#undef MWM_DECOR_TITLE
+}
/* Stores the X11 window ID of the currently focused window */
xcb_window_t focused_id = XCB_NONE;
+/* Because 'focused_id' might be reset to force input focus (after click to
+ * raise), we separately keep track of the X11 window ID to be able to always
+ * tell whether the focused window actually changed. */
+static xcb_window_t last_focused = XCB_NONE;
+
/* The bottom-to-top window stack of all windows which are managed by i3.
* Used for x_get_window_stack(). */
static xcb_window_t *btt_stack;
free(state);
/* Invalidate focused_id to correctly focus new windows with the same ID */
- focused_id = XCB_NONE;
+ focused_id = last_focused = XCB_NONE;
}
/*
/* As the pixmap only depends on the size and not on the position, it
* is enough to check if width/height have changed. Also, we don’t
* create a pixmap at all when the window is actually not visible
- * (height == 0). */
- if ((state->rect.width != rect.width ||
- state->rect.height != rect.height)) {
+ * (height == 0) or when it is not needed. */
+ bool has_rect_changed = (state->rect.width != rect.width || state->rect.height != rect.height);
+
+ /* The pixmap of a borderless leaf container will not be used except
+ * for the titlebar in a stack or tabs (issue #1013). */
+ bool is_pixmap_needed = (con->border_style != BS_NONE ||
+ !con_is_leaf(con) ||
+ con->parent->layout == L_STACKED ||
+ con->parent->layout == L_TABBED);
+
+ /* Check if the container has an unneeded pixmap left over from
+ * previously having a border or titlebar. */
+ if (!is_pixmap_needed && con->pixmap != XCB_NONE) {
+ xcb_free_pixmap(conn, con->pixmap);
+ con->pixmap = XCB_NONE;
+ }
+
+ if (has_rect_changed && is_pixmap_needed) {
if (con->pixmap == 0) {
con->pixmap = xcb_generate_id(conn);
con->pm_gc = xcb_generate_id(conn);
}
/* Map if map state changed, also ensure that the child window
- * is changed if we are mapped *and* in initial state (meaning the
- * container was empty before, but now got a child). Unmaps are handled in
- * x_push_node_unmaps(). */
- if ((state->mapped != con->mapped || (con->mapped && state->initial)) &&
+ * is changed if we are mapped and there is a new, unmapped child window.
+ * Unmaps are handled in x_push_node_unmaps(). */
+ if ((state->mapped != con->mapped || (con->window != NULL && !state->child_mapped)) &&
con->mapped) {
xcb_void_cookie_t cookie;
x_push_node_unmaps(current);
}
+/*
+ * Returns true if the given container is currently attached to its parent.
+ *
+ * TODO: Remove once #1185 has been fixed
+ */
+static bool is_con_attached(Con *con) {
+ if (con->parent == NULL)
+ return false;
+
+ Con *current;
+ TAILQ_FOREACH(current, &(con->parent->nodes_head), nodes) {
+ if (current == con)
+ return true;
+ }
+
+ return false;
+}
+
/*
* Pushes all changes (state of each node, see x_push_node() and the window
* stack) to X11.
send_take_focus(to_focus);
set_focus = !focused->window->doesnt_accept_focus;
DLOG("set_focus = %d\n", set_focus);
+
+ if (!set_focus && to_focus != last_focused && is_con_attached(focused))
+ ipc_send_window_event("focus", focused);
}
if (set_focus) {
}
ewmh_update_active_window(to_focus);
+
+ if (to_focus != XCB_NONE && to_focus != last_focused && focused->window != NULL && is_con_attached(focused))
+ ipc_send_window_event("focus", focused);
}
- focused_id = to_focus;
+ focused_id = last_focused = to_focus;
}
}
*/
void x_set_warp_to(Rect *rect)
{
- warp_to = rect;
+ if (!config.disable_focus_follows_mouse)
+ warp_to = rect;
}
/*
}
xcb_cursor_t xcursor_get_cursor(enum xcursor_cursor_t c) {
- assert(c >= 0 && c < XCURSOR_CURSOR_MAX);
+ assert(c < XCURSOR_CURSOR_MAX);
return cursors[c];
}
int xcursor_get_xcb_cursor(enum xcursor_cursor_t c) {
- assert(c >= 0 && c < XCURSOR_CURSOR_MAX);
+ assert(c < XCURSOR_CURSOR_MAX);
return xcb_cursors[c];
}
* Looks in outputs for the Output whose start coordinates are x, y
*
*/
-static Output *get_screen_at(int x, int y) {
+static Output *get_screen_at(unsigned int x, unsigned int y) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->rect.x == x && output->rect.y == y)
if ($args{valgrind}) {
$i3cmd =
qq|valgrind -v --log-file="$outdir/valgrind-for-$test.log" | .
+ qq|--suppressions="./valgrind.supp" | .
qq|--leak-check=full --track-origins=yes --num-callers=20 | .
qq|--tool=memcheck -- $i3cmd|;
}
for my $output (@outputs) {
next if $output->{name} eq '__i3';
# get the first CT_CON of each output
- my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+ my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
@cons = (@cons, @{$content->{nodes}});
}
[ map { $_->{name} } @cons ]
@{$tree->{nodes}};
die "BUG: Could not find output $args{output}" unless defined($output);
# Get the focused workspace on that output and switch to it.
- my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+ my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
my $focused = $content->{focus}->[0];
my $workspace = first { $_->{id} == $focused } @{$content->{nodes}};
$workspace = $workspace->{name};
my @workspaces;
for my $output (@outputs) {
# get the first CT_CON of each output
- my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+ my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
@workspaces = (@workspaces, @{$content->{nodes}});
}
for my $output (@outputs) {
if (!defined($which)) {
@docked = (@docked, map { @{$_->{nodes}} }
- grep { $_->{type} == 5 }
+ grep { $_->{type} eq 'dockarea' }
@{$output->{nodes}});
} elsif ($which eq 'top') {
- my $first = first { $_->{type} == 5 } @{$output->{nodes}};
+ my $first = first { $_->{type} eq 'dockarea' } @{$output->{nodes}};
@docked = (@docked, @{$first->{nodes}}) if defined($first);
} elsif ($which eq 'bottom') {
- my @matching = grep { $_->{type} == 5 } @{$output->{nodes}};
+ my @matching = grep { $_->{type} eq 'dockarea' } @{$output->{nodes}};
my $last = $matching[-1];
@docked = (@docked, @{$last->{nodes}}) if defined($last);
}
my $tree = $i3->get_tree->recv;
my $focused = $tree->{focus}->[0];
my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
- my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+ my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
return $first->{name}
}
my $output;
for my $o (@outputs) {
# get the first CT_CON of each output
- my $content = first { $_->{type} == 2 } @{$o->{nodes}};
+ my $content = first { $_->{type} eq 'con' } @{$o->{nodes}};
if (defined(first { $_->{name} eq $tmp } @{$content->{nodes}})) {
$output = $o;
last;
window => undef,
name => 'root',
orientation => $ignore,
- type => 0,
+ type => 'root',
id => $ignore,
rect => $ignore,
window_rect => $ignore,
ok(@nodes > 0, 'root node has at least one leaf');
-ok((all { $_->{type} == 1 } @nodes), 'all nodes are of type CT_OUTPUT');
+ok((all { $_->{type} eq 'output' } @nodes), 'all nodes are of type CT_OUTPUT');
ok((none { defined($_->{window}) } @nodes), 'no CT_OUTPUT contains a window');
ok((all { @{$_->{nodes}} > 0 } @nodes), 'all nodes have at least one leaf (workspace)');
my @workspaces;
for my $ws (@nodes) {
- my $content = first { $_->{type} == 2 } @{$ws->{nodes}};
+ my $content = first { $_->{type} eq 'con' } @{$ws->{nodes}};
@workspaces = (@workspaces, @{$content->{nodes}});
}
-ok((all { $_->{type} == 4 } @workspaces), 'all workspaces are of type CT_WORKSPACE');
+ok((all { $_->{type} eq 'workspace' } @workspaces), 'all workspaces are of type CT_WORKSPACE');
#ok((all { @{$_->{nodes}} == 0 } @workspaces), 'all workspaces are empty yet');
ok((none { defined($_->{window}) } @workspaces), 'no CT_OUTPUT contains a window');
my @outputs = @{$tree->{nodes}};
my @workspaces;
for my $output (@outputs) {
- # get the first CT_CON of each output
- my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+ my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
@workspaces = (@workspaces, @{$content->{nodes}});
}
is(scalar @outputs, 1, 'exactly one output (testcase not multi-monitor capable)');
my $output = $outputs[0];
# get the first (and only) CT_CON
- return first { $_->{type} == 2 } @{$output->{nodes}};
+ return first { $_->{type} eq 'con' } @{$output->{nodes}};
}
$tmp = fresh_workspace;
($nodes, $focus) = get_ws_content($tmp);
isnt($nodes->[0]->{id}, $split, 'split container closed');
+# clean up the remaining containers to ensure this workspace will be garbage
+# collected.
+cmd 'kill';
+cmd 'kill';
+
##############################################################
# same thing but this time we are moving the cons away instead
# of killing them
# We move the pointer out of our way to avoid a bug where the focus will
# be set to the window under the cursor
+sync_with_i3;
$x->root->warp_pointer(0, 0);
sync_with_i3;
cmd 'fullscreen';
# Focus screen 1
+sync_with_i3;
$x->root->warp_pointer(1025, 0);
sync_with_i3;
my $diff_ws = open_window;
# Focus screen 0
+sync_with_i3;
$x->root->warp_pointer(0, 0);
sync_with_i3;
my @__i3 = grep { $_->{name} eq '__i3' } @{$tree->{nodes}};
is(scalar @__i3, 1, 'output __i3 found');
-my $content = first { $_->{type} == 2 } @{$__i3[0]->{nodes}};
+my $content = first { $_->{type} eq 'con' } @{$__i3[0]->{nodes}};
my @workspaces = @{$content->{nodes}};
my @workspace_names = map { $_->{name} } @workspaces;
ok('__i3_scratch' ~~ @workspace_names, '__i3_scratch workspace found');
# Events
my $new = AnyEvent->condvar;
+my $focus = AnyEvent->condvar;
$i3->subscribe({
window => sub {
my ($event) = @_;
- $new->send($event->{change} eq 'new');
+ if ($event->{change} eq 'new') {
+ $new->send($event);
+ } elsif ($event->{change} eq 'focus') {
+ $focus->send($event);
+ }
}
})->recv;
open_window;
my $t;
-$t = AnyEvent->timer(after => 0.5, cb => sub { $new->send(0); });
+$t = AnyEvent->timer(
+ after => 0.5,
+ cb => sub {
+ $new->send(0);
+ $focus->send(0);
+ }
+);
-ok($new->recv, 'Window "new" event received');
+is($new->recv->{container}->{focused}, 0, 'Window "new" event received');
+is($focus->recv->{container}->{focused}, 1, 'Window "focus" event received');
}
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Restores a simple layout from a JSON file.
+use i3test;
+use File::Temp qw(tempfile);
+use IO::Handle;
+
+################################################################################
+# empty layout file.
+################################################################################
+
+my ($fh, $filename) = tempfile(UNLINK => 1);
+cmd "append_layout $filename";
+
+does_i3_live;
+
+close($fh);
+
+################################################################################
+# simple vsplit layout
+################################################################################
+
+my $ws = fresh_workspace;
+
+my @content = @{get_ws_content($ws)};
+is(@content, 0, 'no nodes on the new workspace yet');
+
+($fh, $filename) = tempfile(UNLINK => 1);
+print $fh <<EOT;
+{
+ "layout": "splitv",
+ "nodes": [
+ {
+ },
+ {
+ }
+ ]
+}
+EOT
+$fh->flush;
+cmd "append_layout $filename";
+
+does_i3_live;
+
+@content = @{get_ws_content($ws)};
+is(@content, 1, 'one node on the workspace now');
+is($content[0]->{layout}, 'splitv', 'node has splitv layout');
+is(@{$content[0]->{nodes}}, 2, 'node has two children');
+
+close($fh);
+
+################################################################################
+# two simple vsplit containers in the same file
+################################################################################
+
+$ws = fresh_workspace;
+
+@content = @{get_ws_content($ws)};
+is(@content, 0, 'no nodes on the new workspace yet');
+
+($fh, $filename) = tempfile(UNLINK => 1);
+print $fh <<EOT;
+{
+ "layout": "splitv",
+ "nodes": [
+ {
+ },
+ {
+ }
+ ]
+}
+
+{
+ "layout": "splitv"
+}
+EOT
+$fh->flush;
+cmd "append_layout $filename";
+
+does_i3_live;
+
+@content = @{get_ws_content($ws)};
+is(@content, 2, 'one node on the workspace now');
+is($content[0]->{layout}, 'splitv', 'first node has splitv layout');
+is(@{$content[0]->{nodes}}, 2, 'first node has two children');
+is($content[1]->{layout}, 'splitv', 'second node has splitv layout');
+is(@{$content[1]->{nodes}}, 0, 'first node has no children');
+
+close($fh);
+
+################################################################################
+# simple vsplit layout with swallow specifications
+################################################################################
+
+$ws = fresh_workspace;
+
+@content = @{get_ws_content($ws)};
+is(@content, 0, 'no nodes on the new workspace yet');
+
+($fh, $filename) = tempfile(UNLINK => 1);
+print $fh <<EOT;
+{
+ "layout": "splitv",
+ "nodes": [
+ {
+ "swallows": [
+ {
+ "class": "top"
+ }
+ ]
+ },
+ {
+ "swallows": [
+ {
+ "class": "bottom"
+ }
+ ]
+ }
+ ]
+}
+EOT
+$fh->flush;
+cmd "append_layout $filename";
+
+does_i3_live;
+
+@content = @{get_ws_content($ws)};
+is(@content, 1, 'one node on the workspace now');
+
+my $top = open_window(
+ name => 'top window',
+ wm_class => 'top',
+ instance => 'top',
+);
+
+my $bottom = open_window(
+ name => 'bottom window',
+ wm_class => 'bottom',
+ instance => 'bottom',
+);
+
+@content = @{get_ws_content($ws)};
+is(@content, 1, 'still one node on the workspace now');
+my @nodes = @{$content[0]->{nodes}};
+is($nodes[0]->{name}, 'top window', 'top window on top');
+
+close($fh);
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Test that movement of a con into a branch will place the moving con at the
+# correct position within the branch.
+#
+# If the direction of movement is the same as the orientation of the branch
+# container, append or prepend the container to the branch in the obvious way.
+# If the movement is to the right or downward, insert the moving container in
+# the first position (i.e., the leftmost or top position resp.) If the movement
+# is to the left or upward, insert the moving container in the last position
+# (i.e., the rightmost or bottom position resp.)
+#
+# If the direction of movement is different from the orientation of the branch
+# container, insert the container into the branch after the focused-inactive
+# container.
+#
+# For testing purposes, we will demonstrate the behavior for tabbed containers
+# to represent the case of split-horizontal branches and stacked containers to
+# represent the case of split-vertical branches.
+#
+# Ticket: #1060
+# Bug still in: 4.6-109-g18cfc36
+
+use i3test;
+
+# Opens tabs on the presently focused branch and adds several additional
+# windows. Shifts focus to somewhere in the middle of the tabs so the most
+# general case can be assumed.
+sub open_tabs {
+ cmd 'layout tabbed';
+ open_window;
+ open_window;
+ open_window;
+ open_window;
+ cmd 'focus left; focus left'
+}
+
+# Likewise for a stack
+sub open_stack {
+ cmd 'layout stacking';
+ open_window;
+ open_window;
+ open_window;
+ open_window;
+ cmd 'focus up; focus up'
+}
+
+# Gets the position of the given leaf within the given branch. The first
+# position is one (1). Returns negative one (-1) if the leaf cannot be found
+# within the branch.
+sub get_leaf_position {
+ my ($branch, $leaf) = @_;
+ my $position = -1;
+ for my $i (0 .. @{$branch->{nodes}}) {
+ if ($branch->{nodes}[$i]->{id} == $leaf) {
+ $position = $i + 1;
+ last;
+ };
+ }
+ return $position;
+}
+
+# convenience function to focus a con by id to avoid having to type an ugly
+# command each time
+sub focus_con {
+ my $con_id = shift @_;
+ cmd "[con_id=\"$con_id\"] focus";
+}
+
+# Places a leaf into a branch and focuses the leaf. The newly created branch
+# will have orientation specified by the second parameter.
+sub branchify {
+ my ($con_id, $orientation) = @_;
+ focus_con($con_id);
+ $orientation eq 'horizontal' ? cmd 'splith' : cmd 'splitv';
+ open_window;
+ focus_con($con_id);
+}
+
+##############################################################################
+# When moving a con right into tabs, the moving con should be placed as the
+# first tab in the branch
+##############################################################################
+my $ws = fresh_workspace;
+
+# create the target leaf
+open_window;
+my $target_leaf = get_focused($ws);
+
+# create the tabbed branch container
+open_window;
+cmd 'splith';
+open_tabs;
+
+# move the target leaf into the tabbed branch
+focus_con($target_leaf);
+cmd 'move right';
+
+# the target leaf should be the first in the branch
+my $branch = shift @{get_ws_content($ws)};
+is($branch->{nodes}[0]->{id}, $target_leaf, 'moving con right into tabs placed it as the first tab in the branch');
+
+# repeat the test when the target is in a branch
+cmd 'move up; move left';
+branchify($target_leaf, 'vertical');
+cmd 'move right';
+
+$branch = pop @{get_ws_content($ws)};
+is($branch->{nodes}[0]->{id}, $target_leaf, 'moving con right into tabs from a branch placed it as the first tab in the branch');
+
+##############################################################################
+# When moving a con right into a stack, the moving con should be placed
+# below the focused-inactive leaf
+##############################################################################
+$ws = fresh_workspace;
+
+# create the target leaf
+open_window;
+$target_leaf = get_focused($ws);
+
+# create the stacked branch container and find the focused leaf
+open_window;
+cmd 'splith';
+open_stack;
+my $secondary_leaf = get_focused($ws);
+
+# move the target leaf into the stacked branch
+focus_con($target_leaf);
+cmd 'move right';
+
+# the secondary focus leaf should be below the target
+$branch = shift @{get_ws_content($ws)};
+my $target_leaf_position = get_leaf_position($branch, $target_leaf);
+my $secondary_leaf_position = get_leaf_position($branch, $secondary_leaf);
+
+is($target_leaf_position, $secondary_leaf_position + 1, 'moving con right into a stack placed it below the focused-inactive leaf');
+
+# repeat the test when the target is in a branch
+cmd 'move up; move left';
+branchify($target_leaf, 'vertical');
+cmd 'move right';
+
+$branch = pop @{get_ws_content($ws)};
+$target_leaf_position = get_leaf_position($branch, $target_leaf);
+$secondary_leaf_position = get_leaf_position($branch, $secondary_leaf);
+
+is($target_leaf_position, $secondary_leaf_position + 1, 'moving con right into a stack from a branch placed it below the focused-inactive leaf');
+
+##############################################################################
+# When moving a con down into a stack, the moving con should be placed at the
+# top of the stack
+##############################################################################
+$ws = fresh_workspace;
+cmd 'layout splitv';
+
+# create the target leaf
+open_window;
+$target_leaf = get_focused($ws);
+
+# create the stacked branch container
+open_window;
+cmd 'splitv';
+open_stack;
+
+# move the target leaf into the stacked branch
+focus_con($target_leaf);
+cmd 'move down';
+
+# the target leaf should be on the top of the stack
+$branch = shift @{get_ws_content($ws)};
+is($branch->{nodes}[0]->{id}, $target_leaf, 'moving con down into a stack placed it on the top of the stack');
+
+# repeat the test when the target is in a branch
+cmd 'move right; move up';
+branchify($target_leaf, 'horizontal');
+cmd 'move down';
+
+$branch = pop @{get_ws_content($ws)};
+is($branch->{nodes}[0]->{id}, $target_leaf, 'moving con down into a stack from a branch placed it on the top of the stack');
+
+##############################################################################
+# When moving a con down into tabs, the moving con should be placed after the
+# focused-inactive tab
+##############################################################################
+$ws = fresh_workspace;
+cmd 'layout splitv';
+
+# create the target leaf
+open_window;
+$target_leaf = get_focused($ws);
+
+# create the tabbed branch container and find the focused tab
+open_window;
+cmd 'splitv';
+open_tabs;
+$secondary_leaf = get_focused($ws);
+
+# move the target leaf into the tabbed branch
+focus_con($target_leaf);
+cmd 'move down';
+
+# the secondary focus tab should be to the right
+$branch = shift @{get_ws_content($ws)};
+$target_leaf_position = get_leaf_position($branch, $target_leaf);
+$secondary_leaf_position = get_leaf_position($branch, $secondary_leaf);
+
+is($target_leaf_position, $secondary_leaf_position + 1, 'moving con down into tabs placed it after the focused-inactive tab');
+
+# repeat the test when the target is in a branch
+cmd 'move right; move up';
+branchify($target_leaf, 'horizontal');
+cmd 'move down';
+
+$branch = pop @{get_ws_content($ws)};
+$target_leaf_position = get_leaf_position($branch, $target_leaf);
+$secondary_leaf_position = get_leaf_position($branch, $secondary_leaf);
+
+is($target_leaf_position, $secondary_leaf_position + 1, 'moving con down into tabs from a branch placed it after the focused-inactive tab');
+
+##############################################################################
+# When moving a con left into tabs, the moving con should be placed as the last
+# tab in the branch
+##############################################################################
+$ws = fresh_workspace;
+
+# create the tabbed branch container
+open_window;
+cmd 'splith';
+open_tabs;
+
+# create the target leaf
+cmd 'focus parent';
+open_window;
+$target_leaf = get_focused($ws);
+
+# move the target leaf into the tabbed branch
+cmd 'move left';
+
+# the target leaf should be last in the branch
+$branch = shift @{get_ws_content($ws)};
+
+is($branch->{nodes}->[-1]->{id}, $target_leaf, 'moving con left into tabs placed it as the last tab in the branch');
+
+# repeat the test when the target leaf is in a branch
+cmd 'move up; move right';
+branchify($target_leaf, 'vertical');
+cmd 'move left';
+
+$branch = shift @{get_ws_content($ws)};
+is($branch->{nodes}->[-1]->{id}, $target_leaf, 'moving con left into tabs from a branch placed it as the last tab in the branch');
+
+##############################################################################
+# When moving a con left into a stack, the moving con should be placed below
+# the focused-inactive leaf
+##############################################################################
+$ws = fresh_workspace;
+
+# create the stacked branch container and find the focused leaf
+open_window;
+open_stack;
+$secondary_leaf = get_focused($ws);
+
+# create the target leaf to the right
+cmd 'focus parent';
+open_window;
+$target_leaf = get_focused($ws);
+
+# move the target leaf into the stacked branch
+cmd 'move left';
+
+# the secondary focus leaf should be below
+$branch = shift @{get_ws_content($ws)};
+$target_leaf_position = get_leaf_position($branch, $target_leaf);
+$secondary_leaf_position = get_leaf_position($branch, $secondary_leaf);
+
+is($target_leaf_position, $secondary_leaf_position + 1, 'moving con left into a stack placed it below the focused-inactive leaf');
+
+# repeat the test when the target leaf is in a branch
+cmd 'move up; move right';
+branchify($target_leaf, 'vertical');
+cmd 'move left';
+
+$branch = shift @{get_ws_content($ws)};
+$target_leaf_position = get_leaf_position($branch, $target_leaf);
+$secondary_leaf_position = get_leaf_position($branch, $secondary_leaf);
+
+is($target_leaf_position, $secondary_leaf_position + 1, 'moving con left into a stack from a branch placed it below the focused-inactive leaf');
+
+##############################################################################
+# When moving a con up into a stack, the moving con should be placed last in
+# the stack
+##############################################################################
+$ws = fresh_workspace;
+cmd 'layout splitv';
+
+# create the stacked branch container
+open_window;
+cmd 'splitv';
+open_stack;
+
+# create the target leaf
+cmd 'focus parent';
+open_window;
+$target_leaf = get_focused($ws);
+
+# move the target leaf into the stacked branch
+cmd 'move up';
+
+# the target leaf should be on the bottom of the stack
+$branch = shift @{get_ws_content($ws)};
+
+is($branch->{nodes}->[-1]->{id}, $target_leaf, 'moving con up into stack placed it on the bottom of the stack');
+
+# repeat the test when the target leaf is in a branch
+cmd 'move right; move down';
+branchify($target_leaf, 'horizontal');
+cmd 'move up';
+
+$branch = shift @{get_ws_content($ws)};
+
+is($branch->{nodes}->[-1]->{id}, $target_leaf, 'moving con up into stack from a branch placed it on the bottom of the stack');
+
+##############################################################################
+# When moving a con up into tabs, the moving con should be placed after the
+# focused-inactive tab
+##############################################################################
+$ws = fresh_workspace;
+cmd 'layout splitv';
+
+# create the tabbed branch container and find the focused leaf
+open_window;
+cmd 'splitv';
+open_tabs;
+$secondary_leaf = get_focused($ws);
+
+# create the target leaf below
+cmd 'focus parent';
+open_window;
+$target_leaf = get_focused($ws);
+
+# move the target leaf into the tabbed branch
+cmd 'move up';
+
+# the secondary focus tab should be to the right
+$branch = shift @{get_ws_content($ws)};
+$target_leaf_position = get_leaf_position($branch, $target_leaf);
+$secondary_leaf_position = get_leaf_position($branch, $secondary_leaf);
+
+is($target_leaf_position, $secondary_leaf_position + 1, 'moving con up into tabs placed it after the focused-inactive tab');
+
+# repeat the test when the target leaf is in a branch
+cmd 'move right; move down';
+branchify($target_leaf, 'horizontal');
+cmd 'move up';
+
+$branch = shift @{get_ws_content($ws)};
+$target_leaf_position = get_leaf_position($branch, $target_leaf);
+$secondary_leaf_position = get_leaf_position($branch, $secondary_leaf);
+
+is($target_leaf_position, $secondary_leaf_position + 1, 'moving con up into tabs from a branch placed it after the focused-inactive tab');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Tests all supported criteria for the "swallows" key.
+use i3test;
+use File::Temp qw(tempfile);
+use IO::Handle;
+use X11::XCB qw(PROP_MODE_REPLACE);
+
+sub verify_swallow_criterion {
+ my ($cfgline, $open_window_cb) = @_;
+
+ my $ws = fresh_workspace;
+
+ my @content = @{get_ws_content($ws)};
+ is(@content, 0, "no nodes on the new workspace yet ($cfgline)");
+
+ my ($fh, $filename) = tempfile(UNLINK => 1);
+ print $fh <<EOT;
+{
+ "layout": "splitv",
+ "nodes": [
+ {
+ "swallows": [
+ {
+ $cfgline
+ }
+ ]
+ }
+ ]
+}
+EOT
+ $fh->flush;
+ cmd "append_layout $filename";
+
+ does_i3_live;
+
+ @content = @{get_ws_content($ws)};
+ is(@content, 1, "one node on the workspace now ($cfgline)");
+
+ my $top = $open_window_cb->();
+
+ @content = @{get_ws_content($ws)};
+ is(@content, 1, "still one node on the workspace now ($cfgline)");
+ my @nodes = @{$content[0]->{nodes}};
+ is($nodes[0]->{window}, $top->id, "top window on top ($cfgline)");
+
+ close($fh);
+}
+
+verify_swallow_criterion(
+ '"class": "^special_class$"',
+ sub { open_window(wm_class => 'special_class') }
+);
+
+# Run the same test again to verify that the window is not being swallowed by
+# the first container. Each swallow condition should only swallow precisely one
+# window.
+verify_swallow_criterion(
+ '"class": "^special_class$"',
+ sub { open_window(wm_class => 'special_class') }
+);
+
+verify_swallow_criterion(
+ '"instance": "^special_instance$"',
+ sub { open_window(wm_class => '', instance => 'special_instance') }
+);
+
+verify_swallow_criterion(
+ '"title": "^special_title$"',
+ sub { open_window(name => 'special_title') }
+);
+
+verify_swallow_criterion(
+ '"window_role": "^special_role$"',
+ sub {
+ open_window(
+ name => 'roletest',
+ before_map => sub {
+ my ($window) = @_;
+ my $atomname = $x->atom(name => 'WM_WINDOW_ROLE');
+ my $atomtype = $x->atom(name => 'STRING');
+ $x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 8,
+ length("special_role") + 1,
+ "special_role\x00"
+ );
+ },
+ );
+ }
+);
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Verifies that i3 does not crash when a layout is partially loadable.
+# ticket #1145, bug still present in commit b109b1b20dd51401dc929407453d3acdd8ff5566
+use i3test;
+use File::Temp qw(tempfile);
+use IO::Handle;
+
+################################################################################
+# empty layout file.
+################################################################################
+
+my ($fh, $filename) = tempfile(UNLINK => 1);
+cmd "append_layout $filename";
+
+does_i3_live;
+
+close($fh);
+
+################################################################################
+# file with a superfluous trailing comma
+################################################################################
+
+my $ws = fresh_workspace;
+
+my @content = @{get_ws_content($ws)};
+is(@content, 0, 'no nodes on the new workspace yet');
+
+($fh, $filename) = tempfile(UNLINK => 1);
+print $fh <<'EOT';
+// vim:ts=4:sw=4:et
+{
+ "border": "pixel",
+ "floating": "auto_off",
+ "geometry": {
+ "height": 777,
+ "width": 199,
+ "x": 0,
+ "y": 0
+ },
+ "name": "Buddy List",
+ "percent": 0.116145833333333,
+ "swallows": [
+ {
+ "class": "^Pidgin$",
+ "window_role": "^buddy_list$"
+ }
+ ],
+ "type": "con"
+}
+
+{
+ // splitv split container with 1 children
+ "border": "pixel",
+ "floating": "auto_off",
+ "layout": "splitv",
+ "percent": 0.883854166666667,
+ "swallows": [
+ {}
+ ],
+ "type": "con",
+ "nodes": [
+ {
+ // splitv split container with 2 children
+ "border": "pixel",
+ "floating": "auto_off",
+ "layout": "splitv",
+ "percent": 1,
+ "swallows": [
+ {}
+ ],
+ "type": "con",
+ "nodes": [
+ {
+ "border": "pixel",
+ "floating": "auto_off",
+ "geometry": {
+ "height": 318,
+ "width": 566,
+ "x": 0,
+ "y": 0
+ },
+ "name": "zsh",
+ "percent": 0.5,
+ "swallows": [
+ {
+ "class": "^URxvt$",
+ "instance": "^IRC$",
+ }
+ ],
+ "type": "con"
+ },
+ {
+ "border": "pixel",
+ "floating": "auto_off",
+ "geometry": {
+ "height": 1057,
+ "width": 636,
+ "x": 0,
+ "y": 0
+ },
+ "name": "Michael Stapelberg",
+ "percent": 0.5,
+ "swallows": [
+ {
+ "class": "^Pidgin$",
+ "window_role": "^conversation$"
+ }
+ ],
+ "type": "con"
+ }
+ ]
+ }
+ ]
+}
+
+EOT
+$fh->flush;
+my $reply = cmd "append_layout $filename";
+diag('reply = ' . Dumper($reply));
+
+does_i3_live;
+
+ok(!$reply->[0]->{success}, 'IPC reply did not indicate success');
+
+close($fh);
+
+################################################################################
+# wrong percent key in a child node
+################################################################################
+
+$ws = fresh_workspace;
+
+@content = @{get_ws_content($ws)};
+is(@content, 0, 'no nodes on the new workspace yet');
+
+($fh, $filename) = tempfile(UNLINK => 1);
+print $fh <<'EOT';
+// vim:ts=4:sw=4:et
+{
+ "border": "pixel",
+ "floating": "auto_off",
+ "layout": "splitv",
+ "type": "con",
+ "nodes": [
+ {
+ "border": "pixel",
+ "floating": "auto_off",
+ "geometry": {
+ "height": 318,
+ "width": 566,
+ "x": 0,
+ "y": 0
+ },
+ "name": "zsh",
+ "percent": 0.833333,
+ "swallows": [
+ {
+ "class": "^URxvt$",
+ "instance": "^IRC$"
+ }
+ ],
+ "type": "con"
+ }
+ ]
+}
+
+EOT
+$fh->flush;
+cmd "append_layout $filename";
+
+does_i3_live;
+
+close($fh);
+
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Verifies that i3 removes swallows specifications for split containers.
+# ticket #1149, bug still present in commit 2fea5ef82bd3528ed62681f9ac64f45830f4acdf
+use i3test;
+use File::Temp qw(tempfile);
+use IO::Handle;
+
+my $ws = fresh_workspace;
+
+my @content = @{get_ws_content($ws)};
+is(@content, 0, 'no nodes on the new workspace yet');
+
+my ($fh, $filename) = tempfile(UNLINK => 1);
+print $fh <<'EOT';
+// vim:ts=4:sw=4:et
+{
+ "border": "pixel",
+ "floating": "auto_off",
+ "geometry": {
+ "height": 777,
+ "width": 199,
+ "x": 0,
+ "y": 0
+ },
+ "name": "Buddy List",
+ "percent": 0.116145833333333,
+ "swallows": [
+ {
+ "class": "^Pidgin$",
+ "window_role": "^buddy_list$"
+ }
+ ],
+ "type": "con"
+}
+
+{
+ // splitv split container with 1 children
+ "border": "pixel",
+ "floating": "auto_off",
+ "layout": "splitv",
+ "percent": 0.883854166666667,
+ "swallows": [
+ {}
+ ],
+ "type": "con",
+ "nodes": [
+ {
+ // splitv split container with 2 children
+ "border": "pixel",
+ "floating": "auto_off",
+ "layout": "splitv",
+ "percent": 1,
+ "swallows": [
+ {}
+ ],
+ "type": "con",
+ "nodes": [
+ {
+ "border": "pixel",
+ "floating": "auto_off",
+ "geometry": {
+ "height": 318,
+ "width": 566,
+ "x": 0,
+ "y": 0
+ },
+ "name": "zsh",
+ "percent": 0.5,
+ "swallows": [
+ {
+ "class": "^URxvt$",
+ "instance": "^IRC$"
+ }
+ ],
+ "type": "con"
+ },
+ {
+ "border": "pixel",
+ "floating": "auto_off",
+ "geometry": {
+ "height": 1057,
+ "width": 636,
+ "x": 0,
+ "y": 0
+ },
+ "name": "Michael Stapelberg",
+ "percent": 0.5,
+ "swallows": [
+ {
+ "class": "^Pidgin$",
+ "window_role": "^conversation$"
+ }
+ ],
+ "type": "con"
+ }
+ ]
+ }
+ ]
+}
+
+EOT
+$fh->flush;
+my $reply = cmd "append_layout $filename";
+
+does_i3_live;
+
+ok($reply->[0]->{success}, 'IPC reply indicates success');
+
+my @nodes = @{get_ws_content($ws)};
+
+is_deeply($nodes[0]->{swallows},
+ [
+ {
+ class => '^Pidgin$',
+ window_role => '^buddy_list$',
+ },
+ ],
+ 'swallows specification not parsed correctly');
+
+is_deeply($nodes[1]->{swallows},
+ [],
+ 'swallows specification empty on split container');
+
+my @children = @{$nodes[1]->{nodes}->[0]->{nodes}};
+
+is_deeply($children[0]->{swallows},
+ [
+ {
+ class => '^URxvt$',
+ instance => '^IRC$',
+ },
+ ],
+ 'swallows specification not parsed correctly');
+
+is_deeply($children[1]->{swallows},
+ [
+ {
+ class => '^Pidgin$',
+ window_role => '^conversation$',
+ },
+ ],
+ 'swallows specification not parsed correctly');
+
+close($fh);
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Verifies the _NET_CURRENT_DESKTOP property correctly tracks the currently
+# active workspace. Specifically checks that the property is 0 on startup with
+# an empty config, tracks changes when switching workspaces and when
+# workspaces are created and deleted.
+#
+# The property not being set on startup was last present in commit
+# 6578976b6159437c16187cf8d1ea38aa9fec9fc8.
+
+use i3test i3_autostart => 0;
+use X11::XCB qw(PROP_MODE_REPLACE);
+
+my $config = <<EOT;
+# i3 config file (v4)
+font font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $root = $x->get_root_window;
+# Manually intern _NET_CURRENT_DESKTOP as $x->atom will not create atoms if
+# they are not yet interned.
+my $atom_cookie = $x->intern_atom(0, length("_NET_CURRENT_DESKTOP"), "_NET_CURRENT_DESKTOP");
+my $_NET_CURRENT_DESKTOP = $x->intern_atom_reply($atom_cookie->{sequence})->{atom};
+my $CARDINAL = $x->atom(name => 'CARDINAL')->id;
+
+$x->delete_property($root, $_NET_CURRENT_DESKTOP);
+
+$x->flush();
+
+sub current_desktop_index {
+ # Returns the _NET_CURRENT_DESKTOP property from the root window. This is
+ # the 0 based index of the current desktop.
+
+ my $cookie = $x->get_property(0, $root, $_NET_CURRENT_DESKTOP,
+ $CARDINAL, 0, 1);
+ my $reply = $x->get_property_reply($cookie->{sequence});
+
+ return undef if $reply->{value_len} != 1;
+ return undef if $reply->{format} != 32;
+ return undef if $reply->{type} != $CARDINAL;
+
+ return unpack 'L', $reply->{value};
+}
+
+my $pid = launch_with_config($config);
+
+is(current_desktop_index, 0, "Starting on desktop 0");
+cmd 'workspace 1';
+is(current_desktop_index, 0, "Change from empty to empty");
+open_window;
+cmd 'workspace 0';
+is(current_desktop_index, 0, "Open on 1 and view 0");
+open_window;
+cmd 'workspace 1';
+is(current_desktop_index, 1, "Open on 0 and view 1");
+cmd 'workspace 2';
+is(current_desktop_index, 2, "Open and view empty");
+
+exit_gracefully($pid);
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Make sure floating containers really can't be split.
+# Ticket: #1177
+# Bug still in: 4.7.2-81-g905440d
+use i3test;
+
+my $ws = fresh_workspace;
+my $window = open_floating_window;
+cmd "layout stacked";
+cmd "splitv";
+
+my $floating_con = get_ws($ws)->{floating_nodes}[0]->{nodes}[0];
+
+is(@{$floating_con->{nodes}}, 0, 'floating con is still a leaf');
+
+cmd 'floating disable';
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+
+use i3test;
+
+SKIP: {
+
+ skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
+
+my $i3 = i3(get_socket_path());
+$i3->connect()->recv;
+
+################################
+# Window focus event
+################################
+
+cmd 'split h';
+
+my $win0 = open_window;
+my $win1 = open_window;
+my $win2 = open_window;
+
+my $focus = AnyEvent->condvar;
+
+$i3->subscribe({
+ window => sub {
+ my ($event) = @_;
+ $focus->send($event);
+ }
+})->recv;
+
+my $t;
+$t = AnyEvent->timer(
+ after => 0.5,
+ cb => sub {
+ $focus->send(0);
+ }
+);
+
+# ensure the rightmost window contains input focus
+$i3->command('[id="' . $win2->id . '"] focus')->recv;
+is($x->input_focus, $win2->id, "Window 2 focused");
+
+cmd 'focus left';
+my $event = $focus->recv;
+is($event->{change}, 'focus', 'Focus event received');
+is($focus->recv->{container}->{name}, 'Window 1', 'Window 1 focused');
+
+$focus = AnyEvent->condvar;
+cmd 'focus left';
+$event = $focus->recv;
+is($event->{change}, 'focus', 'Focus event received');
+is($event->{container}->{name}, 'Window 0', 'Window 0 focused');
+
+$focus = AnyEvent->condvar;
+cmd 'focus right';
+$event = $focus->recv;
+is($event->{change}, 'focus', 'Focus event received');
+is($event->{container}->{name}, 'Window 1', 'Window 1 focused');
+
+$focus = AnyEvent->condvar;
+cmd 'focus right';
+$event = $focus->recv;
+is($event->{change}, 'focus', 'Focus event received');
+is($event->{container}->{name}, 'Window 2', 'Window 2 focused');
+
+$focus = AnyEvent->condvar;
+cmd 'focus right';
+$event = $focus->recv;
+is($event->{change}, 'focus', 'Focus event received');
+is($event->{container}->{name}, 'Window 0', 'Window 0 focused');
+
+$focus = AnyEvent->condvar;
+cmd 'focus left';
+$event = $focus->recv;
+is($event->{change}, 'focus', 'Focus event received');
+is($event->{container}->{name}, 'Window 2', 'Window 2 focused');
+
+}
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+
+use i3test;
+
+SKIP: {
+
+ skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
+
+my $i3 = i3(get_socket_path());
+$i3->connect()->recv;
+
+################################
+# Window title event
+################################
+
+my $window = open_window(name => 'Window 0');
+
+my $title = AnyEvent->condvar;
+
+$i3->subscribe({
+ window => sub {
+ my ($event) = @_;
+ $title->send($event);
+ }
+})->recv;
+
+$window->name('New Window Title');
+
+my $t;
+$t = AnyEvent->timer(
+ after => 0.5,
+ cb => sub {
+ $title->send(0);
+ }
+);
+
+my $event = $title->recv;
+is($event->{change}, 'title', 'Window title change event received');
+is($event->{container}->{name}, 'New Window Title', 'Window title changed');
+
+}
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Verifies that windows with properties that indicate they should be floating
+# are indeed opened floating.
+# Ticket: #1182
+# Bug still in: 4.7.2-97-g84fc808
+use i3test;
+use X11::XCB qw(PROP_MODE_REPLACE);
+
+sub open_with_type {
+ my $window_type = shift;
+
+ my $window = open_window(
+ window_type => $x->atom(name => $window_type),
+ );
+ return $window;
+}
+
+sub open_with_state {
+ my $window_state = shift;
+
+ my $window = open_window(
+ before_map => sub {
+ my ($window) = @_;
+
+ my $atomname = $x->atom(name => '_NET_WM_STATE');
+ my $atomtype = $x->atom(name => 'ATOM');
+ $x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 32,
+ 1,
+ pack('L1', $x->atom(name => $window_state)->id),
+ );
+ },
+ );
+
+ return $window;
+}
+
+sub open_with_fixed_size {
+ # The type of the WM_NORMAL_HINTS property is WM_SIZE_HINTS
+ # http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.2.3
+ my $XCB_ICCCM_SIZE_HINT_P_MIN_SIZE = 0x32;
+ my $XCB_ICCCM_SIZE_HINT_P_MAX_SIZE = 0x16;
+
+ my $flags = $XCB_ICCCM_SIZE_HINT_P_MIN_SIZE | $XCB_ICCCM_SIZE_HINT_P_MAX_SIZE;
+
+ my $min_width = 55;
+ my $max_width = 55;
+ my $min_height = 77;
+ my $max_height = 77;
+
+ my $pad = 0x00;
+
+ my $window = open_window(
+ before_map => sub {
+ my ($window) = @_;
+
+ my $atomname = $x->atom(name => 'WM_NORMAL_HINTS');
+ my $atomtype = $x->atom(name => 'WM_SIZE_HINTS');
+ $x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 32,
+ 12,
+ pack('C5N8', $flags, $pad, $pad, $pad, $pad, 0, 0, 0, $min_width, $min_height, $max_width, $max_height),
+ );
+ },
+ );
+
+ return $window;
+}
+
+my $ws = fresh_workspace;
+
+my $window = open_with_type '_NET_WM_WINDOW_TYPE_DIALOG';
+is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window}, $window->id, 'Dialog window opened floating');
+$window->unmap;
+
+$window = open_with_type '_NET_WM_WINDOW_TYPE_UTILITY';
+is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window}, $window->id, 'Utility window opened floating');
+$window->unmap;
+
+$window = open_with_type '_NET_WM_WINDOW_TYPE_TOOLBAR';
+is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window}, $window->id, 'Toolbar window opened floating');
+$window->unmap;
+
+$window = open_with_type '_NET_WM_WINDOW_TYPE_SPLASH';
+is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window}, $window->id, 'Splash window opened floating');
+$window->unmap;
+
+$window = open_with_state '_NET_WM_STATE_MODAL';
+is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window}, $window->id, 'Modal window opened floating');
+$window->unmap;
+
+$window = open_with_fixed_size;
+is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window}, $window->id, 'Fixed size window opened floating');
+$window->unmap;
+
+done_testing;
return $output->{name};
}
+sync_with_i3;
$x->root->warp_pointer(0, 0);
sync_with_i3;
# Setup workspaces so that they stay open (with an empty container).
################################################################################
+sync_with_i3;
$x->root->warp_pointer(0, 0);
sync_with_i3;
my @outputs = @{$tree->{nodes}};
my $fake0 = first { $_->{name} eq 'fake-0' } @outputs;
- my $fake0_content = first { $_->{type} == 2 } @{$fake0->{nodes}};
+ my $fake0_content = first { $_->{type} eq 'con' } @{$fake0->{nodes}};
my $fake1 = first { $_->{name} eq 'fake-1' } @outputs;
- my $fake1_content = first { $_->{type} == 2 } @{$fake1->{nodes}};
+ my $fake1_content = first { $_->{type} eq 'con' } @{$fake1->{nodes}};
my @fake0_workspaces = map { $_->{name} } @{$fake0_content->{nodes}};
my @fake1_workspaces = map { $_->{name} } @{$fake1_content->{nodes}};
my $i3 = i3(get_socket_path());
+sync_with_i3;
$x->root->warp_pointer(0, 0);
sync_with_i3;
# now on the right output (1024x768)
################################################################################
+sync_with_i3;
$x->root->warp_pointer(683 + 10, 0);
sync_with_i3;
my $i3 = i3(get_socket_path(0));
+ sync_with_i3;
$x->root->warp_pointer(0, 0);
sync_with_i3;
my $i3 = i3(get_socket_path(0));
+sync_with_i3;
$x->root->warp_pointer(0, 0);
sync_with_i3;
cmd 'floating toggle';
# Focus screen 1
+sync_with_i3;
$x->root->warp_pointer(1025, 0);
sync_with_i3;
my $s1_ws = fresh_workspace;
my $fourth = open_window;
# Focus screen 2
+sync_with_i3;
$x->root->warp_pointer(0, 769);
sync_with_i3;
my $s2_ws = fresh_workspace;
my $fifth = open_window;
# Focus screen 3
+sync_with_i3;
$x->root->warp_pointer(1025, 769);
sync_with_i3;
my $s3_ws = fresh_workspace;
my @outputs = @{$tree->{nodes}};
my $fake0 = first { $_->{name} eq 'fake-0' } @outputs;
- my $fake0_content = first { $_->{type} == 2 } @{$fake0->{nodes}};
+ my $fake0_content = first { $_->{type} eq 'con' } @{$fake0->{nodes}};
my $fake1 = first { $_->{name} eq 'fake-1' } @outputs;
- my $fake1_content = first { $_->{type} == 2 } @{$fake1->{nodes}};
+ my $fake1_content = first { $_->{type} eq 'con' } @{$fake1->{nodes}};
my @fake0_workspaces = map { $_->{name} } @{$fake0_content->{nodes}};
my @fake1_workspaces = map { $_->{name} } @{$fake1_content->{nodes}};
--- /dev/null
+#
+# Valgrind suppression file for i3 testcases
+#
+# Format specification:
+# http://valgrind.org/docs/manual/manual-core.html#manual-core.suppress
+#
+
+#
+# GLib
+#
+{
+ Ignore fundamental GType registration
+ Memcheck:Leak
+ ...
+ fun:g_type_register_fundamental
+ ...
+}
+
+{
+ Ignore static GType registration
+ Memcheck:Leak
+ match-leak-kinds: possible
+ ...
+ fun:g_type_register_static
+ ...
+}
+
+{
+ Ignore GObject init function
+ Memcheck:Leak
+ match-leak-kinds: possible
+ ...
+ obj:/usr/lib/libgobject-2.0*
+ ...
+ fun:call_init.part.0
+ ...
+}
* @(#)queue.h 8.5 (Berkeley) 8/20/94
*/
-#ifndef _SYS_QUEUE_H_
-#define _SYS_QUEUE_H_
+#pragma once
/*
* This file defines five types of data structures: singly-linked lists,
_Q_INVALIDATE((elm)->field.cqe_prev); \
_Q_INVALIDATE((elm)->field.cqe_next); \
} while (0)
-
-#endif /* !_SYS_QUEUE_H_ */