]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'master' into next
authorMichael Stapelberg <michael@stapelberg.de>
Sat, 15 Mar 2014 17:05:45 +0000 (18:05 +0100)
committerMichael Stapelberg <michael@stapelberg.de>
Sat, 15 Mar 2014 17:05:45 +0000 (18:05 +0100)
158 files changed:
DEPENDS
Makefile
contrib/per-workspace-layout.pl [new file with mode: 0644]
contrib/sticker-7x5cm-stickma.tif.lzma [new file with mode: 0644]
debian/changelog
debian/control
debian/i3-wm.links [new file with mode: 0644]
debian/rules
docs/NoName-2009-03-12/screenshot.png [new file with mode: 0644]
docs/asciidoc-git.conf
docs/hacking-howto
docs/i3bar-protocol
docs/ipc
docs/userguide
i3-config-wizard/main.c
i3-config-wizard/xcb.h
i3-input/i3-input.h
i3-msg/main.c
i3-nagbar/i3-nagbar.h
i3-nagbar/main.c
i3-save-tree [new file with mode: 0755]
i3bar/include/child.h
i3bar/include/common.h
i3bar/include/config.h
i3bar/include/ipc.h
i3bar/include/mode.h
i3bar/include/outputs.h
i3bar/include/parse_json_header.h
i3bar/include/trayclients.h
i3bar/include/util.h
i3bar/include/workspaces.h
i3bar/include/xcb.h
i3bar/src/child.c
i3bar/src/config.c
i3bar/src/ipc.c
i3bar/src/main.c
i3bar/src/mode.c
i3bar/src/outputs.c
i3bar/src/parse_json_header.c
i3bar/src/workspaces.c
i3bar/src/xcb.c
include/all.h
include/assignments.h
include/atoms.xmacro
include/bindings.h [new file with mode: 0644]
include/click.h
include/cmdparse.h
include/commands.h
include/commands_parser.h
include/con.h
include/config.h
include/config_directives.h
include/config_parser.h
include/data.h
include/debug.h
include/display_version.h
include/ewmh.h
include/fake_outputs.h
include/floating.h
include/handlers.h
include/i3.h
include/i3/ipc.h
include/ipc.h
include/key_press.h
include/libi3.h
include/load_layout.h
include/log.h
include/main.h [new file with mode: 0644]
include/manage.h
include/match.h
include/move.h
include/output.h
include/queue.h
include/randr.h
include/regex.h
include/render.h
include/resize.h
include/restore_layout.h [new file with mode: 0644]
include/scratchpad.h
include/sd-daemon.h
include/shmlog.h
include/sighandler.h
include/startup.h
include/tree.h
include/util.h
include/window.h
include/workspace.h
include/x.h
include/xcb.h
include/xcb_compat.h
include/xcursor.h
include/xinerama.h
include/yajl_utils.h
libi3/get_exe_path.c
libi3/ipc_send_message.c
man/i3.man
src/assignments.c
src/bindings.c [new file with mode: 0644]
src/click.c
src/commands.c
src/commands_parser.c
src/con.c
src/config.c
src/config_directives.c
src/config_parser.c
src/display_version.c
src/ewmh.c
src/fake_outputs.c
src/floating.c
src/handlers.c
src/i3.mk
src/ipc.c
src/key_press.c
src/load_layout.c
src/log.c
src/main.c
src/manage.c
src/match.c
src/move.c
src/randr.c
src/resize.c
src/restore_layout.c [new file with mode: 0644]
src/tree.c
src/util.c
src/window.c
src/x.c
src/xcursor.c
src/xinerama.c
testcases/lib/SocketActivation.pm
testcases/lib/i3test.pm
testcases/t/100-fullscreen.t
testcases/t/116-nestedcons.t
testcases/t/117-workspace.t
testcases/t/122-split.t
testcases/t/130-close-empty-split.t
testcases/t/132-move-workspace.t
testcases/t/156-fullscreen-focus.t
testcases/t/185-scratchpad.t
testcases/t/205-ipc-windows.t
testcases/t/213-layout-restore-simple.t [new file with mode: 0644]
testcases/t/213-move-branch-position.t [new file with mode: 0644]
testcases/t/214-layout-restore-criteria.t [new file with mode: 0644]
testcases/t/215-layout-restore-crash.t [new file with mode: 0644]
testcases/t/216-layout-restore-split-swallows.t [new file with mode: 0644]
testcases/t/217-NET_CURRENT_DESKTOP.t [new file with mode: 0644]
testcases/t/218-regress-floating-split.t [new file with mode: 0644]
testcases/t/219-ipc-window-focus.t [new file with mode: 0644]
testcases/t/220-ipc-window-title.t [new file with mode: 0644]
testcases/t/221-floating-type-hints.t [new file with mode: 0644]
testcases/t/502-focus-output.t
testcases/t/503-workspace.t
testcases/t/504-move-workspace-to-output.t
testcases/t/505-scratchpad-resolution.t
testcases/t/506-focus-right.t
testcases/t/510-focus-across-outputs.t
testcases/t/513-move-workspace.t
testcases/valgrind.supp [new file with mode: 0644]
tests/queue.h

diff --git a/DEPENDS b/DEPENDS
index 083372c7ec17e950e7887ea22c9d08c1ae6f3e54..36a241f45c08efa9916e772224cc2a4c165f87be 100644 (file)
--- a/DEPENDS
+++ b/DEPENDS
@@ -32,5 +32,9 @@
  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.
index ff10dcb5012e00d488ba092387e6d01b7a176868..0e2e42db23014add487ceee01779b46240840d41 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -30,7 +30,7 @@ dist: distclean
        [ ! -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
diff --git a/contrib/per-workspace-layout.pl b/contrib/per-workspace-layout.pl
new file mode 100644 (file)
index 0000000..9304b6f
--- /dev/null
@@ -0,0 +1,55 @@
+#!/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
diff --git a/contrib/sticker-7x5cm-stickma.tif.lzma b/contrib/sticker-7x5cm-stickma.tif.lzma
new file mode 100644 (file)
index 0000000..591360c
Binary files /dev/null and b/contrib/sticker-7x5cm-stickma.tif.lzma differ
index f38553639d37113021bc93c7348db0c018c45706..ac79c8bbeab598a9b717c688c3338fd5083bf821 100644 (file)
@@ -1,8 +1,26 @@
-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
 
index 558b01270b748aa80d87882ebb505fe816ae4a29..02b11d49fc9d41e7625dbe119538f33ad0a44a5c 100644 (file)
@@ -38,7 +38,7 @@ Architecture: any
 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
diff --git a/debian/i3-wm.links b/debian/i3-wm.links
new file mode 100644 (file)
index 0000000..376018b
--- /dev/null
@@ -0,0 +1 @@
+usr/share/man/man1/i3.1.gz usr/share/man/man1/i3-with-shmlog.1.gz
index 55c72b514ead804081879a20e9362bf8150c06ce..7036b90d3000bd1edbc0e449dfdb11775f1ac904 100755 (executable)
@@ -38,7 +38,7 @@ override_dh_auto_build:
        $(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
diff --git a/docs/NoName-2009-03-12/screenshot.png b/docs/NoName-2009-03-12/screenshot.png
new file mode 100644 (file)
index 0000000..15a5c7f
Binary files /dev/null and b/docs/NoName-2009-03-12/screenshot.png differ
index cc135ae959a6a87a6fdb6abf3a87ff9581678c9b..ff1ca4487e29f755cfed9a160be7d97e2ab215df 100644 (file)
@@ -647,7 +647,7 @@ endif::doctype-manpage[]
 </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
index bc59eaeb7a3384afab05025f510f992e195f81c6..f4e3f0318f40f91a1e0def7530cac04a6166b64f 100644 (file)
@@ -172,6 +172,10 @@ values will later be pushed to X11 in +src/x.c+.
 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
index bd8ea5365a4a8883d023f9e4c6e2b88d8251626c..927631549fe9969622824b53c4435e6af08b77c1 100644 (file)
@@ -225,7 +225,7 @@ instance::
        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*:
index 85e5e77e3e7966122f2835c716ac31d07ee35aef..7fb9f1a0f88863daa81bc2b49cf67da1b73c4388 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -1,7 +1,7 @@
 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
@@ -140,12 +140,13 @@ VERSION (7)::
 
 === 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
@@ -227,9 +228,9 @@ name (string)::
        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.
@@ -240,7 +241,7 @@ rect (map)::
  {
   "name": "LVDS1",
   "active": true,
-  "current_workspace": 4,
+  "current_workspace": "4",
   "rect": {
    "x": 0,
    "y": 0,
@@ -251,7 +252,7 @@ rect (map)::
  {
   "name": "VGA1",
   "active": true,
-  "current_workspace": 1,
+  "current_workspace": "1",
   "rect": {
    "x": 1280,
    "y": 0,
@@ -277,7 +278,12 @@ name (string)::
        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.
@@ -627,7 +633,8 @@ mode (2)::
        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.
@@ -670,12 +677,12 @@ but will still be present in the "old" property.
  "change": "focus",
  "current": {
   "id": 28489712,
-  "type":4,
+  "type": "workspace",
   ...
  }
  "old": {
   "id": 28489715,
-  "type": 4,
+  "type": "workspace",
   ...
  }
 }
@@ -707,14 +714,14 @@ mode is simply named default.
 === 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:*
 ---------------------------
@@ -722,7 +729,7 @@ window title as "urxvt").
  "change": "new",
  "container": {
   "id": 35569536,
-  "type": 2,
+  "type": "con",
   ...
  }
 }
@@ -756,13 +763,19 @@ know):
 
 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
index acf8192a67f9c6a37665cf7afe8c0d8dd2e92497..f87cdd80faf260939bd9c02e9f78543c8f9525ed 100644 (file)
@@ -166,7 +166,8 @@ hint and are opened in floating mode by default.
 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>>.
 
@@ -727,6 +728,9 @@ client.unfocused::
        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
@@ -749,6 +753,7 @@ client.focused          #4c7899 #285577 #ffffff #2e9ef4
 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
index 09b94841d28430db851e25bf316d94b85de4cad1..bdd012fc114a7c3ae08067d22657f9251f146176 100644 (file)
@@ -299,7 +299,7 @@ static char *rewrite_binding(const char *input) {
 
     /* 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')
@@ -453,7 +453,7 @@ static char *resolve_tilde(const char *path) {
     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);
index 372ed161c775461bf25cffe0535e774194de0eb1..d51f979a84d5c621445d17ccb32337663164e3bc 100644 (file)
@@ -1,5 +1,4 @@
-#ifndef I3_XCB_H
-#define I3_XCB_H
+#pragma once
 
 /* from X11/keysymdef.h */
 #define XCB_NUM_LOCK                    0xff7f
@@ -7,5 +6,3 @@
 #define xmacro(atom) xcb_atom_t A_ ## atom;
 #include "atoms.xmacro"
 #undef xmacro
-
-#endif
index f1d5f077f4cf421d184aae3f6fb897bba8a5fafd..104296cf6945db1267914e8378d26ec4094fad91 100644 (file)
@@ -1,5 +1,4 @@
-#ifndef I3_INPUT
-#define I3_INPUT
+#pragma once
 
 #include <err.h>
 
@@ -13,5 +12,3 @@
 while (0)
 
 extern xcb_window_t root;
-
-#endif
index 935edc0467609a330ce6d3640778ab4f61bd764c..513f28920bf71ea65b337bd4d576dbdc18193167 100644 (file)
@@ -118,24 +118,18 @@ static int reply_map_key_cb(void *params, const unsigned char *keyVal, unsigned
     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;
 
index 379a7f6f7e4b67a49f0a1c0652234447cc258bc1..9aac709b551783c0c3d515402aee1d4102606265 100644 (file)
@@ -1,5 +1,4 @@
-#ifndef I3_NAGBAR
-#define I3_NAGBAR
+#pragma once
 
 #include <err.h>
 
@@ -17,5 +16,3 @@ while (0)
 #undef xmacro
 
 extern xcb_window_t root;
-
-#endif
index 791da97bb5b19ef016e1b63ba90f32b2357cade3..fea2e688c13e8119d70fc1a361d7b541f87fc92e 100644 (file)
@@ -467,7 +467,8 @@ int main(int argc, char *argv[]) {
         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;
diff --git a/i3-save-tree b/i3-save-tree
new file mode 100755 (executable)
index 0000000..53d67e9
--- /dev/null
@@ -0,0 +1,288 @@
+#!/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
index dc244befe83080808246db148fb68ee8b0930ce8..493292692dfb56d9f4f493361f62458f8af60dea 100644 (file)
@@ -7,8 +7,7 @@
  * child.c: Getting Input for the statusline
  *
  */
-#ifndef CHILD_H_
-#define CHILD_H_
+#pragma once
 
 #include <stdbool.h>
 
@@ -79,5 +78,3 @@ void cont_child(void);
  *
  */
 void send_block_clicked(int button, const char *name, const char *instance, int x, int y);
-
-#endif
index cb55e0d679d6f751ef5ed74d4a9bbc1c375ef829..d63780dc70c23732cfcab3b6851686c18fdae268 100644 (file)
@@ -5,8 +5,7 @@
  * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
  */
-#ifndef COMMON_H_
-#define COMMON_H_
+#pragma once
 
 #include <stdbool.h>
 #include <xcb/xcb.h>
@@ -74,5 +73,3 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head;
 #include "config.h"
 #include "libi3.h"
 #include "parse_json_header.h"
-
-#endif
index c648671259e3c1d8d9ddeeaed81ed75fce967f82..e0b0efee1631dc91f2944159cee4246a6fca5f21 100644 (file)
@@ -7,8 +7,7 @@
  * config.c: Parses the configuration (received from i3).
  *
  */
-#ifndef CONFIG_H_
-#define CONFIG_H_
+#pragma once
 
 #include "common.h"
 
@@ -18,6 +17,9 @@ typedef enum {
     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;
@@ -32,8 +34,7 @@ typedef struct config_t {
     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;
@@ -52,5 +53,3 @@ void parse_config_json(char *json);
  *
  */
 void free_colors(struct xcb_color_strings_t *colors);
-
-#endif
index f20d45f0a12463be617551c5d30728a905cc7d94..5de23878d6a31fbfcc3e3d69549312ea120aa6e2 100644 (file)
@@ -7,8 +7,7 @@
  * ipc.c: Communicating with i3
  *
  */
-#ifndef IPC_H_
-#define IPC_H_
+#pragma once
 
 #include <stdint.h>
 
@@ -37,5 +36,3 @@ int i3_send_msg(uint32_t type, const char* payload);
  *
  */
 void subscribe_events(void);
-
-#endif
index a8491aa93d8215d96673b7ee76e58f814bf0ec7f..6c3833f4ccfd05a4ee8267b413f350d65e5c2e57 100644 (file)
@@ -7,8 +7,7 @@
  * 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>
 
@@ -27,5 +26,3 @@ typedef struct mode mode;
  *
  */
 void parse_mode_json(char *json);
-
-#endif
index ad249786d4a805aef37baf943b2b13e04c5fb57a..9f6add11cfcd9737c45d7b66d5bd510f4f8e3c8e 100644 (file)
@@ -7,8 +7,7 @@
  * outputs.c: Maintaining the output-list
  *
  */
-#ifndef OUTPUTS_H_
-#define OUTPUTS_H_
+#pragma once
 
 #include <xcb/xcb.h>
 
@@ -53,5 +52,3 @@ struct i3_output {
 
     SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
 };
-
-#endif
index 79efddc6427acb9061ba39791b47bb5cb4ef2af7..ef13cf78dd7564c4b686e3ad0e9d958f9f581b81 100644 (file)
@@ -8,8 +8,7 @@
  *                      protocol version and features.
  *
  */
-#ifndef PARSE_JSON_HEADER_H_
-#define PARSE_JSON_HEADER_H_
+#pragma once
 
 #include <stdint.h>
 
@@ -22,5 +21,3 @@
  *
  */
 void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed);
-
-#endif
index e1e795fa3080384d7ef1363124fdce93195730df..7a7e537e0027514cc07fc3fdd42e6ba4f3a288a9 100644 (file)
@@ -5,8 +5,7 @@
  * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
  */
-#ifndef TRAYCLIENT_H_
-#define TRAYCLIENT_H_
+#pragma once
 
 #include "common.h"
 
@@ -21,5 +20,3 @@ struct trayclient {
 
     TAILQ_ENTRY(trayclient) tailq;  /* Pointer for the TAILQ-Macro */
 };
-
-#endif
index 468eff3eec59c955107629535e9a70f4558ed95e..9ffd4467f56ceb3f5078e0432b0c569dd3565b60 100644 (file)
@@ -5,8 +5,7 @@
  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
-#ifndef UTIL_H_
-#define UTIL_H_
+#pragma once
 
 #include "queue.h"
 
@@ -36,8 +35,6 @@
     } \
 } while (0)
 
-#endif
-
 /* Securely fee tail-queues */
 #define FREE_TAILQ(l, type) do { \
     type *walk = TAILQ_FIRST(l); \
index 5fe1ba1efc4da6214e02681a98ede5ee206b9653..9e9ecbba817da480d2fe96afb3f42f40987786a9 100644 (file)
@@ -7,8 +7,7 @@
  * workspaces.c: Maintaining the workspace-lists
  *
  */
-#ifndef WORKSPACES_H_
-#define WORKSPACES_H_
+#pragma once
 
 #include <xcb/xproto.h>
 
@@ -42,5 +41,3 @@ struct i3_ws {
 
     TAILQ_ENTRY(i3_ws) tailq;       /* Pointer for the TAILQ-Macro */
 };
-
-#endif
index e1654a3481dee9ae10a9322641405bed25872ec9..2740f330b964b91af4e9be334ebf494517371568 100644 (file)
@@ -7,8 +7,7 @@
  * xcb.c: Communicating with X
  *
  */
-#ifndef XCB_H_
-#define XCB_H_
+#pragma once
 
 #include <stdint.h>
 //#include "outputs.h"
@@ -133,5 +132,3 @@ void redraw_bars(void);
  *
  */
 void set_current_mode(struct mode *mode);
-
-#endif
index 52e99b4091cf959932c8587adec07c823f16bb8c..cfdf911c11442866176ce158293dd2952befcefc 100644 (file)
 #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 */
@@ -81,12 +80,7 @@ static void clear_status_blocks() {
  * `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;
@@ -272,6 +266,8 @@ static int stdin_end_array(void *context) {
 /*
  * 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;
@@ -291,9 +287,7 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
             exit(EXIT_FAILURE);
         }
         if (n == 0) {
-            /* end of file, kill the watcher */
             ELOG("stdin: received EOF\n");
-            cleanup();
             *ret_buffer_len = -1;
             return NULL;
         }
@@ -424,7 +418,7 @@ void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
     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);
 
@@ -450,20 +444,27 @@ void child_write_output(void) {
 /*
  * 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 };
 
@@ -476,43 +477,41 @@ void start_child(char *command) {
     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 */
index 5ac31b1f3a16d53a3a2d2488377b7b22374c62fd..0e6ba3f1b2c7e8687e08235b772b9abf7ba7d51d 100644 (file)
@@ -127,10 +127,6 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
     }
 
     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;
@@ -216,17 +212,10 @@ static int config_boolean_cb(void *params_, int val) {
 
 /* 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,
 };
 
 /*
index 3536b7dcb27f7b9a57fc45eed4edcb5091fd9fcf..6a2c0e625d0972e5d7391e7fc12dca29c5b89485 100644 (file)
@@ -100,9 +100,6 @@ void got_bar_config(char *reply) {
     /* 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);
 }
@@ -164,7 +161,7 @@ void got_bar_config_update(char *event) {
 
     /* 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);
index 9ae69e3c7d4efc12804d08e3eb2c503241851b9f..8c4cbf6dbe3007b115935b7201641fbefe2db5d2 100644 (file)
@@ -112,7 +112,7 @@ int main(int argc, char **argv) {
                 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':
index 7363971a37de4c8c515926f408babae6619b07e4..a34f206004caaceb395a3196f74e5fc2550602b8 100644 (file)
@@ -72,18 +72,9 @@ static int mode_map_key_cb(void *params_, const unsigned char *keyVal, unsigned
 }
 
 /* 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,
 };
 
 /*
index db9867025d86f097e02a06a5c4a14be959b0d8c6..b407df59dd78a83ea99d0ed6058dd15dabbc1d8b 100644 (file)
@@ -248,18 +248,14 @@ static int outputs_map_key_cb(void *params_, const unsigned char *keyVal, unsign
 }
 
 /* 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,
 };
 
 /*
index c09e0f499aaf107bedc0f531dd391483b0dd2622..86afab3a2b64cd0f280837eb55912f6e36223f0d 100644 (file)
@@ -93,20 +93,6 @@ static int header_map_key(void *ctx, const unsigned char *stringval, unsigned in
     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;
@@ -122,6 +108,12 @@ static void child_init(i3bar_child *child) {
  *
  */
 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;
index 5e01b98d8257e011166adcfe04d8f052a425b575..e07a4303d753a2ec82395d6d959b4be5e6595dc4 100644 (file)
@@ -198,18 +198,12 @@ static int workspaces_map_key_cb(void *params_, const unsigned char *keyVal, uns
 }
 
 /* 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,
 };
 
 /*
index 3a9c061b248c10e123bd18f4e7e22c1db312ae4e..928771d8d214d0488f3bbd8e4c968b07f311ba0b 100644 (file)
@@ -417,7 +417,7 @@ void handle_button(xcb_button_press_event_t *event) {
     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++) {
@@ -1524,7 +1524,9 @@ void reconfig_windows(bool redraw_bars) {
                 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;
@@ -1717,7 +1719,7 @@ void draw_bars(bool unhide) {
                           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) {
index c9c4bbbe8acc3b478cdb65ac148a42ae4e540500..a355d3d2b65a8e1ef08bb9a97556942a4b8c12bc 100644 (file)
 #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
index 570375cf1874220a098f3212f9c0ac1faaecb6e9..b83ee03faab0c3252e49c1f625048c417b9bb96c 100644 (file)
@@ -7,8 +7,7 @@
  * 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
@@ -22,5 +21,3 @@ void run_assignments(i3Window *window);
  *
  */
 Assignment *assignment_for(i3Window *window, int type);
-
-#endif
index 41889eb1a0c3f242ff50a40611c32cb1683c2701..86c6e5629d6aa67cdc97891c6405c7b825842006 100644 (file)
@@ -3,6 +3,7 @@ xmacro(_NET_SUPPORTING_WM_CHECK)
 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)
@@ -31,3 +32,4 @@ xmacro(I3_SHMLOG_PATH)
 xmacro(I3_PID)
 xmacro(_NET_REQUEST_FRAME_EXTENTS)
 xmacro(_NET_FRAME_EXTENTS)
+xmacro(_MOTIF_WM_HINTS)
diff --git a/include/bindings.h b/include/bindings.h
new file mode 100644 (file)
index 0000000..2653202
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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);
index 3c4d5288fdb8119a5e98086bdc1af15d1f625153..c63672a7cce954e461ab979350091d71fe62cef2 100644 (file)
@@ -7,8 +7,7 @@
  * 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
@@ -19,5 +18,3 @@
  *
  */
 int handle_button_press(xcb_button_press_event_t *event);
-
-#endif
index 4a87c39c8b266e73ca09002c3b1b4154d4738842..263801f63d6f125de0d90c7713ec42865326e9d2 100644 (file)
@@ -7,9 +7,6 @@
  * 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
index bbf45ba9cbec7095eea20af4385236a1321d026e..e7d2781dbe1f759c2b94681abb9795be0ef37af2 100644 (file)
@@ -7,8 +7,7 @@
  * commands.c: all command functions (see commands_parser.c)
  *
  */
-#ifndef I3_COMMANDS_H
-#define I3_COMMANDS_H
+#pragma once
 
 #include "commands_parser.h"
 
@@ -288,5 +287,3 @@ void cmd_shmlog(I3_CMD, char *argument);
  *
  */
 void cmd_debuglog(I3_CMD, char *argument);
-
-#endif
index 37c4d4b1705459da0894d8ef2f0c4e0390dd9d2e..059237fd3c8f2a7b30abddd446a05f8fba804ad8 100644 (file)
@@ -7,8 +7,7 @@
  * 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>
 
@@ -35,5 +34,3 @@ struct CommandResult {
 };
 
 struct CommandResult *parse_command(const char *input);
-
-#endif
index ec4ae3524d789f0546a7dc8dccc658dc7a0a0266..0205dfc6ea037936161444931ee33177667111db 100644 (file)
@@ -9,8 +9,7 @@
  *        …).
  *
  */
-#ifndef I3_CON_H
-#define I3_CON_H
+#pragma once
 
 /**
  * Create a new container (and attach it to the given parent, if not NULL).
@@ -81,7 +80,7 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation);
  * 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
@@ -193,7 +192,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
  * container).
  *
  */
-int con_orientation(Con *con);
+orientation_t con_orientation(Con *con);
 
 /**
  * Returns the container which will be focused next when the given container
@@ -340,5 +339,3 @@ void con_set_urgency(Con *con, bool urgent);
  *
  */
 char *con_get_tree_representation(Con *con);
-
-#endif
index 4267dcfe3d335875452193578c5196029de9b983..0c3e25db2214fea50b2a75db208a3235de5ab056 100644 (file)
@@ -10,8 +10,7 @@
  * bindings mode).
  *
  */
-#ifndef I3_CONFIG_H
-#define I3_CONFIG_H
+#pragma once
 
 #include <stdbool.h>
 #include "queue.h"
@@ -180,6 +179,7 @@ struct Config {
         struct Colortriple focused_inactive;
         struct Colortriple unfocused;
         struct Colortriple urgent;
+        struct Colortriple placeholder;
     } client;
     struct config_bar {
         struct Colortriple focused;
@@ -308,12 +308,6 @@ struct Barconfig {
  */
 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
@@ -321,12 +315,6 @@ void translate_keysyms(void);
  */
 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
  *
@@ -339,13 +327,6 @@ void switch_mode(const char *new_mode);
  *
  */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.
  *
@@ -356,5 +337,3 @@ Binding *get_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode
  *
  */
 void kill_configerror_nagbar(bool wait_for_it);
-
-#endif
index 9569a7b0af5b1d5904a5405f1bacc9161bb45d79..a95a6473f2f8c9ad1b740e38350db1b076de9daa 100644 (file)
@@ -7,11 +7,16 @@
  * 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
 
@@ -77,5 +82,3 @@ CFGFUN(bar_status_command, const char *command);
 CFGFUN(bar_binding_mode_indicator, const char *value);
 CFGFUN(bar_workspace_buttons, const char *value);
 CFGFUN(bar_finish);
-
-#endif
index fb863f3bf7b8e366af6fb262c133503f116efa0c..6f1b5315af5e0b98989609844dbb14536560e711 100644 (file)
@@ -7,8 +7,7 @@
  * config_parser.h: config parser-related definitions
  *
  */
-#ifndef I3_CONFIG_PARSER_H
-#define I3_CONFIG_PARSER_H
+#pragma once
 
 #include <yajl/yajl_gen.h>
 
@@ -37,5 +36,3 @@ struct ConfigResult *parse_config(const char *input, struct context *context);
  *
  */
 void parse_file(const char *f);
-
-#endif
index ea1d3240d4157938d017cc37195da934cc7db2a5..6fc7b40af7b3cff28841bdcadfb598c6932850fd 100644 (file)
@@ -7,8 +7,7 @@
  * 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>
@@ -214,6 +213,15 @@ struct regex {
  *
  */
 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 {
@@ -374,7 +382,7 @@ struct Match {
     struct regex *class;
     struct regex *instance;
     struct regex *mark;
-    struct regex *role;
+    struct regex *window_role;
     enum {
         U_DONTCHECK = -1,
         U_LATEST = 0,
@@ -450,6 +458,9 @@ struct Assignment {
     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.
  *
@@ -538,7 +549,7 @@ struct Con {
 
     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.
@@ -595,5 +606,3 @@ struct Con {
     /* Depth of the container window */
     uint16_t depth;
 };
-
-#endif
index 44c95c6d348627d9d9305e52360dd183d11d3ead..3e65c35e0eb01b2c08d67d6011256181ba2f3faf 100644 (file)
@@ -8,9 +8,6 @@
  *          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
index 88a1abc1e9cc1f8013d64d0373a927cc35d51896..6f88ae97f18cb2610590a6712ac5954e9ce8a9ed 100644 (file)
@@ -7,8 +7,7 @@
  * 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
@@ -23,5 +22,3 @@
  *
  */
 void display_running_version(void);
-
-#endif
index c36eaeb09bb815a288fd1f0f0aee07aab9e97258..9cc589d124fbda58730f6b6dbb569e530da6c83c 100644 (file)
@@ -7,8 +7,7 @@
  * 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.
@@ -62,5 +61,3 @@ void ewmh_setup_hints(void);
  *
  */
 void ewmh_update_workarea(void);
-
-#endif
index bfeba292470af38e800a553eec644becf3385a2f..75ef77ba8e71f6ea3ecc01f2dacfce1b9e5c6313 100644 (file)
@@ -8,8 +8,7 @@
  * 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.
@@ -19,5 +18,3 @@
  *
  */
 void fake_outputs_init(const char *output_spec);
-
-#endif
index 43600187f60d5e4db3e386a93f7d84e5415e0bde..fa3bdcc3fc132774f512497ad7ccfa89cf5f47c4 100644 (file)
@@ -7,8 +7,7 @@
  * floating.c: Floating windows.
  *
  */
-#ifndef I3_FLOATING_H
-#define I3_FLOATING_H
+#pragma once
 
 #include "tree.h"
 
@@ -183,5 +182,3 @@ void floating_reposition(Con *con, Rect newrect);
  *
  */
 void floating_fix_coordinates(Con *con, Rect *old_rect, Rect *new_rect);
-
-#endif
index b2e7ce2e9288c3751486c22d3f4964734f351297..db7d06b5fec7e172b1b8a488ec0752c7faf0987c 100644 (file)
@@ -8,8 +8,7 @@
  *             …).
  *
  */
-#ifndef I3_HANDLERS_H
-#define I3_HANDLERS_H
+#pragma once
 
 #include <xcb/randr.h>
 
@@ -63,5 +62,3 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state,
                        xcb_window_t window, xcb_atom_t atom,
                        xcb_get_property_reply_t *property);
 #endif
-
-#endif
index 1bc8b55da13bd81a3b5aa6f93a1ee84c98a69016..4ed0d8d54f1c62796503fa5f79f168a7fb2e3738 100644 (file)
@@ -7,8 +7,7 @@
  * 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>
@@ -62,5 +61,3 @@ extern bool xcursor_supported, xkb_supported;
 extern xcb_window_t root;
 extern struct ev_loop *main_loop;
 extern bool only_check_config;
-
-#endif
index 6a50ccc8701fffe7fb68084de093929bed9f5553..94a39904a7d5bb1232142015cd6b3a7e0c915fa3 100644 (file)
@@ -8,8 +8,7 @@
  * 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>
 
@@ -101,5 +100,3 @@ typedef struct i3_ipc_header {
 
 /** Bar config update will be triggered to update the bar config */
 #define I3_IPC_EVENT_BARCONFIG_UPDATE           (I3_IPC_EVENT_MASK | 4)
-
-#endif
index dfb12b9bc3cab1246f8717c9aff31f2244e86d19..2c25b4e9b84114181e6bb36a39135114669f83e4 100644 (file)
@@ -7,8 +7,7 @@
  * 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>
@@ -89,4 +88,8 @@ void dump_node(yajl_gen gen, Con *con, bool inplace_restart);
  */
 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);
index b231b8f5b4d34ba027ff1dc80c0a922c28720fe8..89199ce8314d59e007439bfed30dd0a1c6cee5ec 100644 (file)
@@ -7,8 +7,7 @@
  * 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;
 
@@ -30,5 +29,3 @@ void handle_key_press(xcb_key_press_event_t *event);
  *
  */
 void kill_commanderror_nagbar(bool wait_for_it);
-
-#endif
index 8c580da8388da1a97533e02e4108591349f82df6..3815777b49b713ec6679b6c628949fe6ca69e137 100644 (file)
@@ -8,8 +8,7 @@
  * as i3-msg, i3-config-wizard, …
  *
  */
-#ifndef I3_LIBI3_H
-#define I3_LIBI3_H
+#pragma once
 
 #include <stdbool.h>
 #include <stdarg.h>
@@ -382,5 +381,3 @@ char *get_process_filename(const char *prefix);
  * Returned value must be freed by the caller.
  */
 char *get_exe_path(const char *argv0);
-
-#endif
index 282512b2454e1be458e40d41538a2473817dd6d4..0a5328fafc31f601b12a2d032b089bcf96979ea0 100644 (file)
@@ -8,9 +8,6 @@
  *                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);
index ef4dbd3ce08f618a98965a4e01d6a5f5cd3df205..2400092ba2b64244092e0aba2e5a0eca1f4e3da0 100644 (file)
@@ -7,8 +7,7 @@
  * log.c: Logging functions.
  *
  */
-#ifndef I3_LOG_H
-#define I3_LOG_H
+#pragma once
 
 #include <stdarg.h>
 #include <stdbool.h>
@@ -102,5 +101,3 @@ void verboselog(char *fmt, ...)
  * failures. This function is invoked automatically when exiting.
  */
 void purge_zerobyte_logfile(void);
-
-#endif
diff --git a/include/main.h b/include/main.h
new file mode 100644 (file)
index 0000000..18c6e37
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * 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);
index d50f64d49b67efa9a7f538025edd2f5c09b1100e..bd1a14f76ec4184ad559356bcf6ade3a113e80d3 100644 (file)
@@ -7,8 +7,7 @@
  * manage.c: Initially managing new windows (or existing ones on restart).
  *
  */
-#ifndef I3_MANAGE_H
-#define I3_MANAGE_H
+#pragma once
 
 #include "data.h"
 
@@ -52,4 +51,3 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
                      uint32_t border_width);
 
 #endif
-#endif
index e1d259040dfddbd10546bec6469e8df7a7543ee3..09975cac20fe644162930e08ea25742e1d12644f 100644 (file)
@@ -11,8 +11,7 @@
  * 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
@@ -46,5 +45,3 @@ bool match_matches_window(Match *match, i3Window *window);
  *
  */
 void match_free(Match *match);
-
-#endif
index d45e676e4d52b5e8b8b7c6cef919f17321dea2d9..5c8a7d20f2ad7ce2d0d55271548af987a945b909 100644 (file)
@@ -7,8 +7,7 @@
  * 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,
@@ -16,5 +15,3 @@
  *
  */
 void tree_move(int direction);
-
-#endif
index e87da22e063b1314bffebb349b6764a079006013..10ee7d1740ac4d4f96ee7dca366063713ab06511 100644 (file)
@@ -7,13 +7,10 @@
  * 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
index 0c6852150e5bf60b77333254e17ddd7d5440369b..2307149b6c2529b394eebdf0a3c127b8049f8ea2 100644 (file)
@@ -32,8 +32,7 @@
  *     @(#)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,
@@ -536,5 +535,3 @@ struct {                                                            \
        _Q_INVALIDATE((elm)->field.cqe_prev);                           \
        _Q_INVALIDATE((elm)->field.cqe_next);                           \
 } while (0)
-
-#endif /* !_SYS_QUEUE_H_ */
index dadcfd64912bf55836d3486014582e4ef87da9e1..43f7efe826ae26820842aa5a67fc10ea67a614cc 100644 (file)
@@ -9,8 +9,7 @@
  * (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>
@@ -85,7 +84,7 @@ Output *get_output_by_name(const char *name);
  * 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.
@@ -121,5 +120,3 @@ Output *get_output_next(direction_t direction, Output *current, output_close_far
  *
  */
 Output *get_output_next_wrap(direction_t direction, Output *current);
-
-#endif
index 7403abefb9dda55cf5431fe20c9c5d9f0602775e..e11d377e453ae7d8ddb7db83ce383d62414de226 100644 (file)
@@ -7,8 +7,7 @@
  * 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
@@ -35,5 +34,3 @@ void regex_free(struct regex *regex);
  *
  */
 bool regex_matches(struct regex *regex, const char *input);
-
-#endif
index a0eca01ab72572b6eb278b4a22848e59ed628ed5..1794b513894ea0c8583ff15400e1a66f679f39db 100644 (file)
@@ -8,8 +8,7 @@
  *           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
@@ -25,5 +24,3 @@ void render_con(Con *con, bool render_fullscreen);
  * Returns the height for the decorations
  */
 int render_deco_height(void);
-
-#endif
index ae26ee992a380b4462e714e5b59ec0db7725e5d0..5c795046c5fdd27c3269c1eab3aa01555145a6d5 100644 (file)
@@ -7,11 +7,8 @@
  * 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
diff --git a/include/restore_layout.h b/include/restore_layout.h
new file mode 100644 (file)
index 0000000..3f0229d
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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);
index c6157052ff5c9017d38b057137ba91670ddfa125..1aca73bda56ce466932628b0a5466b363fa3a41d 100644 (file)
@@ -7,8 +7,7 @@
  * 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
@@ -39,5 +38,3 @@ void scratchpad_show(Con *con);
  *
  */
 void scratchpad_fix_resolution(void);
-
-#endif
index 4b853a15bec92fa98785fdb6638cfd2d22f67dd9..8746a3ad9bc2c41364911ff36dfd935369cc194c 100644 (file)
@@ -1,7 +1,6 @@
 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
 
-#ifndef foosddaemonhfoo
-#define foosddaemonhfoo
+#pragma once
 
 /***
   Copyright 2010 Lennart Poettering
@@ -261,5 +260,3 @@ int sd_booted(void) _sd_hidden_;
 #ifdef __cplusplus
 }
 #endif
-
-#endif
index 94da2bdb5065fc3e47c4d5d2ecf4d7f14872812f..5af697e708ff90283585893f5a81df418666282d 100644 (file)
@@ -8,8 +8,7 @@
  * 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>
@@ -43,5 +42,3 @@ typedef struct i3_shmlog_header {
      * tail -f) in an efficient way. */
     pthread_cond_t condvar;
 } i3_shmlog_header;
-
-#endif
index 25d3385bc1e1cfec27e1f5bcd053966ccba6184e..184db73bc7d7d2890668aed32eba46510f252d96 100644 (file)
@@ -9,13 +9,10 @@
  *               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
index e39fe63b08f751dcd86d1cc26bf8c678bd486cff..fb017103bbb68c1c31cbc7a8bbcea9789246d2f8 100644 (file)
@@ -10,8 +10,7 @@
  *            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>
@@ -62,5 +61,3 @@ struct Startup_Sequence *startup_sequence_get(i3Window *cwindow,
  *
  */
 char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply);
-
-#endif
index 2799afee5fdf452b179dd8a597d3a539f40be929..b3c2a515131eef95ef964ab94ffce7a3057ee803 100644 (file)
@@ -7,8 +7,7 @@
  * 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
@@ -105,5 +104,3 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry);
  *
  */
 void tree_flatten(Con *child);
-
-#endif
index 61a38f3ec030537f184cf9a84c1f23b96373efa2..53ea68e4d7699d57deeb7c065614e49c2fc0599f 100644 (file)
@@ -8,8 +8,7 @@
  *         also libi3).
  *
  */
-#ifndef I3_UTIL_H
-#define I3_UTIL_H
+#pragma once
 
 #include <err.h>
 
@@ -141,5 +140,3 @@ void start_nagbar(pid_t *nagbar_pid, char *argv[]);
  *
  */
 void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it);
-
-#endif
index abc27d6aaf69e1ecf10abfdb9b3cda59fac80448..480cee18844362adb707ea3a447828bca216419a 100644 (file)
@@ -7,8 +7,7 @@
  * 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
@@ -63,4 +62,16 @@ void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool befo
  */
 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);
index 907e959f0347e8903eef96bee29498698570f718..463ccf19a8c1723d4704e96896b3610fbaa1bc63 100644 (file)
@@ -8,8 +8,7 @@
  *              workspaces.
  *
  */
-#ifndef I3_WORKSPACE_H
-#define I3_WORKSPACE_H
+#pragma once
 
 #include "data.h"
 #include "tree.h"
@@ -181,4 +180,3 @@ Con *workspace_attach_to(Con *ws);
  * The container inherits the layout from the workspace.
  */
 Con *workspace_encapsulate(Con *ws);
-#endif
index f1fa7e6a7dc3d690bbdb4a5dcc457a5bd568331a..07f8a725cfb3a8e3c52eefbfd94246b28951bd6d 100644 (file)
@@ -8,8 +8,7 @@
  *      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;
@@ -130,5 +129,3 @@ void x_set_warp_to(Rect *rect);
  *
  */
 void x_mask_event_mask(uint32_t mask);
-
-#endif
index 540135694187e9a6d2e4f896ca60844b0d61cdd8..848fe295251ec632f19e61629b645b9c85990a9a 100644 (file)
@@ -7,8 +7,7 @@
  * 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"
@@ -137,5 +136,3 @@ uint16_t get_visual_depth(xcb_visualid_t visual_id);
  *
  */
 xcb_visualid_t get_visualid_by_depth(uint16_t depth);
-
-#endif
index fc09a254323953afb13a7ed76147e0e988f6d719..89c2c457887ed0d660cc6518f5a0efae405fbac9 100644 (file)
@@ -9,8 +9,7 @@
  *               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
@@ -43,5 +45,3 @@
 #define XCB_ATOM_ATOM ATOM
 #define XCB_ATOM_WM_NORMAL_HINTS WM_NORMAL_HINTS
 #define XCB_ATOM_STRING STRING
-
-#endif
index 868fee7823647dadc30a0718269ec792e816ef2c..bb329e4c88ca06cfb90ed1da5c63a3d0a560fb52 100644 (file)
@@ -7,8 +7,7 @@
  * xcursor.c: libXcursor support for themed cursors.
  *
  */
-#ifndef I3_XCURSOR_CURSOR_H
-#define I3_XCURSOR_CURSOR_H
+#pragma once
 
 #include <xcb/xcb_cursor.h>
 
@@ -41,5 +40,3 @@ int xcursor_get_xcb_cursor(enum xcursor_cursor_t c);
  *
  */
 void xcursor_set_root_cursor(int cursor_id);
-
-#endif
index ca7c2ab538a3938b42a6efdaaabc1bcb0833b047..46c2a63558ccc12715ead04d1e91d0732b94e9d6 100644 (file)
@@ -9,8 +9,7 @@
  * driver which does not support RandR in 2011 *sigh*.
  *
  */
-#ifndef I3_XINERAMA_H
-#define I3_XINERAMA_H
+#pragma once
 
 #include "data.h"
 
@@ -20,5 +19,3 @@
  *
  */
 void xinerama_init(void);
-
-#endif
index d8a53d3a16682feb3ba0a8dfd2d282d3fa80a537..cf963073fcbdb3dd40dc0277f941e8c00d4878a3 100644 (file)
@@ -7,8 +7,7 @@
  * 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>
@@ -27,5 +26,3 @@ typedef size_t ylength;
 #define yalloc(callbacks, client) yajl_alloc(callbacks, NULL, NULL, client)
 typedef unsigned int ylength;
 #endif
-
-#endif
index e0437e5e501a727e9e26ebf8f9f4f537e12bd4b8..032ea3dacf60ac799b9c683d25fef892c6b75561 100644 (file)
@@ -30,7 +30,7 @@ char *get_exe_path(const char *argv0) {
 #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);
        }
index c5560c0de9789aef20e27ec667cfd36c7ce0464a..0c360c9c5e47628e3e8becf61e96b47a2eefa7db 100644 (file)
@@ -33,7 +33,7 @@ int ipc_send_message(int sockfd, const uint32_t message_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
index 4358463b09bcba8f283d1530f93de1e3f5ee3e80..de8b60cfdc5b008fdaa2f003b418cccbfd1c28e8 100644 (file)
@@ -175,7 +175,8 @@ You can specify a custom path using the -c option.
 -------------------------------------------------------------
 # 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
index 655816a3bc0ffd1ad464c92a3adbffe0f7c42c26..ea39eafa77e1d038d6b41636ebe43189d036f941 100644 (file)
@@ -28,7 +28,7 @@ void run_assignments(i3Window *window) {
             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;
 
diff --git a/src/bindings.c b/src/bindings.c
new file mode 100644 (file)
index 0000000..5dfe547
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * 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);
+    }
+}
index 340c8414696040313fc30d4231d61580c6e90a11..22e70b9f7954c1445dcbb46faef3259c7522acf8 100644 (file)
@@ -62,12 +62,7 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
         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);
 
@@ -152,15 +147,15 @@ static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click
         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;
@@ -242,9 +237,9 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
             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);
@@ -258,6 +253,12 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
                 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);
@@ -310,7 +311,9 @@ done:
  */
 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;
 
@@ -346,6 +349,11 @@ int handle_button_press(xcb_button_press_event_t *event) {
         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) {
index f73f2bb320e7ca511b2f2843ff7817f34597677a..c0f1d1874a00ff544436ab5b7858f1673437db15 100644 (file)
@@ -339,7 +339,7 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) {
     }
 
     if (strcmp(ctype, "window_role") == 0) {
-        current_match->role = regex_new(cvalue);
+        current_match->window_role = regex_new(cvalue);
         return;
     }
 
@@ -874,11 +874,31 @@ void cmd_nop(I3_CMD, char *comment) {
  */
 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);
 }
 
 /*
index 4f04501c21408d02930e43c4285d9c20f03cc78e..05d39cff53790ef42ed61ab1e896a137f19a3984 100644 (file)
@@ -232,7 +232,7 @@ struct CommandResult *parse_command(const char *input) {
 
     /* 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')
index ba14e06c66028083c46da87a16c5f7ed0dc75562..5a3c88d21ce0bd8dff731ff8e3df44c8a8a40f6e 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -353,7 +353,7 @@ struct bfs_entry {
  * 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
@@ -826,7 +826,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
  * 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 */
index 337b83a84886bbbef4df14a16b90a60be9da3915..a8e66314fbba2eff3e4c912dedb3a254a07df70e 100644 (file)
@@ -30,156 +30,6 @@ void ungrab_all_keys(xcb_connection_t *conn) {
     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
  *
@@ -456,6 +306,9 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
     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");
index b28ad49d3d9c66e68e1b7195b2f4a884225890b2..f5a592f558ca3ae55aa1290c832c7494d4aae7b2 100644 (file)
@@ -55,7 +55,7 @@ CFGFUN(criteria_add, const char *ctype, const char *cvalue) {
     }
 
     if (strcmp(ctype, "window_role") == 0) {
-        current_match->role = regex_new(cvalue);
+        current_match->window_role = regex_new(cvalue);
         return;
     }
 
@@ -130,7 +130,11 @@ static bool eval_boolstr(const char *str) {
             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;
@@ -167,24 +171,8 @@ CFGFUN(font, const char *font) {
        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);
 }
 
 
@@ -192,39 +180,20 @@ CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, co
  * 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) {
index bbd59a4572b08c47eedd6245e637708e9542ce7d..cb21dae7c9c058ad601eaf445470c1f67223a0ec 100644 (file)
@@ -159,7 +159,7 @@ static const char *get_string(const char *identifier) {
     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;
@@ -346,7 +346,7 @@ struct ConfigResult *parse_config(const char *input, struct context *context) {
 
     /* 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')
@@ -585,7 +585,7 @@ struct ConfigResult *parse_config(const char *input, struct context *context) {
             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);
index 427c4ff7d69a52139cd755fbd1355290bd26d5c8..da667ffa30d5a1ebf60e1caf942e28c85d0cce4e 100644 (file)
@@ -42,17 +42,8 @@ static int version_map_key(void *ctx, const unsigned char *stringval, unsigned i
 }
 
 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,
 };
 
 /*
@@ -135,7 +126,7 @@ void display_running_version(void) {
 
     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);
     }
@@ -151,7 +142,7 @@ void display_running_version(void) {
     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);
     }
index 0298de3d4c06ba225751387301e68911d16eaf83..331df5dd3a47cb8ed2a06872841f315f6c26bf61 100644 (file)
@@ -27,6 +27,9 @@ void ewmh_update_current_desktop(void) {
     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);
index e115329952fc14f0a2860276d856b11a8f354ba9..acbc456d99bebe57d15f711d523ed7c43042b16f 100644 (file)
@@ -18,7 +18,7 @@ static int num_screens;
  * 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)
index 10962403b8fa09775f210c8951391029a43ef940..4180ba724903ea7494407016ef9ffdb77a7a6e99 100644 (file)
@@ -535,12 +535,12 @@ void floating_resize_window(Con *con, const bool proportional,
      * 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;
@@ -567,6 +567,104 @@ void floating_resize_window(Con *con, const bool proportional,
         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
@@ -578,17 +676,14 @@ void floating_resize_window(Con *con, const bool proportional,
 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 */
@@ -599,8 +694,9 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
         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;
     }
 
@@ -618,92 +714,39 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
             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;
 }
 
 /*
index fce6627659487199dfe66f1286c4e3da406d6686..8c3bb48665626a24966efe90d6a20e330ef8fff7 100644 (file)
@@ -528,6 +528,17 @@ static void handle_destroy_notify_event(xcb_destroy_notify_event_t *event) {
     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
  *
@@ -538,10 +549,17 @@ static bool handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t
     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;
 }
 
@@ -556,10 +574,17 @@ static bool handle_windowname_change_legacy(void *data, xcb_connection_t *conn,
     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;
 }
 
@@ -1034,7 +1059,7 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom)
     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;
 
index c09610baac03944ede5d7cea3c7813ed7c9bfeaa..c6c30d7a46c3ed0e175225813b9111a63c81cc29 100644 (file)
--- a/src/i3.mk
+++ b/src/i3.mk
@@ -79,6 +79,7 @@ install-i3: i3
        $(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
index 74edc741c2a5ba2a8d27d8e91576d89ddba9998e..82ab14284301aca20d59a3cde1daf5ccd85f1c85 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -153,7 +153,30 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     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");
@@ -283,6 +306,32 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
         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;
@@ -330,16 +379,28 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     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) {
@@ -766,7 +827,6 @@ static int add_subscription(void *extra, const unsigned char *s,
  */
 IPC_HANDLER(subscribe) {
     yajl_handle p;
-    yajl_callbacks callbacks;
     yajl_status stat;
     ipc_client *current, *client = NULL;
 
@@ -785,8 +845,9 @@ IPC_HANDLER(subscribe) {
     }
 
     /* 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);
@@ -1000,3 +1061,32 @@ void ipc_send_workspace_focus_event(Con *current, Con *old) {
     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, "");
+}
index 2f77a2a4da31b56e2bfcb8311ea1f90380e2658a..68e2fca22fa86c40453a55bafaf4bd87c9587495 100644 (file)
@@ -51,17 +51,10 @@ static int json_end_map(void *ctx) {
 }
 
 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,
 };
 
 /*
@@ -91,7 +84,7 @@ void handle_key_press(xcb_key_press_event_t *event) {
     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
@@ -100,7 +93,7 @@ void handle_key_press(xcb_key_press_event_t *event) {
     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
index 1b08f8c17eae72821d8b69499a92f8039b8d7e88..627c1f6eb1bdc46efa79e6c27f5aa4af6d99d51a 100644 (file)
@@ -67,6 +67,23 @@ static int json_start_map(void *ctx) {
 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");
@@ -84,7 +101,12 @@ static int json_end_map(void *ctx) {
 
 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;
@@ -145,12 +167,20 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
 #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));
@@ -189,6 +219,23 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
                 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);
@@ -267,6 +314,7 @@ static int json_int(void *ctx, long long 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;
 
@@ -306,8 +354,8 @@ static int json_int(void *ctx, long 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) {
@@ -347,8 +395,7 @@ static int json_double(void *ctx, double val) {
     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);
@@ -370,16 +417,16 @@ void tree_append_json(const char *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);
@@ -387,21 +434,34 @@ void tree_append_json(const char *filename) {
     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);
index 7bd2e74e5494553e047ba01209feaf52368b46d0..ec44f3ae963804fada28edff9ead570837ba6e05 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -250,7 +250,7 @@ static void vlog(const bool print, const char *fmt, va_list args) {
 
         /* 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();
index 9f92207b0b29d2cabe89bfe4d2c5bba2debb450b..5ff9afe023bda9f17e0cff6b61515838d0afdbb4 100644 (file)
@@ -31,6 +31,10 @@ struct rlimit original_rlimit_core;
 /** The number of file descriptors passed via socket activation. */
 int listen_fds;
 
+/* We keep the xcb_check watcher around to be able to enable and disable it
+ * temporarily for drag_pointer(). */
+static struct ev_check *xcb_check;
+
 static int xkb_event_base;
 
 int xkb_current_group;
@@ -143,6 +147,23 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
     }
 }
 
+/*
+ * 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
@@ -319,11 +340,11 @@ int main(int argc, char *argv[]) {
                 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;
@@ -618,6 +639,8 @@ int main(int argc, char *argv[]) {
         }
     }
 
+    restore_connect();
+
     /* Setup NetWM atoms */
     #define xmacro(name) \
         do { \
@@ -738,9 +761,12 @@ int main(int argc, char *argv[]) {
     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);
index af9e8ef2cbf4d3d0c646dc05b29c37b8041436ea..25d387971163ba6eda22c247476f332a605c1049 100644 (file)
@@ -75,35 +75,6 @@ void restore_geometry(void) {
     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.
  *
@@ -118,7 +89,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     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);
@@ -188,7 +160,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     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);
 
@@ -222,6 +195,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     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);
@@ -234,8 +213,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     /* 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) {
@@ -252,7 +230,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
             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;
@@ -315,9 +293,23 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
         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);
 
@@ -334,13 +326,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     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");
@@ -353,7 +344,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
             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");
@@ -374,15 +365,21 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
     /* 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 &&
@@ -403,7 +400,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
                    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);
@@ -430,6 +427,11 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
         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;
@@ -477,7 +479,14 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     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.
index 350a2c11d0e115091e01ab7af96a31af55e29ebd..de3990fa3f36f1e9f9a533f59181633cd80ece4a 100644 (file)
@@ -47,7 +47,7 @@ bool match_is_empty(Match *match) {
             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 &&
@@ -75,7 +75,7 @@ void match_copy(Match *dest, Match *src) {
     DUPLICATE_REGEX(application);
     DUPLICATE_REGEX(class);
     DUPLICATE_REGEX(instance);
-    DUPLICATE_REGEX(role);
+    DUPLICATE_REGEX(window_role);
 }
 
 /*
@@ -121,9 +121,9 @@ bool match_matches_window(Match *match, i3Window *window) {
         }
     }
 
-    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;
@@ -196,7 +196,7 @@ void match_free(Match *match) {
     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);
@@ -204,5 +204,5 @@ void match_free(Match *match) {
     FREE(match->class);
     FREE(match->instance);
     FREE(match->mark);
-    FREE(match->role);
+    FREE(match->window_role);
 }
index 0c67650c6ef6779502f4df29a0f83189a168dbe0..baca4cef747c290374f3efe8bd3ae8dfb4bc99c9 100644 (file)
@@ -136,7 +136,11 @@ static void move_to_output_directed(Con *con, direction_t direction) {
  *
  */
 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;
 
@@ -184,7 +188,13 @@ void tree_move(int direction) {
                           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)
@@ -225,22 +235,24 @@ void tree_move(int direction) {
     }
 
     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
index 1aef9c9c620c59af800c4a1e972f6dd6cbb0aecb..53a9a1777d8f45d391187e19c0668c16bb24e058 100644 (file)
@@ -77,7 +77,7 @@ Output *get_first_output(void) {
  * 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)
index cc4ba841f8b34377d353ad4eea46787dca3be4dd..61c4ada7e9c1e5329285edc3ad17e1f72527f625 100644 (file)
@@ -102,8 +102,6 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
 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);
@@ -122,19 +120,29 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
     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;
@@ -152,8 +160,12 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
 
     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, &params);
 
     xcb_destroy_window(conn, helpwin);
@@ -164,10 +176,7 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
     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);
 
diff --git a/src/restore_layout.c b/src/restore_layout.c
new file mode 100644 (file)
index 0000000..cdf1551
--- /dev/null
@@ -0,0 +1,350 @@
+#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;
+    }
+}
index 836183ec114439aa2b5c6cac9092abd3b095dd4d..80af522317a543deb0059d22e7534db26e46664c 100644 (file)
@@ -84,7 +84,7 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) {
     };
     focused = croot;
 
-    tree_append_json(globbed);
+    tree_append_json(globbed, NULL);
 
     printf("appended tree, using new root\n");
     croot = TAILQ_FIRST(&(croot->nodes_head));
@@ -260,7 +260,8 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
         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);
@@ -387,7 +388,7 @@ void tree_close_con(kill_window_t kill_window) {
  *
  */
 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;
     }
index e2df3ca9c0c9456f606d237bd90ce18d7a744793..f672cc2e994b1ff86c4d8b9f6eda165609a70d90 100644 (file)
@@ -130,7 +130,7 @@ char *resolve_tilde(const char *path) {
         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);
@@ -224,7 +224,7 @@ char *store_restart_layout(void) {
         return NULL;
     }
 
-    int written = 0;
+    size_t written = 0;
     while (written < length) {
         int n = write(fd, payload + written, length - written);
         /* TODO: correct error-handling */
@@ -242,9 +242,9 @@ char *store_restart_layout(void) {
         }
         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);
index b51be53f9f4e86db4f99692e8a75f6072a5884e6..c3a35cf8ecc93f546ab0b49a55fefbe04f00f376 100644 (file)
@@ -32,7 +32,7 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool bef
     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",
@@ -254,3 +254,59 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
 
     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
+}
diff --git a/src/x.c b/src/x.c
index ac5498d2330d466fe03d60b3c7499c127c47356c..cd36a2834f03412a1b959b46a83125e6f26b7479 100644 (file)
--- a/src/x.c
+++ b/src/x.c
 /* 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;
@@ -232,7 +237,7 @@ void x_con_kill(Con *con) {
     free(state);
 
     /* Invalidate focused_id to correctly focus new windows with the same ID */
-    focused_id = XCB_NONE;
+    focused_id = last_focused = XCB_NONE;
 }
 
 /*
@@ -667,9 +672,24 @@ void x_push_node(Con *con) {
         /* 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);
@@ -731,10 +751,9 @@ void x_push_node(Con *con) {
     }
 
     /* 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;
 
@@ -834,6 +853,24 @@ static void x_push_node_unmaps(Con *con) {
         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.
@@ -958,6 +995,9 @@ void x_push_changes(Con *con) {
                 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) {
@@ -976,9 +1016,12 @@ void x_push_changes(Con *con) {
                 }
 
                 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;
         }
     }
 
@@ -1084,7 +1127,8 @@ void x_set_i3_atoms(void) {
  */
 void x_set_warp_to(Rect *rect)
 {
-    warp_to = rect;
+    if (!config.disable_focus_follows_mouse)
+        warp_to = rect;
 }
 
 /*
index dcbe2ad0d8cd5c0c9b678dab97d394e7e94bcdae..dbaf2e5b1b28b5850e9fdcd9c3a576a2f0a85652 100644 (file)
@@ -61,11 +61,11 @@ void xcursor_set_root_cursor(int cursor_id) {
 }
 
 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];
 }
index 7e5b5aebb4acb60e64379ceed47ed17c0428f42e..b3e51ac535ae8f628f8e52d3bd33a9c35115b5e9 100644 (file)
@@ -22,7 +22,7 @@ static int num_screens;
  * 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)
index 228caaa620b3eb7f05edb8573355bd53907c4ef2..d32f6051d9fa17f4af26e252892f22c71d937c1e 100644 (file)
@@ -110,6 +110,7 @@ sub activate_i3 {
         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|;
         }
index 414362ae1be8414e3f1a75e15a1de46e9734cc0a..d71a6e86cb9ece155cf90a67093cfb7b2aeb6567 100644 (file)
@@ -391,7 +391,7 @@ sub get_workspace_names {
     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 ]
@@ -434,7 +434,7 @@ sub fresh_workspace {
                         @{$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};
@@ -479,7 +479,7 @@ sub get_ws {
     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}});
     }
 
@@ -589,13 +589,13 @@ sub get_dock_clients {
     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);
         }
@@ -645,7 +645,7 @@ sub focused_ws {
     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}
 }
index 54b29c9de331e33ed0b1c348689bff361e2f7a68..206116ee171eed08091e3900ed30331c783a1a1f 100644 (file)
@@ -34,7 +34,7 @@ my @outputs = @{$tree->{nodes}};
 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;
index 84e86879753b1821262e785ae1ae045c6112878e..eb2fe1447007508c6f18c0b22674c60e1b584bfc 100644 (file)
@@ -51,7 +51,7 @@ my $expected = {
     window => undef,
     name => 'root',
     orientation => $ignore,
-    type => 0,
+    type => 'root',
     id => $ignore,
     rect => $ignore,
     window_rect => $ignore,
@@ -90,16 +90,16 @@ my @nodes = @{$tree->{nodes}};
 
 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');
 
index d8a8733ba1cfb3111733a193df87ef401090a47c..04d9b9dd3a58a51ec7acddacfd27aa777d2ad644 100644 (file)
@@ -196,8 +196,7 @@ sub workspace_numbers_sorted {
     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}});
     }
 
index 361716c14d449f534e880d96c0c95b83b25d13d8..6afdd806fc52bbcd82b48430d33b35b50ab55f28 100644 (file)
@@ -145,7 +145,7 @@ sub get_output_content {
     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;
index bcc838969cc0f00eb51d4a7ba97bf542e40a21a1..d14330887ab51e2b0216aa6b62df352a35c5595c 100644 (file)
@@ -53,6 +53,11 @@ cmd 'kill';
 ($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
index 6367488a3b4b6495fe872c356049e7b10bba31d5..1e0a3478efc98e02dfbc5bddaba03000a7953969 100644 (file)
@@ -22,6 +22,7 @@ my $i3 = i3(get_socket_path());
 
 # 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;
 
index 170c641d2e0b918c146738ce4f8965e52903c62d..b337de9a1db1a25db443dbbbdb71177e72d8ced7 100644 (file)
@@ -126,6 +126,7 @@ is($nodes->[0]->{focused}, 1, 'fullscreen window focused');
 cmd 'fullscreen';
 
 # Focus screen 1
+sync_with_i3;
 $x->root->warp_pointer(1025, 0);
 sync_with_i3;
 
@@ -134,6 +135,7 @@ cmd "workspace $tmp";
 my $diff_ws = open_window;
 
 # Focus screen 0
+sync_with_i3;
 $x->root->warp_pointer(0, 0);
 sync_with_i3;
 
index 5901f99cfb6538fc273ac0ccf5ceb404c23eb656..edfc46e0374158eab1eba96380de55f2da92bc3f 100644 (file)
@@ -35,7 +35,7 @@ is($tree->{name}, 'root', 'root node is the first thing we get');
 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');
index aa679e20fdf13d7bfdff6cabd4f5b8770b1a4162..22c11ce290a077607e685e0584833b8579a4fff0 100644 (file)
@@ -30,19 +30,31 @@ $i3->connect()->recv;
 # 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');
 
 }
 
diff --git a/testcases/t/213-layout-restore-simple.t b/testcases/t/213-layout-restore-simple.t
new file mode 100644 (file)
index 0000000..d681b8d
--- /dev/null
@@ -0,0 +1,163 @@
+#!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;
diff --git a/testcases/t/213-move-branch-position.t b/testcases/t/213-move-branch-position.t
new file mode 100644 (file)
index 0000000..c2928c9
--- /dev/null
@@ -0,0 +1,376 @@
+#!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;
diff --git a/testcases/t/214-layout-restore-criteria.t b/testcases/t/214-layout-restore-criteria.t
new file mode 100644 (file)
index 0000000..05e1c9b
--- /dev/null
@@ -0,0 +1,110 @@
+#!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;
diff --git a/testcases/t/215-layout-restore-crash.t b/testcases/t/215-layout-restore-crash.t
new file mode 100644 (file)
index 0000000..4430dac
--- /dev/null
@@ -0,0 +1,191 @@
+#!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;
diff --git a/testcases/t/216-layout-restore-split-swallows.t b/testcases/t/216-layout-restore-split-swallows.t
new file mode 100644 (file)
index 0000000..2e2028a
--- /dev/null
@@ -0,0 +1,160 @@
+#!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;
diff --git a/testcases/t/217-NET_CURRENT_DESKTOP.t b/testcases/t/217-NET_CURRENT_DESKTOP.t
new file mode 100644 (file)
index 0000000..9ea4bd1
--- /dev/null
@@ -0,0 +1,75 @@
+#!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;
diff --git a/testcases/t/218-regress-floating-split.t b/testcases/t/218-regress-floating-split.t
new file mode 100644 (file)
index 0000000..d66adc1
--- /dev/null
@@ -0,0 +1,35 @@
+#!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;
diff --git a/testcases/t/219-ipc-window-focus.t b/testcases/t/219-ipc-window-focus.t
new file mode 100644 (file)
index 0000000..4bacd86
--- /dev/null
@@ -0,0 +1,94 @@
+#!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;
diff --git a/testcases/t/220-ipc-window-title.t b/testcases/t/220-ipc-window-title.t
new file mode 100644 (file)
index 0000000..4c93ab5
--- /dev/null
@@ -0,0 +1,57 @@
+#!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;
diff --git a/testcases/t/221-floating-type-hints.t b/testcases/t/221-floating-type-hints.t
new file mode 100644 (file)
index 0000000..01c73a7
--- /dev/null
@@ -0,0 +1,119 @@
+#!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;
index 8c1c36c5a9f551cb06678b881180ffced0c875d5..cf297f0e2e72ee6ed8df94816deb3664de75156b 100644 (file)
@@ -41,6 +41,7 @@ sub focused_output {
     return $output->{name};
 }
 
+sync_with_i3;
 $x->root->warp_pointer(0, 0);
 sync_with_i3;
 
index c79643e3592705bb98441b7ee5ee10f56691c740..7fe7f15b237953c7c6af3149260973bb3895af1b 100644 (file)
@@ -31,6 +31,7 @@ my $pid = launch_with_config($config);
 # Setup workspaces so that they stay open (with an empty container).
 ################################################################################
 
+sync_with_i3;
 $x->root->warp_pointer(0, 0);
 sync_with_i3;
 
index 018707e6cf5e522a8527b8b6db57b5e4acc2a600..efe0d6e7ab76f0587d429d82dac16cd2233a17e1 100644 (file)
@@ -77,10 +77,10 @@ sub workspaces_per_screen {
     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}};
index de13a544f133dc8622ea05cd6ec9482a83d7ecf4..2235e802fec5354c7e61d335ae8da188190d6c10 100644 (file)
@@ -29,6 +29,7 @@ my $pid = launch_with_config($config);
 
 my $i3 = i3(get_socket_path());
 
+sync_with_i3;
 $x->root->warp_pointer(0, 0);
 sync_with_i3;
 
@@ -76,6 +77,7 @@ verify_scratchpad_doesnt_move($second);
 # now on the right output (1024x768)
 ################################################################################
 
+sync_with_i3;
 $x->root->warp_pointer(683 + 10, 0);
 sync_with_i3;
 
index 3f58b00c2950baf697e6fc9dc737241ebd9330b7..aa0b9c9aee921d982a43458e0d63b7c1a87083dd 100644 (file)
@@ -28,6 +28,7 @@ sub test_focus_left_right {
 
     my $i3 = i3(get_socket_path(0));
 
+    sync_with_i3;
     $x->root->warp_pointer(0, 0);
     sync_with_i3;
 
@@ -140,6 +141,7 @@ my $pid = launch_with_config($config);
 
 my $i3 = i3(get_socket_path(0));
 
+sync_with_i3;
 $x->root->warp_pointer(0, 0);
 sync_with_i3;
 
index 2c6fd396ff5471e0e55c0faee59152978ee81b44..afa0ddef77f3a925eecab1aa70b4816f7288468b 100644 (file)
@@ -38,6 +38,7 @@ my $third = open_window;
 cmd 'floating toggle';
 
 # Focus screen 1
+sync_with_i3;
 $x->root->warp_pointer(1025, 0);
 sync_with_i3;
 my $s1_ws = fresh_workspace;
@@ -45,6 +46,7 @@ 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;
@@ -52,6 +54,7 @@ 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;
index 17c8de71eb6d143288e7931036dbe116f9c450e1..3e27a6c09f070560a26a9902de592ee559f53cb7 100644 (file)
@@ -37,10 +37,10 @@ sub workspaces_per_screen {
     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}};
diff --git a/testcases/valgrind.supp b/testcases/valgrind.supp
new file mode 100644 (file)
index 0000000..150e3a6
--- /dev/null
@@ -0,0 +1,37 @@
+#
+# 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
+    ...
+}
index 75bb957aec793b66a6da6763a8d205f7e3e4fe42..cc129da7db3ae73b3ff8fb44ade668635d1e1bc2 100644 (file)
@@ -32,8 +32,7 @@
  *     @(#)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,
@@ -523,5 +522,3 @@ struct {                                                            \
        _Q_INVALIDATE((elm)->field.cqe_prev);                           \
        _Q_INVALIDATE((elm)->field.cqe_next);                           \
 } while (0)
-
-#endif /* !_SYS_QUEUE_H_ */