]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'next'
authorMichael Stapelberg <michael@stapelberg.de>
Tue, 12 Mar 2013 13:05:47 +0000 (14:05 +0100)
committerMichael Stapelberg <michael@stapelberg.de>
Tue, 12 Mar 2013 13:05:47 +0000 (14:05 +0100)
92 files changed:
.gitignore
DEPENDS
RELEASE-NOTES-4.5 [new file with mode: 0644]
debian/changelog
debian/control
docs/hacking-howto
docs/i3-pod2html
docs/i3bar-protocol
docs/ipc
docs/userguide
i3-config-wizard/cfgparse.l [deleted file]
i3-config-wizard/cfgparse.y [deleted file]
i3-config-wizard/i3-config-wizard.mk
i3-config-wizard/main.c
i3-dmenu-desktop
i3-dump-log/main.c
i3-input/main.c
i3-msg/i3-msg.mk
i3-msg/main.c
i3-nagbar/main.c
i3bar/include/common.h
i3bar/include/xcb.h
i3bar/include/xcb_atoms.def
i3bar/src/child.c
i3bar/src/config.c
i3bar/src/ipc.c
i3bar/src/xcb.c
include/atoms.xmacro
include/con.h
include/config.h
include/config_parser.h
include/data.h
include/i3/ipc.h
include/key_press.h
include/libi3.h
include/randr.h
include/render.h
include/util.h
include/xcb.h
libi3/font.c
libi3/get_process_filename.c [new file with mode: 0644]
libi3/ipc_recv_message.c
libi3/ipc_send_message.c
man/asciidoc.conf
parser-specs/config.spec
src/cfgparse.l [deleted file]
src/cfgparse.y [deleted file]
src/commands.c
src/con.c
src/config.c
src/config_directives.c
src/config_parser.c
src/display_version.c
src/ewmh.c
src/floating.c
src/handlers.c
src/i3.mk
src/ipc.c
src/key_press.c
src/main.c
src/manage.c
src/output.c
src/randr.c
src/render.c
src/scratchpad.c
src/tree.c
src/util.c
src/workspace.c
src/x.c
testcases/.gitignore [new file with mode: 0644]
testcases/complete-run.pl
testcases/lib/SocketActivation.pm
testcases/lib/i3test.pm
testcases/new-test
testcases/t/113-urgent.t
testcases/t/122-split.t
testcases/t/132-move-workspace.t
testcases/t/166-assign.t
testcases/t/173-regress-focus-assign.t
testcases/t/175-startup-notification.t
testcases/t/185-scratchpad.t
testcases/t/189-floating-constraints.t
testcases/t/201-config-parser.t
testcases/t/202-scratchpad-criteria.t [new file with mode: 0644]
testcases/t/204-regress-scratchpad-move.t [new file with mode: 0644]
testcases/t/205-ipc-windows.t [new file with mode: 0644]
testcases/t/503-workspace.t
testcases/t/504-move-workspace-to-output.t
testcases/t/508-move-workspace-focus.t [deleted file]
testcases/t/510-focus-across-outputs.t
testcases/t/512-move-wraps.t [new file with mode: 0644]
testcases/t/513-move-workspace.t [new file with mode: 0644]

index 26c170f2b4e4e0177afd1b171e4b059f83b3aefc..efeb48936443475a822c82f59f0e5eb117369437 100644 (file)
@@ -5,14 +5,6 @@ include/all.h.pch
 *.swp
 *.gcda
 *.gcno
-testcases/testsuite-*
-testcases/latest
-testcases/Makefile
-testcases/Makefile.old
-testcases/.last_run_timings.json
-testcases/_Inline
-testcases/inc
-testcases/META.yml
 test.commands_parser
 test.config_parser
 *.output
@@ -32,3 +24,5 @@ libi3.a
 docs/*.pdf
 docs/*.html
 !/docs/refcard.html
+i3-command-parser.stamp
+i3-config-parser.stamp
diff --git a/DEPENDS b/DEPENDS
index fe9ba17f9eb66ad960ac734d1d7034ec7059a084..4b0902723a179ee6d1a4ed31102b61eeceb78bd0 100644 (file)
--- a/DEPENDS
+++ b/DEPENDS
@@ -10,9 +10,7 @@
 │ pkg-config  │ 0.25   │ 0.26   │ http://pkgconfig.freedesktop.org/      │
 │ libxcb      │ 1.1.93 │ 1.7    │ http://xcb.freedesktop.org/dist/       │
 │ xcb-util    │ 0.3.3  │ 0.3.8  │ http://xcb.freedesktop.org/dist/       │
-│ libev       │ 4.0    │ 4.04   │ http://libev.schmorp.de/               │
-│ flex        │ 2.5.35 │ 2.5.35 │ http://flex.sourceforge.net/           │
-│ bison       │ 2.4.1  │ 2.4.1  │ http://www.gnu.org/software/bison/     │
+│ libev       │ 4.0    │ 4.11   │ http://libev.schmorp.de/               │
 │ yajl        │ 1.0.8  │ 2.0.1  │ http://lloyd.github.com/yajl/          │
 │ asciidoc    │ 8.3.0  │ 8.6.4  │ http://www.methods.co.nz/asciidoc/     │
 │ xmlto       │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/     │
diff --git a/RELEASE-NOTES-4.5 b/RELEASE-NOTES-4.5
new file mode 100644 (file)
index 0000000..1e59a17
--- /dev/null
@@ -0,0 +1,103 @@
+
+ ┌──────────────────────────────┐
+ │ Release notes for i3 v4.5    │
+ └──────────────────────────────┘
+
+This is the i3 v4.5. This version is considered stable. All users of i3 are
+strongly encouraged to upgrade.
+
+Most of the changes are cleanups and bugfixes. Due to cleanups, i3 no longer
+  depends on flex/bison at all. Furthermore, libev ≥ 4 is now a hard dependency
+  (libev < 4 is not supported anymore).
+
+One important change to note is that moving windows to a different output will
+  no longer move focus to that output. If you want to have the old behavior,
+  modify the keybindings for moving in your configfile like this:
+
+    bindsym $mod+Shift+1 move workspace 1; workspace 1
+
+ ┌────────────────────────────┐
+ │ Changes in v4.5            │
+ └────────────────────────────┘
+
+  • docs/hacking-howto: refer people to cr.i3wm.org
+  • docs/ipc: Adds Go IPC lib to the docs.
+  • docs/userguide: remove obsolete sentence about client.background
+  • docs/userguide: be explicit about assignment processing order
+  • docs/userguide: be more clear about the resize command arguments
+  • docs/userguide: fix typo: s/11x/11px/
+  • i3-dmenu-desktop: don’t add “geany” if “Geany” is already present
+  • i3-dmenu-desktop: strip newlines from dmenu ≥ 4.4
+  • i3-dmenu-desktop: skip files with broken utf8 but warn about it
+  • i3-dmenu-desktop: skip broken files (no/empty Exec=) but warn about them
+  • i3-dmenu-desktop: List filenames of .desktop files
+  • i3-dmenu-desktop: remove %i from commandline
+  • i3-nagbar: Work around terminals not supporting -e with quoted arguments
+  • i3-nagbar: use the same font as configured for i3
+  • i3bar: set _NET_SYSTEM_TRAY_COLORS for symbolic icons (gtk3+)
+  • i3bar: don’t use X11 borders to avoid overlapping in hide mode
+  • i3bar: separator color via config; separator width and on/off via ipc
+  • i3bar: Allow min_width of a block in i3bar to be a string
+  • i3-msg: parse command replies and display errors nicely if there were
+    errors
+  • Draw 1px tab separators left/right instead of 2px on the right only
+  • Render tree before destroying X11 containers upon unmap
+  • scratchpad show: move visible scratchpad window from another workspace to
+    focused workspace instead of doing nothing
+  • ignore MotionNotify events generated while warping the pointer
+  • Allow X11 servers which do not support the XKB extension.
+  • remove the urgency indicator when a window is closed
+  • wrap when moving containers to outputs with direction
+  • scratchpad_show: focus unfocused scratchpad window
+  • Split workspace instead of changing orientation
+  • scratchpad: always auto center on 'scratchpad show' if window hasn't been
+    repositioned by the user
+  • Add a new IPC event for changes on windows.
+  • config: accept “smart” as popup_during_fullscreen parameter
+  • Add support for _NET_WM_STATE_DEMANDS_ATTENTION.
+  • Obey WM_SIZE_HINTS's resize increments in floating mode
+  • Do not move focus if a container is moved across outputs
+
+ ┌────────────────────────────┐
+ │ Bugfixes                   │
+ └────────────────────────────┘
+
+  • Ignore ConfigureRequests for scratchpad windows
+  • Correctly parse `move ... workspace *_on_output`
+  • i3bar: Set separator color properly when drawing
+  • Properly parse commands like “move workspace torrent”
+  • Handle nested transient popups properly
+  • Fix decoration rect size for windows without border
+  • parse outputs as "word", not "string", to ignore trailing whitespace
+  • fix crash when disabling output without any windows
+  • scratchpad: fix crash when moving last window of an invisible workspace
+  • fix coordinates of scratchpad windows on output changes
+  • call scratchpad_show() when focusing scratchpad windows via criteria
+  • fix continuous resize bug in floating mode, e.g. with xbmc
+  • fix “overlapping” --release key bindings
+  • fix IPC messages writes with low buffer sizes
+  • unregister as window manager before restarting (fixes a race condition)
+  • Fix bind[code|sym] --release
+  • remove superfluous #include <xcb/xcb_atom.h>
+  • Makefile: Repect AR environment variable
+  • i3-input: restore input focus on exit()
+  • Also draw right tab border for split containers
+  • Fix scrolling on a tabbed titlebar which contains split cons
+  • Correctly close floating windows
+  • handle MapRequests sent between i3 registering as a wm and handling events
+  • i3bar: fake DestroyNotify and send MANAGER ClientMessages to fix tray restarts
+
+ ┌────────────────────────────┐
+ │ Thanks!                    │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+  Adrien Schildknecht, alex, András Mohari, Artem Shinkarov, badboy, bafain,
+  cradle, dcoppa, Donald, dRbiG, eeemsi, else, emias, f8l, Francesco Mazzoli,
+  jasper, joepd, Kacper Kowalik, Kai, knopwob, Marcos, Marius Muja, Mats,
+  MeanEYE, Merovius, oblique, paolo, phlux, Piotr S. Staszewski, pnutzh4x0r,
+  rasi, saurabhgeek92, Sebastian Rachuj, Sebastian Ullrich, slowpoke, Steven
+  Allen, supplantr, Tai-Lin Chu, Tucos, Vivien Didelot, xeen
+
+-- Michael Stapelberg, 2013-03-12
index dcb966aed79ada9febb06ab3200d183efe0d2511..ebdfbcef3ec7369c850fa23966f225ab4b92a40a 100644 (file)
@@ -1,3 +1,9 @@
+i3-wm (4.4.1-0) unstable; urgency=low
+
+  * NOT YET RELEASED
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Wed, 12 Dec 2012 00:23:32 +0100
+
 i3-wm (4.4-1) experimental; urgency=low
 
   * New upstream release
index 02f00de25f2e7cb13c2d2e2250ba7fb040ec1846..f9ecb5582319e63446cddc6c93ed6ff33c5a44b8 100644 (file)
@@ -14,9 +14,7 @@ Build-Depends: debhelper (>= 7.0.50~),
                xmlto,
                docbook-xml,
                pkg-config,
-               libev-dev,
-               flex,
-               bison,
+               libev-dev (>= 1:4.04),
                libyajl-dev,
                libpcre3-dev,
                libstartup-notification0-dev (>= 0.10),
index 8a246efc3efc03c5220c20f6658799d3a0c2af79..633c277165f40b3a4d22315573367f0b2641f31b 100644 (file)
@@ -1,7 +1,7 @@
 Hacking i3: How To
 ==================
-Michael Stapelberg <michael+i3@stapelberg.de>
-July 2011
+Michael Stapelberg <michael@i3wm.org>
+February 2013
 
 This document is intended to be the first thing you read before looking and/or
 touching i3’s source code. It should contain all important information to help
@@ -28,7 +28,8 @@ In the case of i3, the tasks (and order of them) are the following:
   the first client of X) and manage them (reparent them, create window
   decorations, etc.)
 . When new windows are created, manage them
-. Handle the client’s `_WM_STATE` property, but only the `_WM_STATE_FULLSCREEN`
+. Handle the client’s `_WM_STATE` property, but only `_WM_STATE_FULLSCREEN` and
+  `_NET_WM_STATE_DEMANDS_ATTENTION`
 . Handle the client’s `WM_NAME` property
 . Handle the client’s size hints to display them proportionally
 . Handle the client’s urgency hint
@@ -947,31 +948,20 @@ For a short introduction into using git, see
 http://www.spheredev.org/wiki/Git_for_the_lazy or, for more documentation, see
 http://git-scm.com/documentation
 
-When you want to send a patch because you fixed a bug or implemented a cool
-feature (please talk to us before working on features to see whether they are
-maybe already implemented, not possible for some some reason, or don’t fit
-into the concept), please use git to create a patchfile.
+Please talk to us before working on new features to see whether they will be
+accepted. There are a few things which we don’t want to see in i3, e.g. a
+command which will focus windows in an alt+tab like way.
 
-First of all, update your working copy to the latest version of the master
-branch:
+When working on bugfixes, please make sure you mention that you are working on
+it in the corresponding bugreport at http://bugs.i3wm.org/. In case there is no
+bugreport yet, please create one.
 
---------
-git pull
---------
+After you are done, please submit your work for review at http://cr.i3wm.org/
 
-Afterwards, make the necessary changes for your bugfix/feature. Then, review
-the changes using +git diff+ (you might want to enable colors in the diff using
-+git config diff.color auto+).  When you are definitely done, use +git commit
--a+ to commit all changes you’ve made.
-
-Then, use the following command to generate a patchfile which we can directly
-apply to the branch, preserving your commit message and name:
-
------------------------
-git format-patch origin
------------------------
-
-Just send us the generated file via email.
+Do not send emails to the mailing list or any author directly, and don’t submit
+them in the bugtracker, since all reviews should be done in public at
+http://cr.i3wm.org/. In order to make your review go as fast as possible, you
+could have a look at previous reviews and see what the common mistakes are.
 
 == Thought experiments
 
index 56a769f8b8247eb503c77da3a03a25e7cbf8fff0..bda7e8d7dc69902db6a13ec5b24e937ebb034da5 100755 (executable)
@@ -30,7 +30,7 @@ $parser->html_header_before_title(
 <link rel="icon" type="image/png" href="/favicon.png">
 <meta charset="utf-8">
 <meta name="generator" content="Pod::Simple::HTML">
-<meta name="description" content="i3 Perl documentation (testsuite)">
+<meta name="description" content="i3 Perl documentation">
 <link rel="stylesheet" href="http://i3wm.org/css/style.css" type="text/css" />
 <style type="text/css">
 .pod pre {
@@ -81,7 +81,7 @@ $parser->html_header_after_title(
                        </ul>
        <br style="clear: both">
 <div id="content" class="pod">
-<h1>i3 Perl documentation (testsuite)</h1>
+<h1>i3 Perl documentation</h1>
 
 EOF
 );
index 2cf6dd0a18ebb263552e263a080e83887ee89913..9225d97eab134f90b9567095f2cb2eea622ef3e0 100644 (file)
@@ -140,6 +140,10 @@ min_width::
        will be padded to the left and/or the right side, according to the +align+
        key. This is useful when you want to prevent the whole status line to shift
        when value take more or less space between each iteration.
+        The value can also be a string. In this case, the width of the text given
+        by +min_width+ determines the minimum width of the block. This is useful
+        when you want to set a sensible minimum width regardless of which font you
+        are using, and at what particular size.
 align::
        Align text on the +center+ (default), +right+ or +left+ of the block, when
        the minimum width of the latter, specified by the +min_width+ key, is not
@@ -154,6 +158,16 @@ urgent::
        A boolean which specifies whether the current value is urgent. Examples
        are battery charge values below 1 percent or no more available disk
        space (for non-root users). The presentation of urgency is up to i3bar.
+separator::
+       A boolean which specifies whether a separator line should be drawn
+       after this block. The default is true, meaning the separator line will
+       be drawn. Note that if you disable the separator line, there will still
+       be a gap after the block, unless you also use +separator_block_width+.
+separator_block_width::
+       The amount of pixels to leave blank after the block. In the middle of
+       this gap, a separator line will be drawn unless +separator+ is
+       disabled. Normally, you want to set this to an odd value (the default
+       is 9 pixels), since the separator line is drawn in the middle.
 
 If you want to put in your own entries into a block, prefix the key with an
 underscore (_). i3bar will ignore all keys it doesn’t understand, and prefixing
@@ -168,6 +182,17 @@ of the i3bar protocol.
 }
 ------------------------------------------
 
+In the following example, the longest (widest) possible value of the block is
+used to set the minimum width:
+
+------------------------------------------
+{
+ "full_text": "CPU 4%",
+ "min_width": "CPU 100%",
+ "align": "left"
+}
+------------------------------------------
+
 An example of a block which uses all possible entries follows:
 
 *Example*:
@@ -180,6 +205,8 @@ An example of a block which uses all possible entries follows:
  "align": "right",
  "urgent": false,
  "name": "ethernet",
- "instance": "eth0"
+ "instance": "eth0",
+ "separator": true,
+ "separator_block_width": 9
 }
 ------------------------------------------
index 17397548a25f59e20ba8f533c5ac59aee318c43f..8cfb21d035659d2089a4c59a0a3809b205192c7f 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -507,6 +507,8 @@ background::
        Background color of the bar.
 statusline::
        Text color to be used for the statusline.
+separator::
+       Text color to be used for the separator.
 focused_workspace_text/focused_workspace_bg::
        Text color/background color for a workspace button when the workspace
        has focus.
@@ -621,6 +623,9 @@ output (1)::
        outputs, CRTCs or output properties).
 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).
 
 *Example:*
 --------------------------------------------------------------------
@@ -694,6 +699,30 @@ mode is simply named default.
 { "change": "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").
+
+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").
+
+*Example:*
+---------------------------
+{
+ "change": "new",
+ "container": {
+  "id": 35569536,
+  "type": 2,
+  ...
+ }
+}
+---------------------------
+
 == See also (existing libraries)
 
 [[libraries]]
@@ -712,3 +741,5 @@ Perl::
 Python::
        * https://github.com/whitelynx/i3ipc
        * https://github.com/ziberna/i3-py (includes higher-level features)
+Go::
+       * https://github.com/proxypoke/i3ipc
index be3a0b1d01e61eccd67265155e1a0097293d0845..a0f521c27b2613806742b35e494e15d351bea26f 100644 (file)
@@ -1,7 +1,7 @@
 i3 User’s Guide
 ===============
 Michael Stapelberg <michael@i3wm.org>
-August 2012
+February 2013
 
 This document contains all the information you need to configure and use the i3
 window manager. If it does not, please check http://faq.i3wm.org/ first, then
@@ -324,7 +324,7 @@ font pango:[family list] [style options] [size]
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 font pango:DejaVu Sans Mono 10
 font pango:DejaVu Sans Mono, Terminus Bold Semi-Condensed 11
-font pango:Terminus 11x
+font pango:Terminus 11px
 --------------------------------------------------------------
 
 [[keybindings]]
@@ -590,6 +590,10 @@ title change. As i3 will get the title as soon as the application maps the
 window (mapping means actually displaying it on the screen), you’d need to have
 to match on 'Firefox' in this case.
 
+Assignments are processed by i3 in the order in which they appear in the config
+file. The first one which matches the window wins and later assignments are not
+considered.
+
 *Syntax*:
 ------------------------------------------------------------
 assign <criteria> [→] workspace
@@ -732,10 +736,7 @@ client.background color
 -----------------------
 
 Only clients that do not cover the whole area of this window expose the color
-used to paint it. If you use a color other than black for your terminals, you
-most likely want to set the client background color to the same color as your
-terminal program's background color to avoid black gaps between the rendered
-area of the terminal and the i3 border.
+used to paint it.
 
 Colors are in HTML hex format (#rrggbb), see the following example:
 
@@ -1154,6 +1155,8 @@ background::
        Background color of the bar.
 statusline::
        Text color to be used for the statusline.
+separator::
+       Text color to be used for the separator.
 focused_workspace::
        Border, background and text color for a workspace button when the workspace
        has focus.
@@ -1175,6 +1178,7 @@ urgent_workspace::
 colors {
     background <color>
     statusline <color>
+    separator <color>
 
     colorclass <border> <background> <text>
 }
@@ -1186,6 +1190,7 @@ bar {
     colors {
         background #000000
         statusline #ffffff
+        separator #666666
 
         focused_workspace  #4c7899 #285577 #ffffff
         active_workspace   #333333 #5f676a #ffffff
@@ -1612,7 +1617,7 @@ bindsym $mod+r mode "resize"
 Often when in a multi-monitor environment, you want to quickly jump to a
 specific window. For example, while working on workspace 3 you may want to
 jump to your mail client to email your boss that you’ve achieved some
-important goal. Instead of figuring out how to navigate to your mailclient,
+important goal. Instead of figuring out how to navigate to your mail client,
 it would be more convenient to have a shortcut. You can use the +focus+ command
 with criteria for that.
 
diff --git a/i3-config-wizard/cfgparse.l b/i3-config-wizard/cfgparse.l
deleted file mode 100644 (file)
index 772b847..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-%option nounput
-%option noinput
-%option noyy_top_state
-%option stack
-
-%{
-/*
- * vim:ts=8:expandtab
- *
- */
-#include <stdio.h>
-#include <string.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include "cfgparse.tab.h"
-
-int yycolumn = 1;
-
-struct context {
-        int line_number;
-        char *line_copy;
-
-        char *compact_error;
-
-        /* These are the same as in YYLTYPE */
-        int first_column;
-        int last_column;
-};
-
-
-#define YY_DECL int yylex (struct context *context)
-
-#define YY_USER_ACTION { \
-        context->first_column = yycolumn; \
-        context->last_column = yycolumn+yyleng-1; \
-        yycolumn += yyleng; \
-}
-
-%}
-
-EOL    (\r?\n)
-
-%s BINDCODE_COND
-%s BIND_AWS_COND
-%s BIND_A2WS_COND
-%x BUFFER_LINE
-
-%%
-
-       {
-               /* This is called when a new line is lexed. We only want the
-                * first line to match to go into state BUFFER_LINE */
-               if (context->line_number == 0) {
-                       context->line_number = 1;
-                       BEGIN(INITIAL);
-                       yy_push_state(BUFFER_LINE);
-               }
-       }
-
-<BUFFER_LINE>^[^\r\n]*/{EOL}? {
-       /* save whole line */
-       context->line_copy = strdup(yytext);
-
-       yyless(0);
-       yy_pop_state();
-       yy_set_bol(true);
-       yycolumn = 1;
-}
-
-
-<BIND_A2WS_COND>[^\n]+          { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; }
-[0-9]+                          { yylval.number = atoi(yytext); return NUMBER; }
-bind                            { BEGIN(BINDCODE_COND); return TOKBINDCODE; }
-bindcode                        { BEGIN(BINDCODE_COND); return TOKBINDCODE; }
-Mod1                            { yylval.number = (1 << 3); return MODIFIER; }
-Mod2                            { yylval.number = (1 << 4); return MODIFIER; }
-Mod3                            { yylval.number = (1 << 5); return MODIFIER; }
-Mod4                            { yylval.number = (1 << 6); return MODIFIER; }
-Mod5                            { yylval.number = (1 << 7); return MODIFIER; }
-Mode_switch                     { yylval.number = (1 << 8); return MODIFIER; }
-$mod                           { yylval.number = (1 << 9); return TOKMODVAR; }
-control                         { return TOKCONTROL; }
-ctrl                            { return TOKCONTROL; }
-shift                           { return TOKSHIFT; }
-{EOL}                           {
-                                 if (context->line_copy) {
-                                    free(context->line_copy);
-                                   context->line_copy = NULL;
-                                 }
-                                  context->line_number++;
-                                  BEGIN(INITIAL);
-                                  yy_push_state(BUFFER_LINE);
-                                }
-<BINDCODE_COND>[ \t]+           { BEGIN(BIND_AWS_COND); return WHITESPACE; }
-<BIND_AWS_COND>[ \t]+           { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
-[ \t]+                          { return WHITESPACE; }
-.                               { return (int)yytext[0]; }
-
-<<EOF>> {
-        while (yy_start_stack_ptr > 0)
-                yy_pop_state();
-        yyterminate();
-}
-
-%%
diff --git a/i3-config-wizard/cfgparse.y b/i3-config-wizard/cfgparse.y
deleted file mode 100644 (file)
index 17c3953..0000000
+++ /dev/null
@@ -1,208 +0,0 @@
-%{
-/*
- * vim:ts=4:sw=4:expandtab
- *
- */
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <X11/Xlib.h>
-#include <X11/XKBlib.h>
-
-#include "libi3.h"
-
-extern Display *dpy;
-
-struct context {
-        int line_number;
-        char *line_copy;
-
-        char *compact_error;
-
-        /* These are the same as in YYLTYPE */
-        int first_column;
-        int last_column;
-
-        char *result;
-};
-
-typedef struct yy_buffer_state *YY_BUFFER_STATE;
-extern int yylex(struct context *context);
-extern int yyparse(void);
-extern FILE *yyin;
-YY_BUFFER_STATE yy_scan_string(const char *);
-
-static struct context *context;
-static xcb_connection_t *conn;
-static xcb_key_symbols_t *keysyms;
-
-/* We don’t need yydebug for now, as we got decent error messages using
- * yyerror(). Should you ever want to extend the parser, it might be handy
- * to just comment it in again, so it stays here. */
-//int yydebug = 1;
-
-void yyerror(const char *error_message) {
-    fprintf(stderr, "\n");
-    fprintf(stderr, "CONFIG: %s\n", error_message);
-    fprintf(stderr, "CONFIG: line %d:\n",
-        context->line_number);
-    fprintf(stderr, "CONFIG:   %s\n", context->line_copy);
-    fprintf(stderr, "CONFIG:   ");
-    for (int c = 1; c <= context->last_column; c++)
-        if (c >= context->first_column)
-            fprintf(stderr, "^");
-        else fprintf(stderr, " ");
-    fprintf(stderr, "\n");
-    fprintf(stderr, "\n");
-}
-
-int yywrap() {
-    return 1;
-}
-
-char *rewrite_binding(const char *bindingline) {
-    char *result = NULL;
-
-    conn = xcb_connect(NULL, NULL);
-    if (conn == NULL || xcb_connection_has_error(conn)) {
-        fprintf(stderr, "Cannot open display\n");
-        exit(1);
-    }
-    keysyms = xcb_key_symbols_alloc(conn);
-
-    context = calloc(sizeof(struct context), 1);
-
-    yy_scan_string(bindingline);
-
-    if (yyparse() != 0) {
-        fprintf(stderr, "Could not parse configfile\n");
-        exit(1);
-    }
-
-    result = context->result;
-
-    if (context->line_copy)
-        free(context->line_copy);
-    free(context);
-    xcb_key_symbols_free(keysyms);
-    xcb_disconnect(conn);
-
-    return result;
-}
-
-/* XXX: does not work for combinations of modifiers yet */
-static char *modifier_to_string(int modifiers) {
-    //printf("should convert %d to string\n", modifiers);
-    if (modifiers == (1 << 3))
-        return strdup("$mod+");
-    else if (modifiers == ((1 << 3) | (1 << 0)))
-        return strdup("$mod+Shift+");
-    else if (modifiers == (1 << 9))
-        return strdup("$mod+");
-    else if (modifiers == ((1 << 9) | (1 << 0)))
-        return strdup("$mod+Shift+");
-    else if (modifiers == (1 << 0))
-        return strdup("Shift+");
-    else return strdup("");
-}
-
-/*
- * Returns true if sym is bound to any key except for 'except_keycode' on the
- * first four layers (normal, shift, mode_switch, mode_switch + shift).
- *
- */
-static bool keysym_used_on_other_key(KeySym sym, xcb_keycode_t except_keycode) {
-    xcb_keycode_t i,
-                  min_keycode = xcb_get_setup(conn)->min_keycode,
-                  max_keycode = xcb_get_setup(conn)->max_keycode;
-
-    for (i = min_keycode; i && i <= max_keycode; i++) {
-        if (i == except_keycode)
-            continue;
-        for (int level = 0; level < 4; level++) {
-            if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym)
-                continue;
-            return true;
-        }
-    }
-    return false;
-}
-
-%}
-
-%error-verbose
-%lex-param { struct context *context }
-
-%union {
-    int number;
-    char *string;
-}
-
-%token <number>NUMBER "<number>"
-%token <string>STR "<string>"
-%token TOKBINDCODE
-%token TOKMODVAR "$mod"
-%token MODIFIER "<modifier>"
-%token TOKCONTROL "control"
-%token TOKSHIFT "shift"
-%token WHITESPACE "<whitespace>"
-
-%%
-
-lines: /* empty */
-    | lines WHITESPACE bindcode
-    | lines error
-    | lines bindcode
-    ;
-
-bindcode:
-    TOKBINDCODE WHITESPACE binding_modifiers NUMBER WHITESPACE STR
-    {
-        //printf("\tFound keycode binding mod%d with key %d and command %s\n", $<number>3, $4, $<string>6);
-        int level = 0;
-        if (($<number>3 & (1 << 0))) {
-            /* When shift is included, we really need to use the second-level
-             * symbol (upper-case). The lower-case symbol could be on a
-             * different key than the upper-case one (unlikely for letters, but
-             * more likely for special characters). */
-            level = 1;
-
-            /* Try to use the keysym on the first level (lower-case). In case
-             * this doesn’t make it ambiguous (think of a keyboard layout
-             * having '1' on two different keys, but '!' only on keycode 10),
-             * we’ll stick with the keysym of the first level.
-             *
-             * This reduces a lot of confusion for users who switch keyboard
-             * layouts from qwerty to qwertz or other slight variations of
-             * qwerty (yes, that happens quite often). */
-            KeySym sym = XkbKeycodeToKeysym(dpy, $4, 0, 0);
-            if (!keysym_used_on_other_key(sym, $4))
-                level = 0;
-        }
-        KeySym sym = XkbKeycodeToKeysym(dpy, $4, 0, level);
-        char *str = XKeysymToString(sym);
-        char *modifiers = modifier_to_string($<number>3);
-        sasprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $<string>6);
-        free(modifiers);
-    }
-    ;
-
-binding_modifiers:
-    /* NULL */                               { $<number>$ = 0; }
-    | binding_modifier
-    | binding_modifiers '+' binding_modifier { $<number>$ = $<number>1 | $<number>3; }
-    | binding_modifiers '+'                  { $<number>$ = $<number>1; }
-    ;
-
-binding_modifier:
-    MODIFIER        { $<number>$ = $<number>1; }
-    | TOKMODVAR     { $<number>$ = $<number>1; }
-    | TOKCONTROL    { $<number>$ = (1 << 2); }
-    | TOKSHIFT      { $<number>$ = (1 << 0); }
-    ;
index 3847786ece17a46ef5386e3b12147bbab457ba10..e759b4bd8c9fae4c17e85a0367febdbd4b7e3566 100644 (file)
@@ -2,27 +2,18 @@ ALL_TARGETS += i3-config-wizard/i3-config-wizard
 INSTALL_TARGETS += install-i3-config-wizard
 CLEAN_TARGETS += clean-i3-config-wizard
 
-i3_config_wizard_SOURCES_GENERATED  = i3-config-wizard/cfgparse.tab.c i3-config-wizard/cfgparse.yy.c
-i3_config_wizard_SOURCES           := $(filter-out $(i3_config_wizard_SOURCES_GENERATED),$(wildcard i3-config-wizard/*.c))
+i3_config_wizard_SOURCES           := $(wildcard i3-config-wizard/*.c)
 i3_config_wizard_HEADERS           := $(wildcard i3-config-wizard/*.h)
 i3_config_wizard_CFLAGS             = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(X11_CFLAGS) $(PANGO_CFLAGS)
 i3_config_wizard_LIBS               = $(XCB_LIBS) $(XCB_KBD_LIBS) $(X11_LIBS) $(PANGO_LIBS)
 
-i3_config_wizard_OBJECTS := $(i3_config_wizard_SOURCES_GENERATED:.c=.o) $(i3_config_wizard_SOURCES:.c=.o)
+i3_config_wizard_OBJECTS := $(i3_config_wizard_SOURCES:.c=.o)
 
 
-i3-config-wizard/%.o: i3-config-wizard/%.c $(i3_config_wizard_HEADERS)
+i3-config-wizard/%.o: i3-config-wizard/%.c $(i3_config_wizard_HEADERS) i3-config-parser.stamp
        echo "[i3-config-wizard] CC $<"
        $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_config_wizard_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
 
-i3-config-wizard/cfgparse.yy.c: i3-config-wizard/cfgparse.l i3-config-wizard/cfgparse.tab.o $(i3_config_wizard_HEADERS)
-       echo "[i3-config-wizard] LEX $<"
-       $(FLEX) -i -o $@ $<
-
-i3-config-wizard/cfgparse.tab.c: i3-config-wizard/cfgparse.y $(i3_config_wizard_HEADERS)
-       echo "[i3-config-wizard] YACC $<"
-       $(BISON) --debug --verbose -b $(basename $< .y) -d $<
-
 i3-config-wizard/i3-config-wizard: libi3.a $(i3_config_wizard_OBJECTS)
        echo "[i3-config-wizard] Link i3-config-wizard"
        $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_config_wizard_LIBS)
@@ -34,4 +25,4 @@ install-i3-config-wizard: i3-config-wizard/i3-config-wizard
 
 clean-i3-config-wizard:
        echo "[i3-config-wizard] Clean"
-       rm -f $(i3_config_wizard_OBJECTS) $(i3_config_wizard_SOURCES_GENERATED) i3-config-wizard/i3-config-wizard i3-config-wizard/cfgparse.{output,dot,tab.h,y.o}
+       rm -f $(i3_config_wizard_OBJECTS) $(i3_config_wizard_SOURCES_GENERATED) i3-config-wizard/i3-config-wizard i3-config-wizard/cfgparse.*
index 1eb738f631ba3aa34a9dbe62ad08030eb586651a..54c8e0245478d11b97677d8d445e4518d7ee8510 100644 (file)
@@ -36,6 +36,7 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <glob.h>
+#include <assert.h>
 
 #include <xcb/xcb.h>
 #include <xcb/xcb_aux.h>
@@ -44,6 +45,7 @@
 
 #include <X11/Xlib.h>
 #include <X11/keysym.h>
+#include <X11/XKBlib.h>
 
 /* We need SYSCONFDIR for the path to the keycode config template, so raise an
  * error if it’s not defined for whatever reason */
@@ -68,6 +70,7 @@ enum { MOD_Mod1, MOD_Mod4 } modifier = MOD_Mod4;
 static char *config_path;
 static uint32_t xcb_numlock_mask;
 xcb_connection_t *conn;
+static xcb_key_symbols_t *keysyms;
 xcb_screen_t *root_screen;
 static xcb_get_modifier_mapping_reply_t *modmap_reply;
 static i3Font font;
@@ -80,9 +83,342 @@ static xcb_key_symbols_t *symbols;
 xcb_window_t root;
 Display *dpy;
 
-char *rewrite_binding(const char *bindingline);
 static void finish();
 
+#include "GENERATED_config_enums.h"
+
+typedef struct token {
+    char *name;
+    char *identifier;
+    /* This might be __CALL */
+    cmdp_state next_state;
+    union {
+        uint16_t call_identifier;
+    } extra;
+} cmdp_token;
+
+typedef struct tokenptr {
+    cmdp_token *array;
+    int n;
+} cmdp_token_ptr;
+
+
+#include "GENERATED_config_tokens.h"
+
+static cmdp_state state;
+/* A list which contains the states that lead to the current state, e.g.
+ * INITIAL, WORKSPACE_LAYOUT.
+ * When jumping back to INITIAL, statelist_idx will simply be set to 1
+ * (likewise for other states, e.g. MODE or BAR).
+ * This list is used to process the nearest error token. */
+static cmdp_state statelist[10] = { INITIAL };
+/* NB: statelist_idx points to where the next entry will be inserted */
+static int statelist_idx = 1;
+
+struct stack_entry {
+    /* Just a pointer, not dynamically allocated. */
+    const char *identifier;
+    enum {
+        STACK_STR = 0,
+        STACK_LONG = 1,
+    } type;
+    union {
+        char *str;
+        long num;
+    } val;
+};
+
+/* 10 entries should be enough for everybody. */
+static struct stack_entry stack[10];
+
+/*
+ * Pushes a string (identified by 'identifier') on the stack. We simply use a
+ * single array, since the number of entries we have to store is very small.
+ *
+ */
+static void push_string(const char *identifier, const char *str) {
+    for (int c = 0; c < 10; c++) {
+        if (stack[c].identifier != NULL &&
+            strcmp(stack[c].identifier, identifier) != 0)
+            continue;
+        if (stack[c].identifier == NULL) {
+            /* Found a free slot, let’s store it here. */
+            stack[c].identifier = identifier;
+            stack[c].val.str = sstrdup(str);
+            stack[c].type = STACK_STR;
+        } else {
+            /* Append the value. */
+            char *prev = stack[c].val.str;
+            sasprintf(&(stack[c].val.str), "%s,%s", prev, str);
+            free(prev);
+        }
+        return;
+    }
+
+    /* When we arrive here, the stack is full. This should not happen and
+     * means there’s either a bug in this parser or the specification
+     * contains a command with more than 10 identified tokens. */
+    fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
+                    "in the code, or a new command which contains more than "
+                    "10 identified tokens.\n");
+    exit(1);
+}
+
+static void push_long(const char *identifier, long num) {
+    for (int c = 0; c < 10; c++) {
+        if (stack[c].identifier != NULL)
+            continue;
+        /* Found a free slot, let’s store it here. */
+        stack[c].identifier = identifier;
+        stack[c].val.num = num;
+        stack[c].type = STACK_LONG;
+        return;
+    }
+
+    /* When we arrive here, the stack is full. This should not happen and
+     * means there’s either a bug in this parser or the specification
+     * contains a command with more than 10 identified tokens. */
+    fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
+                    "in the code, or a new command which contains more than "
+                    "10 identified tokens.\n");
+    exit(1);
+
+}
+
+static const char *get_string(const char *identifier) {
+    for (int c = 0; c < 10; c++) {
+        if (stack[c].identifier == NULL)
+            break;
+        if (strcmp(identifier, stack[c].identifier) == 0)
+            return stack[c].val.str;
+    }
+    return NULL;
+}
+
+
+static void clear_stack(void) {
+    for (int c = 0; c < 10; c++) {
+        if (stack[c].type == STACK_STR && stack[c].val.str != NULL)
+            free(stack[c].val.str);
+        stack[c].identifier = NULL;
+        stack[c].val.str = NULL;
+        stack[c].val.num = 0;
+    }
+}
+
+/*
+ * Returns true if sym is bound to any key except for 'except_keycode' on the
+ * first four layers (normal, shift, mode_switch, mode_switch + shift).
+ *
+ */
+static bool keysym_used_on_other_key(KeySym sym, xcb_keycode_t except_keycode) {
+    xcb_keycode_t i,
+                  min_keycode = xcb_get_setup(conn)->min_keycode,
+                  max_keycode = xcb_get_setup(conn)->max_keycode;
+
+    for (i = min_keycode; i && i <= max_keycode; i++) {
+        if (i == except_keycode)
+            continue;
+        for (int level = 0; level < 4; level++) {
+            if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym)
+                continue;
+            return true;
+        }
+    }
+    return false;
+}
+
+
+static char *next_state(const cmdp_token *token) {
+    cmdp_state _next_state = token->next_state;
+
+    if (token->next_state == __CALL) {
+        const char *modifiers = get_string("modifiers");
+        int keycode = atoi(get_string("key"));
+        int level = 0;
+        if (modifiers != NULL &&
+            strstr(modifiers, "Shift") != NULL) {
+            /* When shift is included, we really need to use the second-level
+             * symbol (upper-case). The lower-case symbol could be on a
+             * different key than the upper-case one (unlikely for letters, but
+             * more likely for special characters). */
+            level = 1;
+
+            /* Try to use the keysym on the first level (lower-case). In case
+             * this doesn’t make it ambiguous (think of a keyboard layout
+             * having '1' on two different keys, but '!' only on keycode 10),
+             * we’ll stick with the keysym of the first level.
+             *
+             * This reduces a lot of confusion for users who switch keyboard
+             * layouts from qwerty to qwertz or other slight variations of
+             * qwerty (yes, that happens quite often). */
+            KeySym sym = XkbKeycodeToKeysym(dpy, keycode, 0, 0);
+            if (!keysym_used_on_other_key(sym, keycode))
+                level = 0;
+        }
+        KeySym sym = XkbKeycodeToKeysym(dpy, keycode, 0, level);
+        char *str = XKeysymToString(sym);
+        const char *release = get_string("release");
+        char *res;
+        char *modrep = (modifiers == NULL ? sstrdup("") : sstrdup(modifiers));
+        char *comma;
+        while ((comma = strchr(modrep, ',')) != NULL) {
+            *comma = '+';
+        }
+        sasprintf(&res, "bindsym %s%s%s %s%s\n", (modifiers == NULL ? "" : modrep), (modifiers == NULL ? "" : "+"), str, (release == NULL ? "" : release), get_string("command"));
+        clear_stack();
+        return res;
+    }
+
+    state = _next_state;
+
+    /* See if we are jumping back to a state in which we were in previously
+     * (statelist contains INITIAL) and just move statelist_idx accordingly. */
+    for (int i = 0; i < statelist_idx; i++) {
+        if (statelist[i] != _next_state)
+            continue;
+        statelist_idx = i+1;
+        return NULL;
+    }
+
+    /* Otherwise, the state is new and we add it to the list */
+    statelist[statelist_idx++] = _next_state;
+    return NULL;
+}
+
+
+static char *rewrite_binding(const char *input) {
+    state = INITIAL;
+    statelist_idx = 1;
+
+    const char *walk = input;
+    const size_t len = strlen(input);
+    int c;
+    const cmdp_token *token;
+    char *result = NULL;
+
+    /* The "<=" operator is intentional: We also handle the terminating 0-byte
+     * explicitly by looking for an 'end' token. */
+    while ((walk - input) <= len) {
+        /* Skip whitespace before every token, newlines are relevant since they
+         * separate configuration directives. */
+        while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
+            walk++;
+
+               //printf("remaining input: %s\n", walk);
+
+        cmdp_token_ptr *ptr = &(tokens[state]);
+        for (c = 0; c < ptr->n; c++) {
+            token = &(ptr->array[c]);
+
+            /* A literal. */
+            if (token->name[0] == '\'') {
+                if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
+                    if (token->identifier != NULL)
+                        push_string(token->identifier, token->name + 1);
+                    walk += strlen(token->name) - 1;
+                    if ((result = next_state(token)) != NULL)
+                        return result;
+                    break;
+                }
+                continue;
+            }
+
+            if (strcmp(token->name, "number") == 0) {
+                /* Handle numbers. We only accept decimal numbers for now. */
+                char *end = NULL;
+                errno = 0;
+                long int num = strtol(walk, &end, 10);
+                if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) ||
+                    (errno != 0 && num == 0))
+                    continue;
+
+                /* No valid numbers found */
+                if (end == walk)
+                    continue;
+
+                if (token->identifier != NULL)
+                    push_long(token->identifier, num);
+
+                /* Set walk to the first non-number character */
+                walk = end;
+                if ((result = next_state(token)) != NULL)
+                    return result;
+                break;
+            }
+
+            if (strcmp(token->name, "string") == 0 ||
+                strcmp(token->name, "word") == 0) {
+                const char *beginning = walk;
+                /* Handle quoted strings (or words). */
+                if (*walk == '"') {
+                    beginning++;
+                    walk++;
+                    while (*walk != '\0' && (*walk != '"' || *(walk-1) == '\\'))
+                        walk++;
+                } else {
+                    if (token->name[0] == 's') {
+                        while (*walk != '\0' && *walk != '\r' && *walk != '\n')
+                            walk++;
+                    } else {
+                        /* For a word, the delimiters are white space (' ' or
+                         * '\t'), closing square bracket (]), comma (,) and
+                         * semicolon (;). */
+                        while (*walk != ' ' && *walk != '\t' &&
+                               *walk != ']' && *walk != ',' &&
+                               *walk !=  ';' && *walk != '\r' &&
+                               *walk != '\n' && *walk != '\0')
+                            walk++;
+                    }
+                }
+                if (walk != beginning) {
+                    char *str = scalloc(walk-beginning + 1);
+                    /* We copy manually to handle escaping of characters. */
+                    int inpos, outpos;
+                    for (inpos = 0, outpos = 0;
+                         inpos < (walk-beginning);
+                         inpos++, outpos++) {
+                        /* We only handle escaped double quotes to not break
+                         * backwards compatibility with people using \w in
+                         * regular expressions etc. */
+                        if (beginning[inpos] == '\\' && beginning[inpos+1] == '"')
+                            inpos++;
+                        str[outpos] = beginning[inpos];
+                    }
+                    if (token->identifier)
+                        push_string(token->identifier, str);
+                    free(str);
+                    /* If we are at the end of a quoted string, skip the ending
+                     * double quote. */
+                    if (*walk == '"')
+                        walk++;
+                    if ((result = next_state(token)) != NULL)
+                        return result;
+                    break;
+                }
+            }
+
+            if (strcmp(token->name, "end") == 0) {
+                //printf("checking for end: *%s*\n", walk);
+                if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
+                    if ((result = next_state(token)) != NULL)
+                        return result;
+                    /* To make sure we start with an appropriate matching
+                     * datastructure for commands which do *not* specify any
+                     * criteria, we re-initialize the criteria system after
+                     * every command. */
+                    // TODO: make this testable
+                    walk++;
+                    break;
+               }
+           }
+        }
+    }
+
+    return NULL;
+}
+
+
 /*
  * Having verboselog() and errorlog() is necessary when using libi3.
  *
@@ -466,6 +802,7 @@ int main(int argc, char *argv[]) {
         xcb_connection_has_error(conn))
         errx(1, "Cannot open display\n");
 
+    keysyms = xcb_key_symbols_alloc(conn);
     xcb_get_modifier_mapping_cookie_t modmap_cookie;
     modmap_cookie = xcb_get_modifier_mapping(conn);
     symbols = xcb_key_symbols_alloc(conn);
index 20c7fbeaaec296aafe71becc8b60a40a506bf6d8..085c4f1c3e5780aeb6d31f6182a02840aa41402e 100755 (executable)
@@ -6,7 +6,7 @@
 # No dependencies except for perl ≥ v5.10
 
 use strict;
-use warnings;
+use warnings qw(FATAL utf8);
 use Data::Dumper;
 use IPC::Open2;
 use POSIX qw(locale_h);
@@ -17,25 +17,35 @@ use Getopt::Long;
 use Pod::Usage;
 use v5.10;
 use utf8;
-use open ':encoding(utf8)';
+use open ':encoding(UTF-8)';
 
 binmode STDOUT, ':utf8';
 binmode STDERR, ':utf8';
 
 # reads in a whole file
 sub slurp {
-    open(my $fh, '<', shift) or die "$!";
+    my ($filename) = @_;
+    open(my $fh, '<', $filename) or die "$!";
     local $/;
-    <$fh>;
+    my $result;
+    eval {
+        $result = <$fh>;
+    };
+    if ($@) {
+        warn "Could not read $filename: $@";
+        return undef;
+    } else {
+        return $result;
+    }
 }
 
-my $entry_type = 'both';
+my @entry_types;
 my $dmenu_cmd = 'dmenu -i';
 my $result = GetOptions(
     'dmenu=s' => \$dmenu_cmd,
-    'entry-type=s' => \$entry_type,
+    'entry-type=s' => \@entry_types,
     'version' => sub {
-        say "dmenu-desktop 1.2 © 2012 Michael Stapelberg";
+        say "dmenu-desktop 1.4 © 2012 Michael Stapelberg";
         exit 0;
     },
     'help' => sub {
@@ -44,6 +54,11 @@ my $result = GetOptions(
 
 die "Could not parse command line options" unless $result;
 
+# Filter entry types and set default type(s) if none selected
+my @valid_types = ('name', 'command', 'filename');
+@entry_types = grep { $_ ~~ @valid_types } @entry_types;
+@entry_types = ('name', 'command') unless @entry_types;
+
 # ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
 # ┃ Convert LC_MESSAGES into an ordered list of suffixes to search for in the ┃
 # ┃ .desktop files (e.g. “Name[de_DE@euro]” for LC_MESSAGES=de_DE.UTF-8@euro  ┃
@@ -135,7 +150,9 @@ for my $file (values %desktops) {
     # Extract all “Name” and “Exec” keys from the [Desktop Entry] group
     # and store them in $apps{$base}.
     my %names;
-    my @lines = split("\n", slurp($file));
+    my $content = slurp($file);
+    next unless defined($content);
+    my @lines = split("\n", $content);
     for my $line (@lines) {
         my $first = substr($line, 0, 1);
         next if $line eq '' || $first eq '#';
@@ -209,6 +226,13 @@ for my $app (keys %apps) {
     next if (!exists($apps{$app}->{Type}) ||
              $apps{$app}->{Type} ne 'Application');
 
+    # Skip broken files (Type=application, but no Exec key).
+    if (!exists($apps{$app}->{Exec}) ||
+        $apps{$app}->{Exec} eq '') {
+        warn 'File ' . $apps{$app}->{_Location} . ' is broken: it contains Type=Application, but no Exec key/value pair.';
+        next;
+    }
+
     # Don’t offer apps which have NoDisplay == true or Hidden == true.
     # See http://wiki.xfce.org/howto/customize-menu#hide_menu_entries
     # for the difference between NoDisplay and Hidden.
@@ -232,7 +256,7 @@ for my $app (keys %apps) {
         }
     }
 
-    if ($entry_type eq 'name' || $entry_type eq 'both') {
+    if ('name' ~~ @entry_types) {
         if (exists($choices{$name})) {
             # There are two .desktop files which contain the same “Name” value.
             # I’m not sure if that is allowed to happen, but we disambiguate the
@@ -248,10 +272,25 @@ for my $app (keys %apps) {
         $choices{$name} = $app;
     }
 
-    if ($entry_type eq 'command' || $entry_type eq 'both') {
+    if ('command' ~~ @entry_types) {
         my ($command) = split(' ', $apps{$app}->{Exec});
+
+        # Don’t add “geany” if “Geany” is already present.
+        my @keys = map { lc } keys %choices;
+        next if lc(basename($command)) ~~ @keys;
+
         $choices{basename($command)} = $app;
     }
+
+    if ('filename' ~~ @entry_types) {
+        my $filename = basename($app, '.desktop');
+
+        # Don’t add “geany” if “Geany” is already present.
+        my @keys = map { lc } keys %choices;
+        next if lc($filename) ~~ @keys;
+
+        $choices{$filename} = $app;
+    }
 }
 
 # %choices now looks like this:
@@ -282,6 +321,8 @@ my $status = ($? >> 8);
 exit $status unless $status == 0;
 
 my $choice = <$dmenu_out>;
+# dmenu ≥ 4.4 adds a newline after the choice
+chomp($choice);
 my $app;
 # Exact match: the user chose “Avidemux (GTK+)”
 if (exists($choices{$choice})) {
@@ -341,6 +382,7 @@ $exec =~ s/%c/$name/g;
 # XXX: Icons are not implemented. Is the complexity (looking up the path if
 # only a name is given) actually worth it?
 #$exec =~ s/%i/--icon $icon/g;
+$exec =~ s/%i//g;
 
 # location of .desktop file
 $exec =~ s/%k/$location/g;
@@ -392,7 +434,7 @@ system('i3-msg', $cmd) == 0 or die "Could not launch i3-msg: $?";
 
 =head1 SYNOPSIS
 
-    i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=both]
+    i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=name]
 
 =head1 DESCRIPTION
 
@@ -441,17 +483,18 @@ version of dmenu.
 
 =item B<--entry-type=type>
 
-Display the (localized) "Name" (type = name) or the command (type = command) or
-both (type = both) in dmenu.
+Display the (localized) "Name" (type = name), the command (type = command) or
+the (*.desktop) filename (type = filename) in dmenu. This option can be
+specified multiple times.
 
 Examples are "GNU Image Manipulation Program" (type = name), "gimp" (type =
-command) and both (type = both).
+command), and "libreoffice-writer" (type = filename).
 
 =back
 
 =head1 VERSION
 
-Version 1.2
+Version 1.4
 
 =head1 AUTHOR
 
index 48465c75718e696a44779638a5198f98c1e993dd..852a5cf7b2fc944e50272fcd2c1a9b676c2070f6 100644 (file)
@@ -42,14 +42,19 @@ static int check_for_wrap(void) {
     /* The log wrapped. Print the remaining content and reset walk to the top
      * of the log. */
     wrap_count = header->wrap_count;
-    write(STDOUT_FILENO, walk, ((logbuffer + header->offset_last_wrap) - walk));
+    const int len = (logbuffer + header->offset_last_wrap) - walk;
+    if (write(STDOUT_FILENO, walk, len) != len)
+        err(EXIT_FAILURE, "write()");
     walk = logbuffer + sizeof(i3_shmlog_header);
     return 1;
 }
 
 static void print_till_end(void) {
     check_for_wrap();
-    int n = write(STDOUT_FILENO, walk, ((logbuffer + header->offset_next_write) - walk));
+    const int len = (logbuffer + header->offset_next_write) - walk;
+    const int n = write(STDOUT_FILENO, walk, len);
+    if (len != n)
+        err(EXIT_FAILURE, "write()");
     if (n > 0) {
         walk += n;
     }
@@ -121,7 +126,7 @@ int main(int argc, char *argv[]) {
 
     struct stat statbuf;
 
-    /* NB: While we must never read, we need O_RDWR for the pthread condvar. */
+    /* NB: While we must never write, we need O_RDWR for the pthread condvar. */
     int logbuffer_shm = shm_open(shmname, O_RDWR, 0);
     if (logbuffer_shm == -1)
         err(EXIT_FAILURE, "Could not shm_open SHM segment for the i3 log (%s)", shmname);
@@ -129,7 +134,7 @@ int main(int argc, char *argv[]) {
     if (fstat(logbuffer_shm, &statbuf) != 0)
         err(EXIT_FAILURE, "stat(%s)", shmname);
 
-    /* NB: While we must never read, we need O_RDWR for the pthread condvar. */
+    /* NB: While we must never write, we need PROT_WRITE for the pthread condvar. */
     logbuffer = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
     if (logbuffer == MAP_FAILED)
         err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log");
index f98afd3d195995c637eb1711e0d8dc5b36e732f7..49db4df231dd234260e30369d3f589353140b78f 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-input/main.c: Utility which lets the user input commands and sends them
  *                  to i3.
@@ -76,6 +76,24 @@ void errorlog(char *fmt, ...) {
     va_end(args);
 }
 
+/*
+ * Restores the X11 input focus to whereever it was before.
+ * This is necessary because i3-input’s window has override_redirect=1
+ * (→ unmanaged by the window manager) and thus i3-input changes focus itself.
+ * This function is called on exit().
+ *
+ */
+static void restore_input_focus(void) {
+    xcb_generic_error_t *error;
+    xcb_get_input_focus_reply_t *reply = xcb_get_input_focus_reply(conn, focus_cookie, &error);
+    if (error != NULL) {
+        fprintf(stderr, "[i3-input] ERROR: Could not restore input focus (X error %d)\n", error->error_code);
+        return;
+    }
+    xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, reply->focus, XCB_CURRENT_TIME);
+    xcb_flush(conn);
+}
+
 /*
  * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
  * rendering it (UCS-2) or sending it to i3 (UTF-8).
@@ -187,6 +205,10 @@ static void finish_input() {
     /* prefix the command if a prefix was specified on commandline */
     printf("command = %s\n", full);
 
+    restore_input_focus();
+
+    xcb_aux_sync(conn);
+
     ipc_send_message(sockfd, strlen(full), 0, (uint8_t*)full);
 
 #if 0
@@ -239,6 +261,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
         return 1;
     }
     if (sym == XK_Escape) {
+        restore_input_focus();
         exit(0);
     }
 
@@ -283,24 +306,6 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
     return 1;
 }
 
-/*
- * Restores the X11 input focus to whereever it was before.
- * This is necessary because i3-input’s window has override_redirect=1
- * (→ unmanaged by the window manager) and thus i3-input changes focus itself.
- * This function is called on exit().
- *
- */
-static void restore_input_focus(void) {
-    xcb_generic_error_t *error;
-    xcb_get_input_focus_reply_t *reply = xcb_get_input_focus_reply(conn, focus_cookie, &error);
-    if (error != NULL) {
-        fprintf(stderr, "[i3-input] ERROR: Could not restore input focus (X error %d)\n", error->error_code);
-        return;
-    }
-    xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, reply->focus, XCB_CURRENT_TIME);
-    xcb_flush(conn);
-}
-
 int main(int argc, char *argv[]) {
     format = strdup("%s");
     socket_path = getenv("I3SOCK");
@@ -420,7 +425,6 @@ int main(int argc, char *argv[]) {
     /* Set input focus (we have override_redirect=1, so the wm will not do
      * this for us) */
     xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
-    atexit(restore_input_focus);
 
     /* Grab the keyboard to get all input */
     xcb_flush(conn);
@@ -440,6 +444,7 @@ int main(int argc, char *argv[]) {
 
     if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
         fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
+        restore_input_focus();
         exit(-1);
     }
 
index fef9581d785c30b641024205455c5834d09063fa..b1ba7b4d647c0d4bbee5c03a10c8620a34e5df5c 100644 (file)
@@ -5,7 +5,7 @@ CLEAN_TARGETS += clean-i3-msg
 i3_msg_SOURCES := $(wildcard i3-msg/*.c)
 i3_msg_HEADERS := $(wildcard i3-msg/*.h)
 i3_msg_CFLAGS   = $(XCB_CFLAGS) $(PANGO_CFLAGS)
-i3_msg_LIBS     = $(XCB_LIBS)
+i3_msg_LIBS     = $(XCB_LIBS) $(YAJL_LIBS)
 
 i3_msg_OBJECTS := $(i3_msg_SOURCES:.c=.o)
 
index 607c5269b2f52a406132cedbcd5dbf1eefd1cb30..a1428fb8f5f89aac9e35f68420f983b808ee7303 100644 (file)
@@ -28,6 +28,9 @@
 #include <getopt.h>
 #include <limits.h>
 
+#include <yajl/yajl_parse.h>
+#include <yajl/yajl_version.h>
+
 #include <xcb/xcb.h>
 #include <xcb/xcb_aux.h>
 
 
 static char *socket_path;
 
+/*
+ * Having verboselog() and errorlog() is necessary when using libi3.
+ *
+ */
+void verboselog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stdout, fmt, args);
+    va_end(args);
+}
+
+void errorlog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+}
+
+static char *last_key = NULL;
+
+typedef struct reply_t {
+    bool success;
+    char *error;
+    char *input;
+    char *errorposition;
+} reply_t;
+
+static reply_t last_reply;
+
+static int reply_boolean_cb(void *params, int val) {
+    if (strcmp(last_key, "success") == 0)
+        last_reply.success = val;
+    return 1;
+}
+
+#if YAJL_MAJOR >= 2
+static int reply_string_cb(void *params, const unsigned char *val, size_t len) {
+#else
+static int reply_string_cb(void *params, const unsigned char *val, unsigned int len) {
+#endif
+    char *str = scalloc(len + 1);
+    strncpy(str, (const char*)val, len);
+    if (strcmp(last_key, "error") == 0)
+        last_reply.error = str;
+    else if (strcmp(last_key, "input") == 0)
+        last_reply.input = str;
+    else if (strcmp(last_key, "errorposition") == 0)
+        last_reply.errorposition = str;
+    else free(str);
+    return 1;
+}
+
+static int reply_start_map_cb(void *params) {
+    return 1;
+}
+
+static int reply_end_map_cb(void *params) {
+    if (!last_reply.success) {
+        fprintf(stderr, "ERROR: Your command: %s\n", last_reply.input);
+        fprintf(stderr, "ERROR:               %s\n", last_reply.errorposition);
+        fprintf(stderr, "ERROR: %s\n", last_reply.error);
+    }
+    return 1;
+}
+
+
+#if YAJL_MAJOR >= 2
+static int reply_map_key_cb(void *params, const unsigned char *keyVal, size_t keyLen) {
+#else
+static int reply_map_key_cb(void *params, const unsigned char *keyVal, unsigned keyLen) {
+#endif
+    free(last_key);
+    last_key = scalloc(keyLen + 1);
+    strncpy(last_key, (const char*)keyVal, keyLen);
+    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
+};
+
 int main(int argc, char *argv[]) {
     socket_path = getenv("I3SOCK");
     int o, option_index = 0;
@@ -126,7 +222,7 @@ int main(int argc, char *argv[]) {
     addr.sun_family = AF_LOCAL;
     strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
     if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
-        err(EXIT_FAILURE, "Could not connect to i3");
+        err(EXIT_FAILURE, "Could not connect to i3 on socket \"%s\"", socket_path);
 
     if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload) == -1)
         err(EXIT_FAILURE, "IPC: write()");
@@ -135,13 +231,42 @@ int main(int argc, char *argv[]) {
         return 0;
 
     uint32_t reply_length;
+    uint32_t reply_type;
     uint8_t *reply;
     int ret;
-    if ((ret = ipc_recv_message(sockfd, message_type, &reply_length, &reply)) != 0) {
+    if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
         if (ret == -1)
             err(EXIT_FAILURE, "IPC: read()");
         exit(1);
     }
+    if (reply_type != message_type)
+        errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected %d", reply_type, message_type);
+    /* For the reply of commands, have a look if that command was successful.
+     * If not, nicely format the error message. */
+    if (reply_type == I3_IPC_MESSAGE_TYPE_COMMAND) {
+        yajl_handle handle;
+#if YAJL_MAJOR < 2
+        yajl_parser_config parse_conf = { 0, 0 };
+
+        handle = yajl_alloc(&reply_callbacks, &parse_conf, NULL, NULL);
+#else
+        handle = yajl_alloc(&reply_callbacks, NULL, NULL);
+#endif
+        yajl_status state = yajl_parse(handle, (const unsigned char*)reply, reply_length);
+        switch (state) {
+            case yajl_status_ok:
+                break;
+            case yajl_status_client_canceled:
+#if YAJL_MAJOR < 2
+            case yajl_status_insufficient_data:
+#endif
+            case yajl_status_error:
+                errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
+        }
+
+        /* NB: We still fall-through and print the reply, because even if one
+         * command failed, that doesn’t mean that all commands failed. */
+    }
     printf("%.*s\n", reply_length, reply);
     free(reply);
 
index a38d1839ed7b08cfd90ab3fb00c492aa4db805be..2243aa7265b9c869d3ce64b640a2f67f96ce8595 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-nagbar is a utility which displays a nag message, for example in the case
  * when the user has an error in his configuration file.
@@ -10,6 +10,7 @@
  */
 #include <stdio.h>
 #include <sys/types.h>
+#include <sys/stat.h>
 #include <sys/wait.h>
 #include <stdlib.h>
 #include <stdbool.h>
@@ -20,6 +21,7 @@
 #include <stdint.h>
 #include <getopt.h>
 #include <limits.h>
+#include <fcntl.h>
 
 #include <xcb/xcb.h>
 #include <xcb/xcb_aux.h>
@@ -28,6 +30,8 @@
 #include "libi3.h"
 #include "i3-nagbar.h"
 
+static char *argv0 = NULL;
+
 typedef struct {
     i3String *label;
     char *action;
@@ -135,7 +139,42 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
     button_t *button = get_button_at(event->event_x, event->event_y);
     if (!button)
         return;
-    start_application(button->action);
+
+    /* We need to create a custom script containing our actual command
+     * since not every terminal emulator which is contained in
+     * i3-sensible-terminal supports -e with multiple arguments (and not
+     * all of them support -e with one quoted argument either).
+     *
+     * NB: The paths need to be unique, that is, don’t assume users close
+     * their nagbars at any point in time (and they still need to work).
+     * */
+    char *script_path = get_process_filename("nagbar-cmd");
+
+    int fd = open(script_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+    if (fd == -1) {
+        warn("Could not create temporary script to store the nagbar command");
+        return;
+    }
+    FILE *script = fdopen(fd, "w");
+    if (script == NULL) {
+        warn("Could not fdopen() temporary script to store the nagbar command");
+        return;
+    }
+    fprintf(script, "#!/bin/sh\nrm %s\n%s", script_path, button->action);
+    /* Also closes fd */
+    fclose(script);
+
+    char *terminal_cmd;
+    sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", argv0);
+    printf("argv0 = %s\n", argv0);
+    printf("terminal_cmd = %s\n", terminal_cmd);
+
+    setenv("_I3_NAGBAR_CMD", script_path, 1);
+    start_application(terminal_cmd);
+    unsetenv("_I3_NAGBAR_CMD");
+
+    free(terminal_cmd);
+    free(script_path);
 
     /* TODO: unset flag, re-render */
 }
@@ -236,6 +275,29 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
 }
 
 int main(int argc, char *argv[]) {
+    /* The following lines are a horrible kludge. Because terminal emulators
+     * have different ways of interpreting the -e command line argument (some
+     * need -e "less /etc/fstab", others need -e less /etc/fstab), we need to
+     * write commands to a script and then just run that script. However, since
+     * on some machines, $XDG_RUNTIME_DIR and $TMPDIR are mounted with noexec,
+     * we cannot directly execute the script either.
+     *
+     * Therefore, we run i3-nagbar instead and pass the path to the script in
+     * the environment variable $_I3_NAGBAR_CMD. i3-nagbar then execs /bin/sh
+     * with that path in order to run that script.
+     *
+     * From a security point of view, i3-nagbar is just an alias to /bin/sh in
+     * certain circumstances. This should not open any new security issues, I
+     * hope. */
+    char *cmd = NULL;
+    if ((cmd = getenv("_I3_NAGBAR_CMD")) != NULL) {
+        unsetenv("_I3_NAGBAR_CMD");
+        execl("/bin/sh", "/bin/sh", cmd, NULL);
+        err(EXIT_FAILURE, "execv(/bin/sh, /bin/sh, %s)", cmd);
+    }
+
+    argv0 = argv[0];
+
     char *pattern = sstrdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1");
     int o, option_index = 0;
     enum { TYPE_ERROR = 0, TYPE_WARNING = 1 } bar_type = TYPE_ERROR;
index 05fb5aa1151f0cac88f29d6593ab4c5fc05f6704..1365082f54591c0720712ce346ce7c37373ff36d 100644 (file)
@@ -43,6 +43,10 @@ struct status_block {
     blockalign_t align;
 
     bool urgent;
+    bool no_separator;
+
+    /* The amount of pixels necessary to render a separater after the block. */
+    uint32_t sep_block_width;
 
     /* The amount of pixels necessary to render this block. These variables are
      * only temporarily used in refresh_statusline(). */
index 75019c8d2a93ecc4b3893047eea0e6e8fed9442a..d8d0c09049287cf0cc4768836e6325680a3d223d 100644 (file)
@@ -28,6 +28,7 @@
 struct xcb_color_strings_t {
     char *bar_fg;
     char *bar_bg;
+    char *sep_fg;
     char *active_ws_fg;
     char *active_ws_bg;
     char *active_ws_border;
@@ -88,6 +89,15 @@ void get_atoms(void);
  */
 void kick_tray_clients(i3_output *output);
 
+/*
+ * We need to set the _NET_SYSTEM_TRAY_COLORS atom on the tray selection window
+ * to make GTK+ 3 applets with Symbolic Icons visible. If the colors are unset,
+ * they assume a light background.
+ * See also https://bugzilla.gnome.org/show_bug.cgi?id=679591
+ *
+ */
+void init_tray_colors(void);
+
 /*
  * Destroy the bar of the specified output
  *
index b75ceabd538afa3a96aba1fb87fd1a23f06a6403..65a147c4718aeb44fcd57d5580cca4c55925dd1a 100644 (file)
@@ -6,6 +6,7 @@ ATOM_DO(MANAGER)
 ATOM_DO(_NET_SYSTEM_TRAY_ORIENTATION)
 ATOM_DO(_NET_SYSTEM_TRAY_VISUAL)
 ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
+ATOM_DO(_NET_SYSTEM_TRAY_COLORS)
 ATOM_DO(_XEMBED_INFO)
 ATOM_DO(_XEMBED)
 #undef ATOM_DO
index bea1d58e43bb677f6fe25192f450f461ae396587..e5f4ea213cd10a5f16d9a2eb0a21343f45fdae63 100644 (file)
@@ -98,6 +98,10 @@ static int stdin_start_array(void *context) {
 static int stdin_start_map(void *context) {
     parser_ctx *ctx = context;
     memset(&(ctx->block), '\0', sizeof(struct status_block));
+
+    /* Default width of the separator block. */
+    ctx->block.sep_block_width = 9;
+
     return 1;
 }
 
@@ -117,6 +121,9 @@ static int stdin_boolean(void *context, int val) {
     if (strcasecmp(ctx->last_map_key, "urgent") == 0) {
         ctx->block.urgent = val;
     }
+    if (strcasecmp(ctx->last_map_key, "separator") == 0) {
+        ctx->block.no_separator = !val;
+    }
     return 1;
 }
 
@@ -140,6 +147,10 @@ static int stdin_string(void *context, const unsigned char *val, unsigned int le
         } else {
             ctx->block.align = ALIGN_CENTER;
         }
+    } else if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
+        i3String *text = i3string_from_utf8_with_length((const char *)val, len);
+        ctx->block.min_width = (uint32_t)predict_text_width(text);
+        i3string_free(text);
     }
     return 1;
 }
@@ -153,6 +164,9 @@ static int stdin_integer(void *context, long val) {
     if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
         ctx->block.min_width = (uint32_t)val;
     }
+    if (strcasecmp(ctx->last_map_key, "separator_block_width") == 0) {
+        ctx->block.sep_block_width = (uint32_t)val;
+    }
     return 1;
 }
 
index 69355b69b916c8bf75fafdae32da2ee3e62a9467..6c7286c4afd62d5094de71b7c4e76bf56c3579e9 100644 (file)
@@ -161,6 +161,7 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
 
     COLOR(statusline, bar_fg);
     COLOR(background, bar_bg);
+    COLOR(separator, sep_fg);
     COLOR(focused_workspace_border, focus_ws_border);
     COLOR(focused_workspace_bg, focus_ws_bg);
     COLOR(focused_workspace_text, focus_ws_fg);
@@ -260,6 +261,7 @@ void free_colors(struct xcb_color_strings_t *colors) {
     } while (0)
     FREE_COLOR(bar_fg);
     FREE_COLOR(bar_bg);
+    FREE_COLOR(sep_fg);
     FREE_COLOR(active_ws_fg);
     FREE_COLOR(active_ws_bg);
     FREE_COLOR(active_ws_border);
index 2170e50965466f6082f055677a5ccdbe8e42ebbf..faab8e102890a84422b6fc8c5231d71c58c245b3 100644 (file)
@@ -84,6 +84,8 @@ void got_bar_config(char *reply) {
      * workspaces. Everything else (creating the bars, showing the right workspace-
      * buttons and more) is taken care of by the event-drivenness of the code */
     i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
+
+    free_colors(&(config.colors));
     parse_config_json(reply);
 
     /* Now we can actually use 'config', so let's subscribe to the appropriate
@@ -97,7 +99,6 @@ void got_bar_config(char *reply) {
 
     /* Resolve color strings to colorpixels and save them, then free the strings. */
     init_colors(&(config.colors));
-    free_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
index 3a8d8b9c3add9197b9088c640802bead9cc91ba8..d5d4eb8659cfedf7a9ecfbc134e72638e6d5fd8d 100644 (file)
@@ -49,6 +49,10 @@ int              screen;
 xcb_screen_t     *root_screen;
 xcb_window_t     xcb_root;
 
+/* selection window for tray support */
+static xcb_window_t selwin = XCB_NONE;
+static xcb_intern_atom_reply_t *tray_reply = NULL;
+
 /* This is needed for integration with libi3 */
 xcb_connection_t *conn;
 
@@ -80,6 +84,7 @@ static mode binding;
 struct xcb_colors_t {
     uint32_t bar_fg;
     uint32_t bar_bg;
+    uint32_t sep_fg;
     uint32_t active_ws_fg;
     uint32_t active_ws_bg;
     uint32_t active_ws_border;
@@ -145,7 +150,8 @@ void refresh_statusline(void) {
 
         /* If this is not the last block, add some pixels for a separator. */
         if (TAILQ_NEXT(block, blocks) != NULL)
-            block->width += 9;
+            block->width += block->sep_block_width;
+
         statusline_width += block->width + block->x_offset + block->x_append;
     }
 
@@ -167,15 +173,19 @@ void refresh_statusline(void) {
 
         uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
         set_font_colors(statusline_ctx, colorpixel, colors.bar_bg);
-        draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 0, block->width);
+        draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 1, block->width);
         x += block->width + block->x_offset + block->x_append;
 
-        if (TAILQ_NEXT(block, blocks) != NULL) {
+        if (TAILQ_NEXT(block, blocks) != NULL && !block->no_separator && block->sep_block_width > 0) {
             /* This is not the last block, draw a separator. */
-            set_font_colors(statusline_ctx, get_colorpixel("#666666"), colors.bar_bg);
+            uint32_t sep_offset = block->sep_block_width/2 + block->sep_block_width % 2;
+            uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
+            uint32_t values[] = { colors.sep_fg, colors.bar_bg };
+            xcb_change_gc(xcb_connection, statusline_ctx, mask, values);
             xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm,
                           statusline_ctx, 2,
-                          (xcb_point_t[]){ { x - 5, 2 }, { x - 5, font.height - 2 } });
+                          (xcb_point_t[]){ { x - sep_offset, 2 },
+                                           { x - sep_offset, font.height - 2 } });
         }
     }
 }
@@ -255,6 +265,7 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
     } while  (0)
     PARSE_COLOR(bar_fg, "#FFFFFF");
     PARSE_COLOR(bar_bg, "#000000");
+    PARSE_COLOR(sep_fg, "#666666");
     PARSE_COLOR(active_ws_fg, "#FFFFFF");
     PARSE_COLOR(active_ws_bg, "#333333");
     PARSE_COLOR(active_ws_border, "#333333");
@@ -268,6 +279,9 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
     PARSE_COLOR(focus_ws_bg, "#285577");
     PARSE_COLOR(focus_ws_border, "#4c7899");
 #undef PARSE_COLOR
+
+    init_tray_colors();
+    xcb_flush(xcb_connection);
 }
 
 /*
@@ -991,6 +1005,31 @@ void init_xcb_late(char *fontname) {
     }
 }
 
+/*
+ * Inform clients waiting for a new _NET_SYSTEM_TRAY that we took the
+ * selection.
+ *
+ */
+static void send_tray_clientmessage(void) {
+    uint8_t buffer[32] = { 0 };
+    xcb_client_message_event_t *ev = (xcb_client_message_event_t*)buffer;
+
+    ev->response_type = XCB_CLIENT_MESSAGE;
+    ev->window = xcb_root;
+    ev->type = atoms[MANAGER];
+    ev->format = 32;
+    ev->data.data32[0] = XCB_CURRENT_TIME;
+    ev->data.data32[1] = tray_reply->atom;
+    ev->data.data32[2] = selwin;
+
+    xcb_send_event(xcb_connection,
+                   0,
+                   xcb_root,
+                   0xFFFFFF,
+                   (char*)buffer);
+}
+
+
 /*
  * Initializes tray support by requesting the appropriate _NET_SYSTEM_TRAY atom
  * for the X11 display we are running on, then acquiring the selection for this
@@ -1003,11 +1042,11 @@ void init_tray(void) {
     char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
     snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen);
     xcb_intern_atom_cookie_t tray_cookie;
-    xcb_intern_atom_reply_t *tray_reply;
-    tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname);
+    if (tray_reply == NULL)
+        tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname);
 
     /* tray support: we need a window to own the selection */
-    xcb_window_t selwin = xcb_generate_id(xcb_connection);
+    selwin = xcb_generate_id(xcb_connection);
     uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT;
     uint32_t selval[] = { 1 };
     xcb_create_window(xcb_connection,
@@ -1016,7 +1055,7 @@ void init_tray(void) {
                       xcb_root,
                       -1, -1,
                       1, 1,
-                      1,
+                      0,
                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
                       root_screen->root_visual,
                       selmask,
@@ -1033,9 +1072,13 @@ void init_tray(void) {
                         1,
                         &orientation);
 
-    if (!(tray_reply = xcb_intern_atom_reply(xcb_connection, tray_cookie, NULL))) {
-        ELOG("Could not get atom %s\n", atomname);
-        exit(EXIT_FAILURE);
+    init_tray_colors();
+
+    if (tray_reply == NULL) {
+        if (!(tray_reply = xcb_intern_atom_reply(xcb_connection, tray_cookie, NULL))) {
+            ELOG("Could not get atom %s\n", atomname);
+            exit(EXIT_FAILURE);
+        }
     }
 
     xcb_set_selection_owner(xcb_connection,
@@ -1062,23 +1105,48 @@ void init_tray(void) {
         return;
     }
 
-    /* Inform clients waiting for a new _NET_SYSTEM_TRAY that we are here */
-    void *event = scalloc(32);
-    xcb_client_message_event_t *ev = event;
-    ev->response_type = XCB_CLIENT_MESSAGE;
-    ev->window = xcb_root;
-    ev->type = atoms[MANAGER];
-    ev->format = 32;
-    ev->data.data32[0] = XCB_CURRENT_TIME;
-    ev->data.data32[1] = tray_reply->atom;
-    ev->data.data32[2] = selwin;
-    xcb_send_event(xcb_connection,
-                   0,
-                   xcb_root,
-                   0xFFFFFF,
-                   (char*)ev);
-    free(event);
-    free(tray_reply);
+    send_tray_clientmessage();
+}
+
+/*
+ * We need to set the _NET_SYSTEM_TRAY_COLORS atom on the tray selection window
+ * to make GTK+ 3 applets with Symbolic Icons visible. If the colors are unset,
+ * they assume a light background.
+ * See also https://bugzilla.gnome.org/show_bug.cgi?id=679591
+ *
+ */
+void init_tray_colors(void) {
+    /* Convert colors.bar_fg (#rrggbb) to 16-bit RGB */
+    const char *bar_fg = (config.colors.bar_fg ? config.colors.bar_fg : "#FFFFFF");
+
+    DLOG("Setting bar_fg = %s as _NET_SYSTEM_TRAY_COLORS\n", bar_fg);
+
+    char strgroups[3][3] = {{bar_fg[1], bar_fg[2], '\0'},
+                            {bar_fg[3], bar_fg[4], '\0'},
+                            {bar_fg[5], bar_fg[6], '\0'}};
+    const uint8_t r = strtol(strgroups[0], NULL, 16);
+    const uint8_t g = strtol(strgroups[1], NULL, 16);
+    const uint8_t b = strtol(strgroups[2], NULL, 16);
+
+    const uint16_t r16 = ((uint16_t)r << 8) | r;
+    const uint16_t g16 = ((uint16_t)g << 8) | g;
+    const uint16_t b16 = ((uint16_t)b << 8) | b;
+
+    const uint32_t tray_colors[12] = {
+        r16, g16, b16, /* foreground color */
+        r16, g16, b16, /* error color */
+        r16, g16, b16, /* warning color */
+        r16, g16, b16, /* success color */
+    };
+
+    xcb_change_property(xcb_connection,
+                        XCB_PROP_MODE_REPLACE,
+                        selwin,
+                        atoms[_NET_SYSTEM_TRAY_COLORS],
+                        XCB_ATOM_CARDINAL,
+                        32,
+                        12,
+                        tray_colors);
 }
 
 /*
@@ -1138,6 +1206,9 @@ void get_atoms(void) {
  *
  */
 void kick_tray_clients(i3_output *output) {
+    if (TAILQ_EMPTY(output->trayclients))
+        return;
+
     trayclient *trayclient;
     while (!TAILQ_EMPTY(output->trayclients)) {
         trayclient = TAILQ_FIRST(output->trayclients);
@@ -1153,6 +1224,20 @@ void kick_tray_clients(i3_output *output) {
          * event afterwards, but better safe than sorry. */
         TAILQ_REMOVE(output->trayclients, trayclient, tailq);
     }
+
+    /* Fake a DestroyNotify so that Qt re-adds tray icons.
+     * We cannot actually destroy the window because then Qt will not restore
+     * its event mask on the new window. */
+    uint8_t buffer[32] = { 0 };
+    xcb_destroy_notify_event_t *event = (xcb_destroy_notify_event_t*)buffer;
+
+    event->response_type = XCB_DESTROY_NOTIFY;
+    event->event = selwin;
+    event->window = selwin;
+
+    xcb_send_event(conn, false, selwin, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)event);
+
+    send_tray_clientmessage();
 }
 
 /*
@@ -1261,7 +1346,7 @@ void reconfig_windows(void) {
                                                                      xcb_root,
                                                                      walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6,
                                                                      walk->rect.w, font.height + 6,
-                                                                     1,
+                                                                     0,
                                                                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
                                                                      root_screen->root_visual,
                                                                      mask,
@@ -1398,7 +1483,7 @@ void reconfig_windows(void) {
             values[3] = font.height + 6;
             values[4] = XCB_STACK_MODE_ABOVE;
 
-            DLOG("Destroying buffer for output %s", walk->name);
+            DLOG("Destroying buffer for output %s\n", walk->name);
             xcb_free_pixmap(xcb_connection, walk->buffer);
 
             DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
@@ -1407,7 +1492,7 @@ void reconfig_windows(void) {
                                                                         mask,
                                                                         values);
 
-            DLOG("Recreating buffer for output %s", walk->name);
+            DLOG("Recreating buffer for output %s\n", walk->name);
             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
                                                                     root_screen->root_depth,
                                                                     walk->buffer,
@@ -1431,7 +1516,7 @@ void reconfig_windows(void) {
  */
 void draw_bars(bool unhide) {
     DLOG("Drawing Bars...\n");
-    int i = 0;
+    int i = 1;
 
     refresh_statusline();
 
@@ -1530,7 +1615,7 @@ void draw_bars(bool unhide) {
                           outputs_walk->bargc,
                           mask,
                           vals_border);
-            xcb_rectangle_t rect_border = { i, 0, ws_walk->name_width + 10, font.height + 4 };
+            xcb_rectangle_t rect_border = { i, 1, ws_walk->name_width + 10, font.height + 4 };
             xcb_poly_fill_rectangle(xcb_connection,
                                     outputs_walk->buffer,
                                     outputs_walk->bargc,
@@ -1541,14 +1626,14 @@ void draw_bars(bool unhide) {
                           outputs_walk->bargc,
                           mask,
                           vals);
-            xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font.height + 2 };
+            xcb_rectangle_t rect = { i + 1, 2, ws_walk->name_width + 8, font.height + 2 };
             xcb_poly_fill_rectangle(xcb_connection,
                                     outputs_walk->buffer,
                                     outputs_walk->bargc,
                                     1,
                                     &rect);
             set_font_colors(outputs_walk->bargc, fg_color, bg_color);
-            draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, ws_walk->name_width);
+            draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, ws_walk->name_width);
             i += 10 + ws_walk->name_width + 1;
 
         }
@@ -1564,7 +1649,7 @@ void draw_bars(bool unhide) {
                           outputs_walk->bargc,
                           mask,
                           vals_border);
-            xcb_rectangle_t rect_border = { i, 0, binding.width + 10, font.height + 4 };
+            xcb_rectangle_t rect_border = { i, 1, binding.width + 10, font.height + 4 };
             xcb_poly_fill_rectangle(xcb_connection,
                                     outputs_walk->buffer,
                                     outputs_walk->bargc,
@@ -1576,7 +1661,7 @@ void draw_bars(bool unhide) {
                           outputs_walk->bargc,
                           mask,
                           vals);
-            xcb_rectangle_t rect = { i + 1, 1, binding.width + 8, font.height + 2 };
+            xcb_rectangle_t rect = { i + 1, 2, binding.width + 8, font.height + 2 };
             xcb_poly_fill_rectangle(xcb_connection,
                                     outputs_walk->buffer,
                                     outputs_walk->bargc,
@@ -1584,7 +1669,7 @@ void draw_bars(bool unhide) {
                                     &rect);
 
             set_font_colors(outputs_walk->bargc, fg_color, bg_color);
-            draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, binding.width);
+            draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, binding.width);
         }
 
         i = 0;
index af60b9660453d3eb93c58e66f31d691702420c09..205efa171a261f75dc3c45401208f7b04d4361ec 100644 (file)
@@ -2,6 +2,7 @@ xmacro(_NET_SUPPORTED)
 xmacro(_NET_SUPPORTING_WM_CHECK)
 xmacro(_NET_WM_NAME)
 xmacro(_NET_WM_STATE_FULLSCREEN)
+xmacro(_NET_WM_STATE_DEMANDS_ATTENTION)
 xmacro(_NET_WM_STATE)
 xmacro(_NET_WM_WINDOW_TYPE)
 xmacro(_NET_WM_WINDOW_TYPE_DOCK)
index 5c104ebd687f045287732260803d03521c1bf5a3..62eb12d0d7584255c5a93aff2000fa07b4debffb 100644 (file)
@@ -324,6 +324,12 @@ bool con_has_urgent_child(Con *con);
  */
 void con_update_parents_urgency(Con *con);
 
+/**
+ * Set urgency flag to the container, all the parent containers and the workspace.
+ *
+ */
+void con_set_urgency(Con *con, bool urgent);
+
 /**
  * Create a string representing the subtree under con.
  *
index 04f1c85f65ded1e8b1e088afe6a9aa2c6e300489..7056af8587fe0ffba0cc2f3d69058157f8df2b07 100644 (file)
@@ -6,8 +6,8 @@
  *
  * include/config.h: Contains all structs/variables for the configurable
  * part of i3 as well as functions handling the configuration file (calling
- * the parser (src/cfgparse.y) with the correct path, switching key bindings
- * mode).
+ * the parser (src/config_parse.c) with the correct path, switching key
+ * bindings mode).
  *
  */
 #ifndef I3_CONFIG_H
@@ -24,8 +24,6 @@ extern char *current_configpath;
 extern Config config;
 extern SLIST_HEAD(modes_head, Mode) modes;
 extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
-/* defined in src/cfgparse.y */
-extern bool force_old_config_parser;
 
 /**
  * Used during the config file lexing/parsing to keep the state of the lexer
@@ -269,6 +267,7 @@ struct Barconfig {
     struct bar_colors {
         char *background;
         char *statusline;
+        char *separator;
 
         char *focused_workspace_border;
         char *focused_workspace_bg;
@@ -342,7 +341,4 @@ Binding *get_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode
  */
 void kill_configerror_nagbar(bool wait_for_it);
 
-/* prototype for src/cfgparse.y */
-void parse_file(const char *f);
-
 #endif
index 0daf8118ec75ebbd85ce92245180d86ff6d0d102..fb863f3bf7b8e366af6fb262c133503f116efa0c 100644 (file)
@@ -12,6 +12,8 @@
 
 #include <yajl/yajl_gen.h>
 
+extern pid_t config_error_nagbar_pid;
+
 /*
  * The result of a parse_config call. Currently unused, but the JSON output
  * will be useful in the future when we implement a config parsing IPC command.
@@ -29,4 +31,11 @@ struct ConfigResult {
 
 struct ConfigResult *parse_config(const char *input, struct context *context);
 
+/**
+ * Parses the given file by first replacing the variables, then calling
+ * parse_config and possibly launching i3-nagbar.
+ *
+ */
+void parse_file(const char *f);
+
 #endif
index 70b650115ea7586022069199f3aa529b3eb1776a..1632efc76b8e7c6c21082fd09934930c05f544e7 100644 (file)
@@ -196,7 +196,8 @@ struct regex {
 
 /**
  * Holds a keybinding, consisting of a keycode combined with modifiers and the
- * command which is executed as soon as the key is pressed (see src/cfgparse.y)
+ * command which is executed as soon as the key is pressed (see
+ * src/config_parser.c)
  *
  */
 struct Binding {
@@ -569,8 +570,14 @@ struct Con {
     void(*on_remove_child)(Con *);
 
     enum {
+        /* Not a scratchpad window. */
         SCRATCHPAD_NONE = 0,
+
+        /* Just moved to scratchpad, not resized by the user yet.
+         * Window will be auto-centered and sized appropriately. */
         SCRATCHPAD_FRESH = 1,
+
+        /* The user changed position/size of the scratchpad window. */
         SCRATCHPAD_CHANGED = 2
     } scratchpad_state;
 
index 93b2ae87392e43a43b000ae5cd9d0beeed83c6c6..2a3321b50302727281a6838baf893a5c345669d0 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * This public header defines the different constants and message types to use
  * for the IPC interface to i3 (see docs/ipc for more information).
 #ifndef I3_I3_IPC_H
 #define I3_I3_IPC_H
 
+#include <stdint.h>
+
+typedef struct i3_ipc_header {
+    /* 6 = strlen(I3_IPC_MAGIC) */
+    char magic[6];
+    uint32_t size;
+    uint32_t type;
+} __attribute__ ((packed)) i3_ipc_header_t;
+
 /*
  * Messages from clients to i3
  *
@@ -87,4 +96,7 @@
 /* The output event will be triggered upon mode changes */
 #define I3_IPC_EVENT_MODE                       (I3_IPC_EVENT_MASK | 2)
 
+/* The window event will be triggered upon window changes */
+#define I3_IPC_EVENT_WINDOW                     (I3_IPC_EVENT_MASK | 3)
+
 #endif
index 417843a10fbea0c1a5e7c202539d7811e53f36e3..b231b8f5b4d34ba027ff1dc80c0a922c28720fe8 100644 (file)
@@ -10,6 +10,8 @@
 #ifndef I3_KEY_PRESS_H
 #define I3_KEY_PRESS_H
 
+extern pid_t command_error_nagbar_pid;
+
 /**
  * There was a key press. We compare this key code with our bindings table and pass
  * the bound action to parse_command().
index 7547845b9bf2245a97242badd9bcd6d7c9ff8e7f..53f3383d35390671e0f2621e23c57150e1d1fc0a 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * libi3: contains functions which are used by i3 *and* accompanying tools such
  * as i3-msg, i3-config-wizard, …
@@ -47,6 +47,9 @@ struct Font {
     /** The height of the font, built from font_ascent + font_descent */
     int height;
 
+    /** The pattern/name used to load the font. */
+    char *pattern;
+
     union {
         struct {
             /** The xcb-id for the font */
@@ -202,8 +205,8 @@ int ipc_connect(const char *socket_path);
  * Returns 0 on success.
  *
  */
-int ipc_send_message(int sockfd, uint32_t message_size,
-                     uint32_t message_type, const uint8_t *payload);
+int ipc_send_message(int sockfd, const uint32_t message_size,
+                     const uint32_t message_type, const uint8_t *payload);
 
 /**
  * Reads a message from the given socket file descriptor and stores its length
@@ -216,7 +219,7 @@ int ipc_send_message(int sockfd, uint32_t message_size,
  * Returns 0 on success.
  *
  */
-int ipc_recv_message(int sockfd, uint32_t message_type,
+int ipc_recv_message(int sockfd, uint32_t *message_type,
                      uint32_t *reply_length, uint8_t **reply);
 
 /**
@@ -355,4 +358,10 @@ xcb_visualtype_t *get_visualtype(xcb_screen_t *screen);
  */
 bool is_debug_build() __attribute__((const));
 
+/**
+ * Returns the name of a temporary file with the specified prefix.
+ *
+ */
+char *get_process_filename(const char *prefix);
+
 #endif
index b5c02144dc729986d3ecebb502ac2517af33706d..8222b99ac886520b8b1a5c7763a3e6ea0895d1f8 100644 (file)
@@ -88,19 +88,28 @@ Output *get_output_by_name(const char *name);
 Output *get_output_containing(int x, int y);
 
 /**
- * Gets the output which is the last one in the given direction, for example
- * the output on the most bottom when direction == D_DOWN, the output most
- * right when direction == D_RIGHT and so on.
+ * Gets the output which is the next one in the given direction.
+ *
+ * If close_far == CLOSEST_OUTPUT, then the output next to the current one will
+ * selected.  If close_far == FARTHEST_OUTPUT, the output which is the last one
+ * in the given direction will be selected.
  *
- * This function always returns a output.
+ * NULL will be returned when no active outputs are present in the direction
+ * specified (note that ‘current’ counts as such an output).
  *
  */
-Output *get_output_most(direction_t direction, Output *current);
+Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far);
 
 /**
- * Gets the output which is the next one in the given direction.
+ * Like get_output_next with close_far == CLOSEST_OUTPUT, but wraps.
+ *
+ * For example if get_output_next(D_DOWN, x, FARTHEST_OUTPUT) = NULL, then
+ * get_output_next_wrap(D_DOWN, x) will return the topmost output.
+ *
+ * This function always returns a output: if no active outputs can be found,
+ * current itself is returned.
  *
  */
-Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far);
+Output *get_output_next_wrap(direction_t direction, Output *current);
 
 #endif
index 0a5949f9fab35dc029b076cdd3d1407f38edac2d..a0eca01ab72572b6eb278b4a22848e59ed628ed5 100644 (file)
@@ -21,4 +21,9 @@
  */
 void render_con(Con *con, bool render_fullscreen);
 
+/*
+ * Returns the height for the decorations
+ */
+int render_deco_height(void);
+
 #endif
index e397a4e80ac37bbc77e230c6b497f12b3f33b638..61a38f3ec030537f184cf9a84c1f23b96373efa2 100644 (file)
@@ -105,13 +105,6 @@ char *resolve_tilde(const char *path);
  */
 bool path_exists(const char *path);
 
-
-/**
- * Returns the name of a temporary file with the specified prefix.
- *
- */
-char *get_process_filename(const char *prefix);
-
 /**
  * Restart i3 in-place
  * appends -a to argument list to disable autostart
@@ -130,4 +123,23 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);
 
 #endif
 
+/**
+ * Starts an i3-nagbar instance with the given parameters. Takes care of
+ * handling SIGCHLD and killing i3-nagbar when i3 exits.
+ *
+ * The resulting PID will be stored in *nagbar_pid and can be used with
+ * kill_nagbar() to kill the bar later on.
+ *
+ */
+void start_nagbar(pid_t *nagbar_pid, char *argv[]);
+
+/**
+ * Kills the i3-nagbar process, if *nagbar_pid != -1.
+ *
+ * If wait_for_it is set (restarting i3), this function will waitpid(),
+ * otherwise, ev is assumed to handle it (reloading).
+ *
+ */
+void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it);
+
 #endif
index 15d3e28fc4e1e81dbd8020fcd960ce6ba9b3f739..540135694187e9a6d2e4f896ca60844b0d61cdd8 100644 (file)
                           XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |   /* …subwindows get notifies */ \
                           XCB_EVENT_MASK_ENTER_WINDOW)           /* …user moves cursor inside our window */
 
+#define ROOT_EVENT_MASK   (XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \
+                           XCB_EVENT_MASK_BUTTON_PRESS | \
+                           XCB_EVENT_MASK_STRUCTURE_NOTIFY |         /* when the user adds a screen (e.g. video \
+                                                                            projector), the root window gets a \
+                                                                            ConfigureNotify */ \
+                           XCB_EVENT_MASK_POINTER_MOTION | \
+                           XCB_EVENT_MASK_PROPERTY_CHANGE | \
+                           XCB_EVENT_MASK_ENTER_WINDOW)
+
 #define xmacro(atom) xcb_atom_t A_ ## atom;
 #include "atoms.xmacro"
 #undef xmacro
index a2162c47cea1e0b55fd7a0b287d56157e44f0a44..8239b1f4414e8cc2ea6974c35da66c8042a7b6a4 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <assert.h>
@@ -143,13 +143,17 @@ i3Font load_font(const char *pattern, const bool fallback) {
 #if PANGO_SUPPORT
     /* Try to load a pango font if specified */
     if (strlen(pattern) > strlen("pango:") && !strncmp(pattern, "pango:", strlen("pango:"))) {
-        pattern += strlen("pango:");
-        if (load_pango_font(&font, pattern))
+        const char *font_pattern = pattern + strlen("pango:");
+        if (load_pango_font(&font, font_pattern)) {
+            font.pattern = sstrdup(pattern);
             return font;
+        }
     } else if (strlen(pattern) > strlen("xft:") && !strncmp(pattern, "xft:", strlen("xft:"))) {
-        pattern += strlen("xft:");
-        if (load_pango_font(&font, pattern))
+        const char *font_pattern = pattern + strlen("xft:");
+        if (load_pango_font(&font, font_pattern)) {
+            font.pattern = sstrdup(pattern);
             return font;
+        }
     }
 #endif
 
@@ -189,6 +193,7 @@ i3Font load_font(const char *pattern, const bool fallback) {
         }
     }
 
+    font.pattern = sstrdup(pattern);
     LOG("Using X font %s\n", pattern);
 
     /* Get information (height/name) for this font */
@@ -222,6 +227,7 @@ void set_font(i3Font *font) {
  *
  */
 void free_font(void) {
+    free(savedFont->pattern);
     switch (savedFont->type) {
         case FONT_TYPE_NONE:
             /* Nothing to do */
diff --git a/libi3/get_process_filename.c b/libi3/get_process_filename.c
new file mode 100644 (file)
index 0000000..630e3d1
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <err.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#include "libi3.h"
+
+/*
+ * Returns the name of a temporary file with the specified prefix.
+ *
+ */
+char *get_process_filename(const char *prefix) {
+    /* dir stores the directory path for this and all subsequent calls so that
+     * we only create a temporary directory once per i3 instance. */
+    static char *dir = NULL;
+    if (dir == NULL) {
+        /* Check if XDG_RUNTIME_DIR is set. If so, we use XDG_RUNTIME_DIR/i3 */
+        if ((dir = getenv("XDG_RUNTIME_DIR"))) {
+            char *tmp;
+            sasprintf(&tmp, "%s/i3", dir);
+            dir = tmp;
+            struct stat buf;
+            if (stat(dir, &buf) != 0) {
+                if (mkdir(dir, 0700) == -1) {
+                    perror("mkdir()");
+                    return NULL;
+                }
+            }
+        } else {
+            /* If not, we create a (secure) temp directory using the template
+             * /tmp/i3-<user>.XXXXXX */
+            struct passwd *pw = getpwuid(getuid());
+            const char *username = pw ? pw->pw_name : "unknown";
+            sasprintf(&dir, "/tmp/i3-%s.XXXXXX", username);
+            /* mkdtemp modifies dir */
+            if (mkdtemp(dir) == NULL) {
+                perror("mkdtemp()");
+                return NULL;
+            }
+        }
+    }
+    char *filename;
+    sasprintf(&filename, "%s/%s.%d", dir, prefix, getpid());
+    return filename;
+}
index f5c4a62b69dc7ba8742ce1832b4e22422fcc05a3..8fd438e3455f2bf1456e1f52fdd18d442bd0aefb 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <string.h>
@@ -10,6 +10,7 @@
 #include <stdio.h>
 #include <stdint.h>
 #include <unistd.h>
+#include <errno.h>
 
 #include <i3/ipc.h>
 
  * (reply_length) as well as a pointer to its contents (reply).
  *
  * Returns -1 when read() fails, errno will remain.
- * Returns -2 when the IPC protocol is violated (invalid magic, unexpected
+ * Returns -2 on EOF.
+ * Returns -3 when the IPC protocol is violated (invalid magic, unexpected
  * message type, EOF instead of a message). Additionally, the error will be
  * printed to stderr.
  * Returns 0 on success.
  *
  */
-int ipc_recv_message(int sockfd, uint32_t message_type,
+int ipc_recv_message(int sockfd, uint32_t *message_type,
                      uint32_t *reply_length, uint8_t **reply) {
     /* Read the message header first */
-    uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
+    const uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
     char msg[to_read];
     char *walk = msg;
 
     uint32_t read_bytes = 0;
     while (read_bytes < to_read) {
-        int n = read(sockfd, msg + read_bytes, to_read);
+        int n = read(sockfd, msg + read_bytes, to_read - read_bytes);
         if (n == -1)
             return -1;
         if (n == 0) {
-            fprintf(stderr, "IPC: received EOF instead of reply\n");
+            ELOG("IPC: received EOF instead of reply\n");
             return -2;
         }
 
         read_bytes += n;
-        to_read -= n;
     }
 
     if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
-        fprintf(stderr, "IPC: invalid magic in reply\n");
-        return -2;
+        ELOG("IPC: invalid magic in reply\n");
+        return -3;
     }
 
     walk += strlen(I3_IPC_MAGIC);
     *reply_length = *((uint32_t*)walk);
     walk += sizeof(uint32_t);
-    if (*((uint32_t*)walk) != message_type) {
-        fprintf(stderr, "IPC: unexpected reply type (got %d, expected %d)\n", *((uint32_t*)walk), message_type);
-        return -2;
-    }
+    if (message_type != NULL)
+        *message_type = *((uint32_t*)walk);
 
     *reply = smalloc(*reply_length);
 
-    to_read = *reply_length;
     read_bytes = 0;
-    while (read_bytes < to_read) {
-        int n = read(sockfd, *reply + read_bytes, to_read);
-        if (n == -1)
+    int n;
+    while (read_bytes < *reply_length) {
+        if ((n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes)) == -1) {
+            if (errno == EINTR || errno == EAGAIN)
+                continue;
             return -1;
+        }
 
         read_bytes += n;
-        to_read -= n;
     }
 
     return 0;
index 88d87a6a892ec1149096021042c99444704db88c..c5560c0de9789aef20e27ec667cfd36c7ce0464a 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <string.h>
  * Returns 0 on success.
  *
  */
-int ipc_send_message(int sockfd, uint32_t message_size,
-                     uint32_t message_type, const uint8_t *payload) {
-    int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
-    char msg[buffer_size];
-    char *walk = msg;
-
-    strncpy(walk, I3_IPC_MAGIC, buffer_size - 1);
-    walk += strlen(I3_IPC_MAGIC);
-    memcpy(walk, &message_size, sizeof(uint32_t));
-    walk += sizeof(uint32_t);
-    memcpy(walk, &message_type, sizeof(uint32_t));
-    walk += sizeof(uint32_t);
-    memcpy(walk, payload, message_size);
+int ipc_send_message(int sockfd, const uint32_t message_size,
+                     const uint32_t message_type, const uint8_t *payload) {
+    const i3_ipc_header_t header = {
+        /* We don’t use I3_IPC_MAGIC because it’s a 0-terminated C string. */
+        .magic = { 'i', '3', '-', 'i', 'p', 'c' },
+        .size = message_size,
+        .type = message_type
+    };
 
     int sent_bytes = 0;
-    while (sent_bytes < buffer_size) {
-        int n = write(sockfd, msg + sent_bytes, buffer_size - sent_bytes);
-        if (n == -1) {
+    int n = 0;
+
+    /* This first loop is basically unnecessary. No operating system has
+     * buffers which cannot fit 14 bytes into them, so the write() will only be
+     * called once. */
+    while (sent_bytes < sizeof(i3_ipc_header_t)) {
+        if ((n = write(sockfd, ((void*)&header) + sent_bytes, sizeof(i3_ipc_header_t) - sent_bytes)) == -1) {
+            if (errno == EAGAIN)
+                continue;
+            return -1;
+        }
+
+        sent_bytes += n;
+    }
+
+    sent_bytes = 0;
+
+    while (sent_bytes < message_size) {
+        if ((n = write(sockfd, payload + sent_bytes, message_size - sent_bytes)) == -1) {
             if (errno == EAGAIN)
                 continue;
             return -1;
index c28f2274a8a1d997f21590c91bdfcfa877ab6c2b..4926692388064fb9b334901ad13cd9352758e13a 100644 (file)
@@ -7,7 +7,7 @@ template::[header-declarations]
 <refentrytitle>{mantitle}</refentrytitle>
 <manvolnum>{manvolnum}</manvolnum>
 <refmiscinfo class="source">i3</refmiscinfo>
-<refmiscinfo class="version">4.4</refmiscinfo>
+<refmiscinfo class="version">4.5</refmiscinfo>
 <refmiscinfo class="manual">i3 Manual</refmiscinfo>
 </refmeta>
 <refnamediv>
index 168b6a9f7b7da6d0e1bf048a294e7048a5040b4f..6960510d4d386a731b92a3a997befeaf74f9c44c 100644 (file)
@@ -229,7 +229,7 @@ state RESTART_STATE:
 
 # popup_during_fullscreen
 state POPUP_DURING_FULLSCREEN:
-  value = 'ignore', 'leave_fullscreen'
+  value = 'ignore', 'leave_fullscreen', 'smart'
       -> call cfg_popup_during_fullscreen($value)
 
 # client.background <hexcolor>
@@ -272,7 +272,7 @@ state FONT:
 state BINDING:
   release = '--release'
       ->
-  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch'
+  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
       ->
   '+'
       ->
@@ -317,7 +317,7 @@ state MODE_IGNORE_LINE:
 state MODE_BINDING:
   release = '--release'
       ->
-  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch'
+  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
       ->
   '+'
       ->
@@ -419,7 +419,7 @@ state BAR_COLORS:
   end ->
   '#' -> BAR_COLORS_IGNORE_LINE
   'set' -> BAR_COLORS_IGNORE_LINE
-  colorclass = 'background', 'statusline'
+  colorclass = 'background', 'statusline', 'separator'
       -> BAR_COLORS_SINGLE
   colorclass = 'focused_workspace', 'active_workspace', 'inactive_workspace', 'urgent_workspace'
       -> BAR_COLORS_BORDER
diff --git a/src/cfgparse.l b/src/cfgparse.l
deleted file mode 100644 (file)
index 6eef8a5..0000000
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- */
-%option nounput
-%option noinput
-%option noyy_top_state
-%option stack
-
-%{
-#include <stdio.h>
-#include <string.h>
-#include <stdint.h>
-#include <xcb/xcb.h>
-
-#include "log.h"
-#include "data.h"
-#include "config.h"
-#include "util.h"
-#include "libi3.h"
-
-#include "cfgparse.tab.h"
-
-int yycolumn = 1;
-
-#define YY_DECL int yylex (struct context *context)
-
-#define YY_USER_ACTION { \
-    context->first_column = yycolumn; \
-    context->last_column = yycolumn+yyleng-1; \
-    yycolumn += yyleng; \
-}
-
-/* macro to first eat whitespace, then expect a string */
-#define WS_STRING do { \
-    yy_push_state(WANT_STRING); \
-    yy_push_state(EAT_WHITESPACE); \
-} while (0)
-
-#define BAR_TRIPLE_COLOR do { \
-    yy_push_state(BAR_COLOR); \
-    yy_push_state(BAR_COLOR); \
-    yy_push_state(BAR_COLOR); \
-} while (0)
-
-%}
-
-EOL     (\r?\n)
-
-%s WANT_STRING
-%s WANT_QSTRING
-%s BINDSYM_COND
-%s ASSIGN_COND
-%s ASSIGN_TARGET_COND
-%s COLOR_COND
-%s OUTPUT_COND
-%s FOR_WINDOW_COND
-%s EAT_WHITESPACE
-%s BORDER_WIDTH
-
-%x BUFFER_LINE
-%x BAR
-%x BAR_MODE
-%x BAR_MODIFIER
-%x BAR_POSITION
-%x BAR_COLORS
-%x BAR_COLOR
-
-%x EXEC
-%x OPTRELEASE
-
-%%
-
-    {
-        /* This is called when a new line is lexed. We only want the
-         * first line to match to go into state BUFFER_LINE */
-        if (context->line_number == 0) {
-            context->line_number = 1;
-            BEGIN(INITIAL);
-            yy_push_state(BUFFER_LINE);
-        }
-    }
-
-<BUFFER_LINE>^[^\r\n]*/{EOL}? {
-    /* save whole line */
-    context->line_copy = sstrdup(yytext);
-
-    yyless(0);
-    yy_pop_state();
-    yy_set_bol(true);
-    yycolumn = 1;
-}
-
- /* This part of the lexer handles the bar {} blocks */
-<BAR,BAR_MODE,BAR_MODIFIER,BAR_POSITION,BAR_COLORS,BAR_COLOR>[ \t]+ { /* ignore whitespace */ ; }
-<BAR>"{"                        { return '{'; }
-<BAR>"}"                        { yy_pop_state(); return '}'; }
-<BAR>^[ \t]*#[^\n]*             { return TOKCOMMENT; }
-<BAR>output                     { WS_STRING; return TOK_BAR_OUTPUT; }
-<BAR>tray_output                { WS_STRING; return TOK_BAR_TRAY_OUTPUT; }
-<BAR>socket_path                { WS_STRING; return TOK_BAR_SOCKET_PATH; }
-<BAR>mode                       { yy_push_state(BAR_MODE); return TOK_BAR_MODE; }
-<BAR_MODE>hide                  { yy_pop_state(); return TOK_BAR_HIDE; }
-<BAR_MODE>dock                  { yy_pop_state(); return TOK_BAR_DOCK; }
-<BAR>modifier                   { yy_push_state(BAR_MODIFIER); return TOK_BAR_MODIFIER; }
-<BAR_MODIFIER>control           { yy_pop_state(); return TOK_BAR_CONTROL; }
-<BAR_MODIFIER>ctrl              { yy_pop_state(); return TOK_BAR_CONTROL; }
-<BAR_MODIFIER>shift             { yy_pop_state(); return TOK_BAR_SHIFT; }
-<BAR_MODIFIER>Mod1              { yy_pop_state(); return TOK_BAR_MOD1; }
-<BAR_MODIFIER>Mod2              { yy_pop_state(); return TOK_BAR_MOD2; }
-<BAR_MODIFIER>Mod3              { yy_pop_state(); return TOK_BAR_MOD3; }
-<BAR_MODIFIER>Mod4              { yy_pop_state(); return TOK_BAR_MOD4; }
-<BAR_MODIFIER>Mod5              { yy_pop_state(); return TOK_BAR_MOD5; }
-<BAR>position                   { yy_push_state(BAR_POSITION); return TOK_BAR_POSITION; }
-<BAR_POSITION>bottom            { yy_pop_state(); return TOK_BAR_BOTTOM; }
-<BAR_POSITION>top               { yy_pop_state(); return TOK_BAR_TOP; }
-<BAR>status_command             { WS_STRING; return TOK_BAR_STATUS_COMMAND; }
-<BAR>i3bar_command              { WS_STRING; return TOK_BAR_I3BAR_COMMAND; }
-<BAR>font                       { WS_STRING; return TOK_BAR_FONT; }
-<BAR>workspace_buttons          { return TOK_BAR_WORKSPACE_BUTTONS; }
-<BAR>verbose                    { return TOK_BAR_VERBOSE; }
-<BAR>colors                     { yy_push_state(BAR_COLORS); return TOK_BAR_COLORS; }
-<BAR_COLORS>"{"                 { return '{'; }
-<BAR_COLORS>"}"                 { yy_pop_state(); return '}'; }
-<BAR_COLORS>^[ \t]*#[^\n]*      { return TOKCOMMENT; }
-<BAR_COLORS>background          { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_BACKGROUND; }
-<BAR_COLORS>statusline          { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_STATUSLINE; }
-<BAR_COLORS>focused_workspace   { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_FOCUSED_WORKSPACE; }
-<BAR_COLORS>active_workspace    { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_ACTIVE_WORKSPACE; }
-<BAR_COLORS>inactive_workspace  { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_INACTIVE_WORKSPACE; }
-<BAR_COLORS>urgent_workspace    { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_URGENT_WORKSPACE; }
-<BAR_COLOR>#[0-9a-fA-F]+        { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
-<BAR_COLOR>{EOL}                {
-                                  yy_pop_state();
-                                  FREE(context->line_copy);
-                                  context->line_number++;
-                                  yy_push_state(BUFFER_LINE);
-                                }
-<BAR,BAR_COLORS,BAR_MODE,BAR_MODIFIER,BAR_POSITION>[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; }
-
-
-
-<FOR_WINDOW_COND>"]"            { yy_pop_state(); return ']'; }
-<ASSIGN_COND>"["                {
-                                  /* this is the case for the new assign syntax
-                                   * that uses criteria */
-                                  yy_pop_state();
-                                  yy_push_state(FOR_WINDOW_COND);
-                                  /* afterwards we will be in ASSIGN_TARGET_COND */
-                                  return '[';
-                                }
-<EAT_WHITESPACE>[ \t]*          { yy_pop_state(); }
-<EAT_WHITESPACE>{EOL}           { yy_pop_state(); }
-<BINDSYM_COND>{EOL}             { yy_pop_state(); }
-<WANT_QSTRING>\"[^\"]+\"        {
-                                  yy_pop_state();
-                                  /* strip quotes */
-                                  char *copy = sstrdup(yytext+1);
-                                  copy[strlen(copy)-1] = '\0';
-                                  yylval.string = copy;
-                                  return STR;
-                                }
-<WANT_STRING>[^\n]+             { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; }
-<OUTPUT_COND>[a-zA-Z0-9\/_-]+   { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; }
-^[ \t]*#[^\n]*                  { return TOKCOMMENT; }
-<COLOR_COND>#[0-9a-fA-F]+       { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
-<COLOR_COND>{EOL}               {
-                                  yy_pop_state();
-                                  FREE(context->line_copy);
-                                  context->line_number++;
-                                  yy_push_state(BUFFER_LINE);
-                                }
-<ASSIGN_TARGET_COND>[ \t]*→[ \t]*     { BEGIN(WANT_STRING); }
-<ASSIGN_TARGET_COND>[ \t]+      { BEGIN(WANT_STRING); }
-<BORDER_WIDTH>[^\n][0-9]+       { printf("Border width set to: %s\n", yytext); yylval.number = atoi(yytext); return NUMBER;}
-<EXEC>--no-startup-id           { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; }
-<EXEC>.                         { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
-<OPTRELEASE>--release           { printf("--release\n"); yy_pop_state(); return TOK_RELEASE; }
-<OPTRELEASE>.                   { printf("anything else (optrelease): *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
-[0-9-]+                         { yylval.number = atoi(yytext); return NUMBER; }
-bar                             { yy_push_state(BAR); return TOK_BAR; }
-mode                            { return TOKMODE; }
-bind                            { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
-bindcode                        { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
-bindsym                         { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; }
-floating_maximum_size           { return TOKFLOATING_MAXIMUM_SIZE; }
-floating_minimum_size           { return TOKFLOATING_MINIMUM_SIZE; }
-floating_modifier               { return TOKFLOATING_MODIFIER; }
-workspace                       { return TOKWORKSPACE; }
-output                          { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; }
-terminal                        { WS_STRING; return TOKTERMINAL; }
-font                            { WS_STRING; return TOKFONT; }
-assign                          { yy_push_state(ASSIGN_TARGET_COND); yy_push_state(ASSIGN_COND); return TOKASSIGN; }
-set[^\n]*                       { return TOKCOMMENT; }
-ipc-socket                      { WS_STRING; return TOKIPCSOCKET; }
-ipc_socket                      { WS_STRING; return TOKIPCSOCKET; }
-restart_state                   { WS_STRING; return TOKRESTARTSTATE; }
-default_orientation             { return TOK_ORIENTATION; }
-horizontal                      { return TOK_HORIZ; }
-vertical                        { return TOK_VERT; }
-auto                            { return TOK_AUTO; }
-workspace_layout                { return TOK_WORKSPACE_LAYOUT; }
-new_window                      { return TOKNEWWINDOW; }
-new_float                       { return TOKNEWFLOAT; }
-normal                          { yy_push_state(BORDER_WIDTH); return TOK_NORMAL; }
-none                            { return TOK_NONE; }
-1pixel                          { return TOK_1PIXEL; }
-pixel                           { yy_push_state(BORDER_WIDTH); return TOK_PIXEL; }
-hide_edge_borders               { return TOK_HIDE_EDGE_BORDERS; }
-both                            { return TOK_BOTH; }
-focus_follows_mouse             { return TOKFOCUSFOLLOWSMOUSE; }
-force_focus_wrapping            { return TOK_FORCE_FOCUS_WRAPPING; }
-force_xinerama                  { return TOK_FORCE_XINERAMA; }
-force-xinerama                  { return TOK_FORCE_XINERAMA; }
-fake_outputs                    { WS_STRING; return TOK_FAKE_OUTPUTS; }
-fake-outputs                    { WS_STRING; return TOK_FAKE_OUTPUTS; }
-workspace_auto_back_and_forth   { return TOK_WORKSPACE_AUTO_BAF; }
-force_display_urgency_hint      { return TOK_WORKSPACE_URGENCY_TIMER; }
-ms                              { return TOK_TIME_MS; }
-workspace_bar                   { return TOKWORKSPACEBAR; }
-popup_during_fullscreen         { return TOK_POPUP_DURING_FULLSCREEN; }
-ignore                          { return TOK_IGNORE; }
-leave_fullscreen                { return TOK_LEAVE_FULLSCREEN; }
-for_window                      {
-                                  /* Example: for_window [class="urxvt"] border none
-                                   *
-                                   * First, we wait for the ']' that finishes a match (FOR_WINDOW_COND)
-                                   * Then, we require a whitespace (EAT_WHITESPACE)
-                                   * And the rest of the line is parsed as a string
-                                   */
-                                  yy_push_state(WANT_STRING);
-                                  yy_push_state(EAT_WHITESPACE);
-                                  yy_push_state(FOR_WINDOW_COND);
-                                  return TOK_FOR_WINDOW;
-                                }
-default                         { /* yylval.number = MODE_DEFAULT; */return TOK_DEFAULT; }
-stacking                        { /* yylval.number = MODE_STACK; */return TOK_STACKING; }
-stacked                         { return TOK_STACKING; }
-tabbed                          { /* yylval.number = MODE_TABBED; */return TOK_TABBED; }
-stack-limit                     { return TOKSTACKLIMIT; }
-cols                            { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; }
-rows                            { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; }
-exec                            { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOKEXEC; }
-exec_always                     { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOKEXEC_ALWAYS; }
-client.background               { yy_push_state(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; }
-client.focused                  { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; }
-client.focused_inactive         { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; }
-client.unfocused                { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; }
-client.urgent                   { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; }
-bar.focused                     { yy_push_state(COLOR_COND); yylval.color = &config.bar.focused; return TOKCOLOR; }
-bar.unfocused                   { yy_push_state(COLOR_COND); yylval.color = &config.bar.unfocused; return TOKCOLOR; }
-bar.urgent                      { yy_push_state(COLOR_COND); yylval.color = &config.bar.urgent; return TOKCOLOR; }
-Mod1                            { yylval.number = BIND_MOD1; return MODIFIER; }
-Mod2                            { yylval.number = BIND_MOD2; return MODIFIER; }
-Mod3                            { yylval.number = BIND_MOD3; return MODIFIER; }
-Mod4                            { yylval.number = BIND_MOD4; return MODIFIER; }
-Mod5                            { yylval.number = BIND_MOD5; return MODIFIER; }
-Mode_switch                     { yylval.number = BIND_MODE_SWITCH; return MODIFIER; }
-control                         { return TOKCONTROL; }
-ctrl                            { return TOKCONTROL; }
-shift                           { return TOKSHIFT; }
-
-class                           { yy_push_state(WANT_QSTRING); return TOK_CLASS; }
-instance                        { yy_push_state(WANT_QSTRING); return TOK_INSTANCE; }
-window_role                     { yy_push_state(WANT_QSTRING); return TOK_WINDOW_ROLE; }
-id                              { yy_push_state(WANT_QSTRING); return TOK_ID; }
-con_id                          { yy_push_state(WANT_QSTRING); return TOK_CON_ID; }
-con_mark                        { yy_push_state(WANT_QSTRING); return TOK_MARK; }
-title                           { yy_push_state(WANT_QSTRING); return TOK_TITLE; }
-urgent                          { yy_push_state(WANT_QSTRING); return TOK_URGENT; }
-
-<*>{EOL}                        {
-                                  FREE(context->line_copy);
-                                  context->line_number++;
-                                  yy_push_state(BUFFER_LINE);
-                                }
-<BINDSYM_COND>[ \t]+            { yy_pop_state(); yy_push_state(WANT_STRING); }
-<OUTPUT_COND>[ \t]+             { yy_pop_state(); yy_push_state(WANT_STRING); }
-[ \t]+                          { /* ignore whitespace */ ; }
-\"[^\"]+\"                      {
-                                  /* if ASSIGN_COND then */
-                                  if (yy_start_stack_ptr > 0)
-                                      yy_pop_state();
-                                  /* yylval will be the string, but without quotes */
-                                  char *copy = sstrdup(yytext+1);
-                                  copy[strlen(copy)-1] = '\0';
-                                  yylval.string = copy;
-                                  return QUOTEDSTRING;
-                                }
-<ASSIGN_COND>[^ \t\"\[]+        { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; }
-<BINDSYM_COND>[a-zA-Z0-9_]+     { yylval.string = sstrdup(yytext); return WORD; }
-[a-zA-Z]+                       { yylval.string = sstrdup(yytext); return WORD; }
-.                               { return (int)yytext[0]; }
-
-<<EOF>> {
-    while (yy_start_stack_ptr > 0)
-        yy_pop_state();
-    yyterminate();
-}
-
-%%
diff --git a/src/cfgparse.y b/src/cfgparse.y
deleted file mode 100644 (file)
index 2a22aae..0000000
+++ /dev/null
@@ -1,1875 +0,0 @@
-%{
-/*
- * vim:ts=4:sw=4:expandtab
- *
- */
-#undef I3__FILE__
-#define I3__FILE__ "cfgparse.y"
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-#include "all.h"
-
-bool force_old_config_parser = false;
-
-static pid_t configerror_pid = -1;
-
-static Match current_match;
-static Barconfig current_bar;
-/* The pattern which was specified by the user, for example -misc-fixed-*. We
- * store this in a separate variable because in the i3 config struct we just
- * store the i3Font. */
-static char *font_pattern;
-/* The path to the temporary script files used by i3-nagbar. We need to keep
- * them around to delete the files in the i3-nagbar SIGCHLD handler. */
-static char *edit_script_path, *pager_script_path;
-
-typedef struct yy_buffer_state *YY_BUFFER_STATE;
-extern int yylex(struct context *context);
-extern int yyparse(void);
-extern int yylex_destroy(void);
-extern FILE *yyin;
-YY_BUFFER_STATE yy_scan_string(const char *);
-
-static struct bindings_head *current_bindings;
-static struct context *context;
-
-/* We don’t need yydebug for now, as we got decent error messages using
- * yyerror(). Should you ever want to extend the parser, it might be handy
- * to just comment it in again, so it stays here. */
-//int yydebug = 1;
-
-void yyerror(const char *error_message) {
-    context->has_errors = true;
-
-    ELOG("\n");
-    ELOG("CONFIG: %s\n", error_message);
-    ELOG("CONFIG: in file \"%s\", line %d:\n",
-        context->filename, context->line_number);
-    ELOG("CONFIG:   %s\n", context->line_copy);
-    char buffer[context->last_column+1];
-    buffer[context->last_column] = '\0';
-    for (int c = 1; c <= context->last_column; c++)
-        buffer[c-1] = (c >= context->first_column ? '^' : ' ');
-    ELOG("CONFIG:   %s\n", buffer);
-    ELOG("\n");
-}
-
-int yywrap(void) {
-    return 1;
-}
-
-/*
- * Goes through each line of buf (separated by \n) and checks for statements /
- * commands which only occur in i3 v4 configuration files. If it finds any, it
- * returns version 4, otherwise it returns version 3.
- *
- */
-static int detect_version(char *buf) {
-    char *walk = buf;
-    char *line = buf;
-    while (*walk != '\0') {
-        if (*walk != '\n') {
-            walk++;
-            continue;
-        }
-
-        /* check for some v4-only statements */
-        if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
-            strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
-            strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
-            strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
-            printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
-            return 4;
-        }
-
-        /* if this is a bind statement, we can check the command */
-        if (strncasecmp(line, "bind", strlen("bind")) == 0) {
-            char *bind = strchr(line, ' ');
-            if (bind == NULL)
-                goto next;
-            while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
-                bind++;
-            if (*bind == '\0')
-                goto next;
-            if ((bind = strchr(bind, ' ')) == NULL)
-                goto next;
-            while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
-                bind++;
-            if (*bind == '\0')
-                goto next;
-            if (strncasecmp(bind, "layout", strlen("layout")) == 0 ||
-                strncasecmp(bind, "floating", strlen("floating")) == 0 ||
-                strncasecmp(bind, "workspace", strlen("workspace")) == 0 ||
-                strncasecmp(bind, "focus left", strlen("focus left")) == 0 ||
-                strncasecmp(bind, "focus right", strlen("focus right")) == 0 ||
-                strncasecmp(bind, "focus up", strlen("focus up")) == 0 ||
-                strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
-                strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
-                strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
-                strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 ||
-                strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 ||
-                strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 ||
-                strncasecmp(bind, "bar", strlen("bar")) == 0) {
-                printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
-                return 4;
-            }
-        }
-
-next:
-        /* advance to the next line */
-        walk++;
-        line = walk;
-    }
-
-    return 3;
-}
-
-/*
- * Calls i3-migrate-config-to-v4 to migrate a configuration file (input
- * buffer).
- *
- * Returns the converted config file or NULL if there was an error (for
- * example the script could not be found in $PATH or the i3 executable’s
- * directory).
- *
- */
-static char *migrate_config(char *input, off_t size) {
-    int writepipe[2];
-    int readpipe[2];
-
-    if (pipe(writepipe) != 0 ||
-        pipe(readpipe) != 0) {
-        warn("migrate_config: Could not create pipes");
-        return NULL;
-    }
-
-    pid_t pid = fork();
-    if (pid == -1) {
-        warn("Could not fork()");
-        return NULL;
-    }
-
-    /* child */
-    if (pid == 0) {
-        /* close writing end of writepipe, connect reading side to stdin */
-        close(writepipe[1]);
-        dup2(writepipe[0], 0);
-
-        /* close reading end of readpipe, connect writing side to stdout */
-        close(readpipe[0]);
-        dup2(readpipe[1], 1);
-
-        static char *argv[] = {
-            NULL, /* will be replaced by the executable path */
-            NULL
-        };
-        exec_i3_utility("i3-migrate-config-to-v4", argv);
-    }
-
-    /* parent */
-
-    /* close reading end of the writepipe (connected to the script’s stdin) */
-    close(writepipe[0]);
-
-    /* write the whole config file to the pipe, the script will read everything
-     * immediately */
-    int written = 0;
-    int ret;
-    while (written < size) {
-        if ((ret = write(writepipe[1], input + written, size - written)) < 0) {
-            warn("Could not write to pipe");
-            return NULL;
-        }
-        written += ret;
-    }
-    close(writepipe[1]);
-
-    /* close writing end of the readpipe (connected to the script’s stdout) */
-    close(readpipe[1]);
-
-    /* read the script’s output */
-    int conv_size = 65535;
-    char *converted = malloc(conv_size);
-    int read_bytes = 0;
-    do {
-        if (read_bytes == conv_size) {
-            conv_size += 65535;
-            converted = realloc(converted, conv_size);
-        }
-        ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
-        if (ret == -1) {
-            warn("Cannot read from pipe");
-            FREE(converted);
-            return NULL;
-        }
-        read_bytes += ret;
-    } while (ret > 0);
-
-    /* get the returncode */
-    int status;
-    wait(&status);
-    if (!WIFEXITED(status)) {
-        fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
-        return NULL;
-    }
-
-    int returncode = WEXITSTATUS(status);
-    if (returncode != 0) {
-        fprintf(stderr, "Migration process exit code was != 0\n");
-        if (returncode == 2) {
-            fprintf(stderr, "could not start the migration script\n");
-            /* TODO: script was not found. tell the user to fix his system or create a v4 config */
-        } else if (returncode == 1) {
-            fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n");
-            fprintf(stderr, "# i3 config file (v4)\n");
-            /* TODO: nag the user with a message to include a hint for i3 in his config file */
-        }
-        return NULL;
-    }
-
-    return converted;
-}
-
-/*
- * Handler which will be called when we get a SIGCHLD for the nagbar, meaning
- * it exited (or could not be started, depending on the exit code).
- *
- */
-static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
-    ev_child_stop(EV_A_ watcher);
-
-    if (unlink(edit_script_path) != 0)
-        warn("Could not delete temporary i3-nagbar script %s", edit_script_path);
-    if (unlink(pager_script_path) != 0)
-        warn("Could not delete temporary i3-nagbar script %s", pager_script_path);
-
-    if (!WIFEXITED(watcher->rstatus)) {
-        fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
-        return;
-    }
-
-    int exitcode = WEXITSTATUS(watcher->rstatus);
-    printf("i3-nagbar process exited with status %d\n", exitcode);
-    if (exitcode == 2) {
-        fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
-    }
-
-    configerror_pid = -1;
-}
-
-/* We need ev >= 4 for the following code. Since it is not *that* important (it
- * only makes sure that there are no i3-nagbar instances left behind) we still
- * support old systems with libev 3. */
-#if EV_VERSION_MAJOR >= 4
-/*
- * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
- * SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
- *
- */
-static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
-    if (configerror_pid != -1) {
-        LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", configerror_pid);
-        kill(configerror_pid, SIGKILL);
-    }
-}
-#endif
-
-/*
- * Writes the given command as a shell script to path.
- * Returns true unless something went wrong.
- *
- */
-static bool write_nagbar_script(const char *path, const char *command) {
-    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR);
-    if (fd == -1) {
-        warn("Could not create temporary script to store the nagbar command");
-        return false;
-    }
-    write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n"));
-    write(fd, command, strlen(command));
-    close(fd);
-    return true;
-}
-
-/*
- * Starts an i3-nagbar process which alerts the user that his configuration
- * file contains one or more errors. Also offers two buttons: One to launch an
- * $EDITOR on the config file and another one to launch a $PAGER on the error
- * logfile.
- *
- */
-static void start_configerror_nagbar(const char *config_path) {
-    if (only_check_config)
-        return;
-
-    fprintf(stderr, "Starting i3-nagbar due to configuration errors\n");
-
-    /* We need to create a custom script containing our actual command
-     * since not every terminal emulator which is contained in
-     * i3-sensible-terminal supports -e with multiple arguments (and not
-     * all of them support -e with one quoted argument either).
-     *
-     * NB: The paths need to be unique, that is, don’t assume users close
-     * their nagbars at any point in time (and they still need to work).
-     * */
-    edit_script_path = get_process_filename("nagbar-cfgerror-edit");
-    pager_script_path = get_process_filename("nagbar-cfgerror-pager");
-
-    configerror_pid = fork();
-    if (configerror_pid == -1) {
-        warn("Could not fork()");
-        return;
-    }
-
-    /* child */
-    if (configerror_pid == 0) {
-        char *edit_command, *pager_command;
-        sasprintf(&edit_command, "i3-sensible-editor \"%s\" && i3-msg reload\n", config_path);
-        sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename);
-        if (!write_nagbar_script(edit_script_path, edit_command) ||
-            !write_nagbar_script(pager_script_path, pager_command))
-            return;
-
-        char *editaction,
-             *pageraction;
-        sasprintf(&editaction, "i3-sensible-terminal -e \"%s\"", edit_script_path);
-        sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path);
-        char *argv[] = {
-            NULL, /* will be replaced by the executable path */
-            "-t",
-            (context->has_errors ? "error" : "warning"),
-            "-m",
-            (context->has_errors ?
-             "You have an error in your i3 config file!" :
-             "Your config is outdated. Please fix the warnings to make sure everything works."),
-            "-b",
-            "edit config",
-            editaction,
-            (errorfilename ? "-b" : NULL),
-            (context->has_errors ? "show errors" : "show warnings"),
-            pageraction,
-            NULL
-        };
-        exec_i3_utility("i3-nagbar", argv);
-    }
-
-    /* parent */
-    /* install a child watcher */
-    ev_child *child = smalloc(sizeof(ev_child));
-    ev_child_init(child, &nagbar_exited, configerror_pid, 0);
-    ev_child_start(main_loop, child);
-
-/* We need ev >= 4 for the following code. Since it is not *that* important (it
- * only makes sure that there are no i3-nagbar instances left behind) we still
- * support old systems with libev 3. */
-#if EV_VERSION_MAJOR >= 4
-    /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
-     * still running) */
-    ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
-    ev_cleanup_init(cleanup, nagbar_cleanup);
-    ev_cleanup_start(main_loop, cleanup);
-#endif
-}
-
-/*
- * Kills the configerror i3-nagbar process, if any.
- *
- * Called when reloading/restarting.
- *
- * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
- * ev is assumed to handle it (reloading).
- *
- */
-void kill_configerror_nagbar(bool wait_for_it) {
-    if (configerror_pid == -1)
-        return;
-
-    if (kill(configerror_pid, SIGTERM) == -1)
-        warn("kill(configerror_nagbar) failed");
-
-    if (!wait_for_it)
-        return;
-
-    /* When restarting, we don’t enter the ev main loop anymore and after the
-     * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
-     * for us and we would end up with a <defunct> process. Therefore we
-     * waitpid() here. */
-    waitpid(configerror_pid, NULL, 0);
-}
-
-/*
- * Checks for duplicate key bindings (the same keycode or keysym is configured
- * more than once). If a duplicate binding is found, a message is printed to
- * stderr and the has_errors variable is set to true, which will start
- * i3-nagbar.
- *
- */
-static void check_for_duplicate_bindings(struct context *context) {
-    Binding *bind, *current;
-    TAILQ_FOREACH(current, bindings, bindings) {
-        TAILQ_FOREACH(bind, bindings, bindings) {
-            /* Abort when we reach the current keybinding, only check the
-             * bindings before */
-            if (bind == current)
-                break;
-
-            /* Check if one is using keysym while the other is using bindsym.
-             * If so, skip. */
-            /* XXX: It should be checked at a later place (when translating the
-             * keysym to keycodes) if there are any duplicates */
-            if ((bind->symbol == NULL && current->symbol != NULL) ||
-                (bind->symbol != NULL && current->symbol == NULL))
-                continue;
-
-            /* If bind is NULL, current has to be NULL, too (see above).
-             * If the keycodes differ, it can't be a duplicate. */
-            if (bind->symbol != NULL &&
-                strcasecmp(bind->symbol, current->symbol) != 0)
-                continue;
-
-            /* Check if the keycodes or modifiers are different. If so, they
-             * can't be duplicate */
-            if (bind->keycode != current->keycode ||
-                bind->mods != current->mods ||
-                bind->release != current->release)
-                continue;
-
-            context->has_errors = true;
-            if (current->keycode != 0) {
-                ELOG("Duplicate keybinding in config file:\n  modmask %d with keycode %d, command \"%s\"\n",
-                     current->mods, current->keycode, current->command);
-            } else {
-                ELOG("Duplicate keybinding in config file:\n  modmask %d with keysym %s, command \"%s\"\n",
-                     current->mods, current->symbol, current->command);
-            }
-        }
-    }
-}
-
-static void migrate_i3bar_exec(struct Autostart *exec) {
-    ELOG("**********************************************************************\n");
-    ELOG("IGNORING exec command: %s\n", exec->command);
-    ELOG("It contains \"i3bar\". Since i3 v4.1, i3bar will be automatically started\n");
-    ELOG("for each 'bar' configuration block in your i3 config. Please remove the exec\n");
-    ELOG("line and add the following to your i3 config:\n");
-    ELOG("\n");
-    ELOG("    bar {\n");
-    ELOG("        status_command i3status\n");
-    ELOG("    }\n");
-    ELOG("**********************************************************************\n");
-
-    /* Generate a dummy bar configuration */
-    Barconfig *bar_config = scalloc(sizeof(Barconfig));
-    /* The hard-coded ID is not a problem. It does not conflict with the
-     * auto-generated bar IDs and having multiple hard-coded IDs is irrelevant
-     * – they all just contain status_command = i3status */
-    bar_config->id = sstrdup("migrate-bar");
-    bar_config->status_command = sstrdup("i3status");
-    TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
-
-    /* Trigger an i3-nagbar */
-    context->has_warnings = true;
-}
-
-void parse_file(const char *f) {
-    SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
-    int fd, ret, read_bytes = 0;
-    struct stat stbuf;
-    char *buf;
-    FILE *fstr;
-    char buffer[1026], key[512], value[512];
-
-    if ((fd = open(f, O_RDONLY)) == -1)
-        die("Could not open configuration file: %s\n", strerror(errno));
-
-    if (fstat(fd, &stbuf) == -1)
-        die("Could not fstat file: %s\n", strerror(errno));
-
-    buf = scalloc((stbuf.st_size + 1) * sizeof(char));
-    while (read_bytes < stbuf.st_size) {
-        if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
-            die("Could not read(): %s\n", strerror(errno));
-        read_bytes += ret;
-    }
-
-    if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
-        die("Could not lseek: %s\n", strerror(errno));
-
-    if ((fstr = fdopen(fd, "r")) == NULL)
-        die("Could not fdopen: %s\n", strerror(errno));
-
-    while (!feof(fstr)) {
-        if (fgets(buffer, 1024, fstr) == NULL) {
-            if (feof(fstr))
-                break;
-            die("Could not read configuration file\n");
-        }
-
-        /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
-        if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
-            key[0] == '#' || strlen(key) < 3)
-            continue;
-
-        if (strcasecmp(key, "set") == 0) {
-            if (value[0] != '$') {
-                ELOG("Malformed variable assignment, name has to start with $\n");
-                continue;
-            }
-
-            /* get key/value for this variable */
-            char *v_key = value, *v_value;
-            if (strstr(value, " ") == NULL && strstr(value, "\t") == NULL) {
-                ELOG("Malformed variable assignment, need a value\n");
-                continue;
-            }
-
-            if (!(v_value = strstr(value, " ")))
-                v_value = strstr(value, "\t");
-
-            *(v_value++) = '\0';
-            while (*v_value == '\t' || *v_value == ' ')
-                v_value++;
-
-            struct Variable *new = scalloc(sizeof(struct Variable));
-            new->key = sstrdup(v_key);
-            new->value = sstrdup(v_value);
-            SLIST_INSERT_HEAD(&variables, new, variables);
-            DLOG("Got new variable %s = %s\n", v_key, v_value);
-            continue;
-        }
-    }
-    fclose(fstr);
-
-    /* For every custom variable, see how often it occurs in the file and
-     * how much extra bytes it requires when replaced. */
-    struct Variable *current, *nearest;
-    int extra_bytes = 0;
-    /* We need to copy the buffer because we need to invalidate the
-     * variables (otherwise we will count them twice, which is bad when
-     * 'extra' is negative) */
-    char *bufcopy = sstrdup(buf);
-    SLIST_FOREACH(current, &variables, variables) {
-        int extra = (strlen(current->value) - strlen(current->key));
-        char *next;
-        for (next = bufcopy;
-             next < (bufcopy + stbuf.st_size) &&
-             (next = strcasestr(next, current->key)) != NULL;
-             next += strlen(current->key)) {
-            *next = '_';
-            extra_bytes += extra;
-        }
-    }
-    FREE(bufcopy);
-
-    /* Then, allocate a new buffer and copy the file over to the new one,
-     * but replace occurences of our variables */
-    char *walk = buf, *destwalk;
-    char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
-    destwalk = new;
-    while (walk < (buf + stbuf.st_size)) {
-        /* Find the next variable */
-        SLIST_FOREACH(current, &variables, variables)
-            current->next_match = strcasestr(walk, current->key);
-        nearest = NULL;
-        int distance = stbuf.st_size;
-        SLIST_FOREACH(current, &variables, variables) {
-            if (current->next_match == NULL)
-                continue;
-            if ((current->next_match - walk) < distance) {
-                distance = (current->next_match - walk);
-                nearest = current;
-            }
-        }
-        if (nearest == NULL) {
-            /* If there are no more variables, we just copy the rest */
-            strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
-            destwalk += (buf + stbuf.st_size) - walk;
-            *destwalk = '\0';
-            break;
-        } else {
-            /* Copy until the next variable, then copy its value */
-            strncpy(destwalk, walk, distance);
-            strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
-            walk += distance + strlen(nearest->key);
-            destwalk += distance + strlen(nearest->value);
-        }
-    }
-
-    /* analyze the string to find out whether this is an old config file (3.x)
-     * or a new config file (4.x). If it’s old, we run the converter script. */
-    int version = detect_version(buf);
-    if (version == 3) {
-        /* We need to convert this v3 configuration */
-        char *converted = migrate_config(new, stbuf.st_size);
-        if (converted != NULL) {
-            ELOG("\n");
-            ELOG("****************************************************************\n");
-            ELOG("NOTE: Automatically converted configuration file from v3 to v4.\n");
-            ELOG("\n");
-            ELOG("Please convert your config file to v4. You can use this command:\n");
-            ELOG("    mv %s %s.O\n", f, f);
-            ELOG("    i3-migrate-config-to-v4 %s.O > %s\n", f, f);
-            ELOG("****************************************************************\n");
-            ELOG("\n");
-            free(new);
-            new = converted;
-        } else {
-            printf("\n");
-            printf("**********************************************************************\n");
-            printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4\n");
-            printf("was not correctly installed on your system?\n");
-            printf("**********************************************************************\n");
-            printf("\n");
-        }
-    }
-
-
-    context = scalloc(sizeof(struct context));
-    context->filename = f;
-
-    if (force_old_config_parser) {
-        /* now lex/parse it */
-        yy_scan_string(new);
-        if (yyparse() != 0) {
-            fprintf(stderr, "Could not parse configfile\n");
-            exit(1);
-        }
-    } else {
-        struct ConfigResult *config_output = parse_config(new, context);
-        yajl_gen_free(config_output->json_gen);
-    }
-
-    check_for_duplicate_bindings(context);
-
-    /* XXX: The following code will be removed in i3 v4.3 (three releases from
-     * now, as of 2011-10-22) */
-    /* Check for any exec or exec_always lines starting i3bar. We remove these
-     * and add a bar block instead. Additionally, a i3-nagbar warning (not an
-     * error) will be displayed so that users update their config file. */
-    struct Autostart *exec, *next;
-    for (exec = TAILQ_FIRST(&autostarts); exec; ) {
-        next = TAILQ_NEXT(exec, autostarts);
-        if (strstr(exec->command, "i3bar") != NULL) {
-            migrate_i3bar_exec(exec);
-            TAILQ_REMOVE(&autostarts, exec, autostarts);
-        }
-        exec = next;
-    }
-
-    for (exec = TAILQ_FIRST(&autostarts_always); exec; ) {
-        next = TAILQ_NEXT(exec, autostarts_always);
-        if (strstr(exec->command, "i3bar") != NULL) {
-            migrate_i3bar_exec(exec);
-            TAILQ_REMOVE(&autostarts_always, exec, autostarts_always);
-        }
-        exec = next;
-    }
-
-    if (context->has_errors || context->has_warnings) {
-        ELOG("FYI: You are using i3 version " I3_VERSION "\n");
-        if (version == 3)
-            ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
-        start_configerror_nagbar(f);
-    }
-
-    if (force_old_config_parser)
-        yylex_destroy();
-    FREE(context->line_copy);
-    free(context);
-    FREE(font_pattern);
-    free(new);
-    free(buf);
-
-    while (!SLIST_EMPTY(&variables)) {
-        current = SLIST_FIRST(&variables);
-        FREE(current->key);
-        FREE(current->value);
-        SLIST_REMOVE_HEAD(&variables, variables);
-        FREE(current);
-    }
-}
-
-%}
-
-%error-verbose
-%lex-param { struct context *context }
-
-%union {
-    int number;
-    char *string;
-    uint32_t *single_color;
-    struct Colortriple *color;
-    Match *match;
-    struct Binding *binding;
-}
-
-%token  <number>        NUMBER                      "<number>"
-%token  <string>        WORD                        "<word>"
-%token  <string>        STR                         "<string>"
-%token  <string>        STR_NG                      "<string (non-greedy)>"
-%token  <string>        HEXCOLOR                    "#<hex>"
-%token  <string>        OUTPUT                      "<RandR output>"
-%token                  TOKBINDCODE
-%token                  TOKTERMINAL
-%token                  TOKCOMMENT                  "<comment>"
-%token                  TOKFONT                     "font"
-%token                  TOKBINDSYM                  "bindsym"
-%token  <number>        MODIFIER                    "<modifier>"
-%token                  TOKCONTROL                  "control"
-%token                  TOKSHIFT                    "shift"
-%token                  TOKFLOATING_MODIFIER        "floating_modifier"
-%token                  TOKFLOATING_MAXIMUM_SIZE    "floating_maximum_size"
-%token                  TOKFLOATING_MINIMUM_SIZE    "floating_minimum_size"
-%token  <string>        QUOTEDSTRING                "<quoted string>"
-%token                  TOKWORKSPACE                "workspace"
-%token                  TOKOUTPUT                   "output"
-%token                  TOKASSIGN                   "assign"
-%token                  TOKSET
-%token                  TOKIPCSOCKET                "ipc_socket"
-%token                  TOKRESTARTSTATE             "restart_state"
-%token                  TOKEXEC                     "exec"
-%token                  TOKEXEC_ALWAYS              "exec_always"
-%token  <single_color>  TOKSINGLECOLOR
-%token  <color>         TOKCOLOR
-%token                  TOKARROW                    "→"
-%token                  TOKMODE                     "mode"
-%token                  TOK_TIME_MS                 "ms"
-%token                  TOK_BAR                     "bar"
-%token                  TOK_ORIENTATION             "default_orientation"
-%token                  TOK_HORIZ                   "horizontal"
-%token                  TOK_VERT                    "vertical"
-%token                  TOK_AUTO                    "auto"
-%token                  TOK_WORKSPACE_LAYOUT        "workspace_layout"
-%token                  TOKNEWWINDOW                "new_window"
-%token                  TOKNEWFLOAT                 "new_float"
-%token                  TOK_NORMAL                  "normal"
-%token                  TOK_NONE                    "none"
-%token                  TOK_PIXEL                   "pixel"
-%token                  TOK_1PIXEL                  "1pixel"
-%token                  TOK_HIDE_EDGE_BORDERS       "hide_edge_borders"
-%token                  TOK_BOTH                    "both"
-%token                  TOKFOCUSFOLLOWSMOUSE        "focus_follows_mouse"
-%token                  TOK_FORCE_FOCUS_WRAPPING    "force_focus_wrapping"
-%token                  TOK_FORCE_XINERAMA          "force_xinerama"
-%token                  TOK_FAKE_OUTPUTS            "fake_outputs"
-%token                  TOK_WORKSPACE_AUTO_BAF      "workspace_auto_back_and_forth"
-%token                  TOK_WORKSPACE_URGENCY_TIMER "force_display_urgency_hint"
-%token                  TOKWORKSPACEBAR             "workspace_bar"
-%token                  TOK_DEFAULT                 "default"
-%token                  TOK_STACKING                "stacking"
-%token                  TOK_TABBED                  "tabbed"
-%token  <number>        TOKSTACKLIMIT               "stack-limit"
-%token                  TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen"
-%token                  TOK_IGNORE                  "ignore"
-%token                  TOK_LEAVE_FULLSCREEN        "leave_fullscreen"
-%token                  TOK_FOR_WINDOW              "for_window"
-
-%token                  TOK_BAR_OUTPUT              "output (bar)"
-%token                  TOK_BAR_TRAY_OUTPUT         "tray_output"
-%token                  TOK_BAR_SOCKET_PATH         "socket_path"
-%token                  TOK_BAR_MODE                "mode (bar)"
-%token                  TOK_BAR_HIDE                "hide"
-%token                  TOK_BAR_DOCK                "dock"
-%token                  TOK_BAR_MODIFIER            "modifier (bar)"
-%token                  TOK_BAR_CONTROL             "shift (bar)"
-%token                  TOK_BAR_SHIFT               "control (bar)"
-%token                  TOK_BAR_MOD1                "Mod1"
-%token                  TOK_BAR_MOD2                "Mod2"
-%token                  TOK_BAR_MOD3                "Mod3"
-%token                  TOK_BAR_MOD4                "Mod4"
-%token                  TOK_BAR_MOD5                "Mod5"
-%token                  TOK_BAR_POSITION            "position"
-%token                  TOK_BAR_BOTTOM              "bottom"
-%token                  TOK_BAR_TOP                 "top"
-%token                  TOK_BAR_STATUS_COMMAND      "status_command"
-%token                  TOK_BAR_I3BAR_COMMAND       "i3bar_command"
-%token                  TOK_BAR_FONT                "font (bar)"
-%token                  TOK_BAR_WORKSPACE_BUTTONS   "workspace_buttons"
-%token                  TOK_BAR_VERBOSE             "verbose"
-%token                  TOK_BAR_COLORS              "colors"
-%token                  TOK_BAR_COLOR_BACKGROUND    "background"
-%token                  TOK_BAR_COLOR_STATUSLINE    "statusline"
-%token                  TOK_BAR_COLOR_FOCUSED_WORKSPACE "focused_workspace"
-%token                  TOK_BAR_COLOR_ACTIVE_WORKSPACE "active_workspace"
-%token                  TOK_BAR_COLOR_INACTIVE_WORKSPACE "inactive_workspace"
-%token                  TOK_BAR_COLOR_URGENT_WORKSPACE "urgent_workspace"
-%token                  TOK_NO_STARTUP_ID           "--no-startup-id"
-%token                  TOK_RELEASE                 "--release"
-
-%token              TOK_MARK            "mark"
-%token              TOK_CLASS           "class"
-%token              TOK_INSTANCE        "instance"
-%token              TOK_WINDOW_ROLE     "window_role"
-%token              TOK_ID              "id"
-%token              TOK_CON_ID          "con_id"
-%token              TOK_TITLE           "title"
-%token              TOK_URGENT          "urgent"
-
-%type   <binding>       binding
-%type   <binding>       bindcode
-%type   <binding>       bindsym
-%type   <number>        binding_modifiers
-%type   <number>        binding_modifier
-%type   <number>        direction
-%type   <number>        layout_mode
-%type   <number>        border_style
-%type   <number>        new_window
-%type   <number>        hide_edge_borders
-%type   <number>        edge_hiding_mode
-%type   <number>        new_float
-%type   <number>        colorpixel
-%type   <number>        bool
-%type   <number>        popup_setting
-%type   <number>        bar_position_position
-%type   <number>        bar_mode_mode
-%type   <number>        bar_modifier_modifier
-%type   <number>        optional_no_startup_id
-%type   <number>        optional_border_width
-%type   <number>        optional_release
-%type   <string>        command
-%type   <string>        word_or_number
-%type   <string>        duration
-%type   <string>        qstring_or_number
-%type   <string>        optional_workspace_name
-%type   <string>        workspace_name
-%type   <string>        window_class
-
-%%
-
-lines: /* empty */
-    | lines error
-    | lines line
-    ;
-
-line:
-    bindline
-    | for_window
-    | mode
-    | bar
-    | floating_maximum_size
-    | floating_minimum_size
-    | floating_modifier
-    | orientation
-    | workspace_layout
-    | new_window
-    | new_float
-    | hide_edge_borders
-    | focus_follows_mouse
-    | force_focus_wrapping
-    | force_xinerama
-    | fake_outputs
-    | force_display_urgency_hint
-    | workspace_back_and_forth
-    | workspace_bar
-    | workspace
-    | assign
-    | ipcsocket
-    | restart_state
-    | exec
-    | exec_always
-    | single_color
-    | color
-    | terminal
-    | font
-    | comment
-    | popup_during_fullscreen
-    ;
-
-comment:
-    TOKCOMMENT
-    ;
-
-command:
-    STR
-    ;
-
-bindline:
-    binding
-    {
-        TAILQ_INSERT_TAIL(bindings, $1, bindings);
-    }
-    ;
-
-binding:
-    TOKBINDCODE bindcode         { $$ = $2; }
-    | TOKBINDSYM bindsym         { $$ = $2; }
-    ;
-
-bindcode:
-    optional_release binding_modifiers NUMBER command
-    {
-        DLOG("bindcode: release = %d, mod = %d, key = %d, command = %s\n", $1, $2, $3, $4);
-        Binding *new = scalloc(sizeof(Binding));
-
-        new->release = $1;
-        new->keycode = $3;
-        new->mods = $2;
-        new->command = $4;
-
-        $$ = new;
-    }
-    ;
-
-bindsym:
-    optional_release binding_modifiers word_or_number command
-    {
-        DLOG("bindsym: release = %d, mod = %d, key = %s, command = %s\n", $1, $2, $3, $4);
-        Binding *new = scalloc(sizeof(Binding));
-
-        new->release = $1;
-        new->symbol = $3;
-        new->mods = $2;
-        new->command = $4;
-
-        $$ = new;
-    }
-    ;
-
-optional_release:
-    /* empty */ { $$ = B_UPON_KEYPRESS; }
-    | TOK_RELEASE  { $$ = B_UPON_KEYRELEASE; }
-    ;
-
-for_window:
-    TOK_FOR_WINDOW match command
-    {
-        if (match_is_empty(&current_match)) {
-            ELOG("Match is empty, ignoring this for_window statement\n");
-            break;
-        }
-        printf("\t should execute command %s for the criteria mentioned above\n", $3);
-        Assignment *assignment = scalloc(sizeof(Assignment));
-        assignment->type = A_COMMAND;
-        assignment->match = current_match;
-        assignment->dest.command = $3;
-        TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
-    }
-    ;
-
-match:
-    | matchstart criteria matchend
-    {
-        printf("match parsed\n");
-    }
-    ;
-
-matchstart:
-    '['
-    {
-        printf("start\n");
-        match_init(&current_match);
-    }
-    ;
-
-matchend:
-    ']'
-    {
-        printf("match specification finished\n");
-    }
-    ;
-
-criteria:
-    criteria criterion
-    | criterion
-    ;
-
-criterion:
-    TOK_CLASS '=' STR
-    {
-        printf("criteria: class = %s\n", $3);
-        current_match.class = regex_new($3);
-        free($3);
-    }
-    | TOK_INSTANCE '=' STR
-    {
-        printf("criteria: instance = %s\n", $3);
-        current_match.instance = regex_new($3);
-        free($3);
-    }
-    | TOK_WINDOW_ROLE '=' STR
-    {
-        printf("criteria: window_role = %s\n", $3);
-        current_match.role = regex_new($3);
-        free($3);
-    }
-    | TOK_CON_ID '=' STR
-    {
-        printf("criteria: id = %s\n", $3);
-        char *end;
-        long parsed = strtol($3, &end, 10);
-        if (parsed == LONG_MIN ||
-            parsed == LONG_MAX ||
-            parsed < 0 ||
-            (end && *end != '\0')) {
-            ELOG("Could not parse con id \"%s\"\n", $3);
-        } else {
-            current_match.con_id = (Con*)parsed;
-            printf("id as int = %p\n", current_match.con_id);
-        }
-    }
-    | TOK_ID '=' STR
-    {
-        printf("criteria: window id = %s\n", $3);
-        char *end;
-        long parsed = strtol($3, &end, 10);
-        if (parsed == LONG_MIN ||
-            parsed == LONG_MAX ||
-            parsed < 0 ||
-            (end && *end != '\0')) {
-            ELOG("Could not parse window id \"%s\"\n", $3);
-        } else {
-            current_match.id = parsed;
-            printf("window id as int = %d\n", current_match.id);
-        }
-    }
-    | TOK_MARK '=' STR
-    {
-        printf("criteria: mark = %s\n", $3);
-        current_match.mark = regex_new($3);
-        free($3);
-    }
-    | TOK_TITLE '=' STR
-    {
-        printf("criteria: title = %s\n", $3);
-        current_match.title = regex_new($3);
-        free($3);
-    }
-    | TOK_URGENT '=' STR
-    {
-        printf("criteria: urgent = %s\n", $3);
-        if (strcasecmp($3, "latest") == 0 ||
-            strcasecmp($3, "newest") == 0 ||
-            strcasecmp($3, "recent") == 0 ||
-            strcasecmp($3, "last") == 0) {
-            current_match.urgent = U_LATEST;
-        } else if (strcasecmp($3, "oldest") == 0 ||
-                   strcasecmp($3, "first") == 0) {
-            current_match.urgent = U_OLDEST;
-        }
-        free($3);
-    }
-    ;
-
-qstring_or_number:
-    QUOTEDSTRING
-    | NUMBER { sasprintf(&$$, "%d", $1); }
-    ;
-
-word_or_number:
-    WORD
-    | NUMBER
-    {
-        sasprintf(&$$, "%d", $1);
-    }
-    ;
-
-duration:
-    NUMBER { sasprintf(&$$, "%d", $1); }
-    | NUMBER TOK_TIME_MS { sasprintf(&$$, "%d", $1); }
-    ;
-
-mode:
-    TOKMODE QUOTEDSTRING '{' modelines '}'
-    {
-        if (strcasecmp($2, "default") == 0) {
-            printf("You cannot use the name \"default\" for your mode\n");
-            exit(1);
-        }
-        printf("\t now in mode %s\n", $2);
-        printf("\t current bindings = %p\n", current_bindings);
-        Binding *binding;
-        TAILQ_FOREACH(binding, current_bindings, bindings) {
-            printf("got binding on mods %d, keycode %d, symbol %s, command %s\n",
-                            binding->mods, binding->keycode, binding->symbol, binding->command);
-        }
-
-        struct Mode *mode = scalloc(sizeof(struct Mode));
-        mode->name = $2;
-        mode->bindings = current_bindings;
-        current_bindings = NULL;
-        SLIST_INSERT_HEAD(&modes, mode, modes);
-    }
-    ;
-
-
-modelines:
-    /* empty */
-    | modelines modeline
-    ;
-
-modeline:
-    comment
-    | binding
-    {
-        if (current_bindings == NULL) {
-            current_bindings = scalloc(sizeof(struct bindings_head));
-            TAILQ_INIT(current_bindings);
-        }
-
-        TAILQ_INSERT_TAIL(current_bindings, $1, bindings);
-    }
-    ;
-
-bar:
-    TOK_BAR '{' barlines '}'
-    {
-        printf("\t new bar configuration finished, saving.\n");
-        /* Generate a unique ID for this bar */
-        current_bar.id = sstrdup("bar-XXXXXX");
-        /* This works similar to mktemp in that it replaces the last six X with
-         * random letters, but without the restriction that the given buffer
-         * has to contain a valid path name. */
-        char *x = current_bar.id + strlen("bar-");
-        while (*x != '\0') {
-            *(x++) = (rand() % 26) + 'a';
-        }
-
-        /* If no font was explicitly set, we use the i3 font as default */
-        if (!current_bar.font && font_pattern)
-            current_bar.font = sstrdup(font_pattern);
-
-        /* Copy the current (static) structure into a dynamically allocated
-         * one, then cleanup our static one. */
-        Barconfig *bar_config = scalloc(sizeof(Barconfig));
-        memcpy(bar_config, &current_bar, sizeof(Barconfig));
-        TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
-
-        memset(&current_bar, '\0', sizeof(Barconfig));
-    }
-    ;
-
-barlines:
-    /* empty */
-    | barlines barline
-    ;
-
-barline:
-    comment
-    | bar_status_command
-    | bar_i3bar_command
-    | bar_output
-    | bar_tray_output
-    | bar_position
-    | bar_mode
-    | bar_modifier
-    | bar_font
-    | bar_workspace_buttons
-    | bar_verbose
-    | bar_socket_path
-    | bar_colors
-    | bar_color_background
-    | bar_color_statusline
-    | bar_color_focused_workspace
-    | bar_color_active_workspace
-    | bar_color_inactive_workspace
-    | bar_color_urgent_workspace
-    ;
-
-bar_status_command:
-    TOK_BAR_STATUS_COMMAND STR
-    {
-        DLOG("should add status command %s\n", $2);
-        FREE(current_bar.status_command);
-        current_bar.status_command = $2;
-    }
-    ;
-
-bar_i3bar_command:
-    TOK_BAR_I3BAR_COMMAND STR
-    {
-        DLOG("should add i3bar_command %s\n", $2);
-        FREE(current_bar.i3bar_command);
-        current_bar.i3bar_command = $2;
-    }
-    ;
-
-bar_output:
-    TOK_BAR_OUTPUT STR
-    {
-        DLOG("bar output %s\n", $2);
-        int new_outputs = current_bar.num_outputs + 1;
-        current_bar.outputs = srealloc(current_bar.outputs, sizeof(char*) * new_outputs);
-        current_bar.outputs[current_bar.num_outputs] = $2;
-        current_bar.num_outputs = new_outputs;
-    }
-    ;
-
-bar_tray_output:
-    TOK_BAR_TRAY_OUTPUT STR
-    {
-        DLOG("tray %s\n", $2);
-        FREE(current_bar.tray_output);
-        current_bar.tray_output = $2;
-    }
-    ;
-
-bar_position:
-    TOK_BAR_POSITION bar_position_position
-    {
-        DLOG("position %d\n", $2);
-        current_bar.position = $2;
-    }
-    ;
-
-bar_position_position:
-    TOK_BAR_TOP      { $$ = P_TOP; }
-    | TOK_BAR_BOTTOM { $$ = P_BOTTOM; }
-    ;
-
-bar_mode:
-    TOK_BAR_MODE bar_mode_mode
-    {
-        DLOG("mode %d\n", $2);
-        current_bar.mode = $2;
-    }
-    ;
-
-bar_mode_mode:
-    TOK_BAR_HIDE   { $$ = M_HIDE; }
-    | TOK_BAR_DOCK { $$ = M_DOCK; }
-    ;
-
-bar_modifier:
-    TOK_BAR_MODIFIER bar_modifier_modifier
-    {
-        DLOG("modifier %d\n", $2);
-        current_bar.modifier = $2;
-    };
-
-bar_modifier_modifier:
-    TOK_BAR_CONTROL { $$ = M_CONTROL; }
-    | TOK_BAR_SHIFT { $$ = M_SHIFT; }
-    | TOK_BAR_MOD1  { $$ = M_MOD1; }
-    | TOK_BAR_MOD2  { $$ = M_MOD2; }
-    | TOK_BAR_MOD3  { $$ = M_MOD3; }
-    | TOK_BAR_MOD4  { $$ = M_MOD4; }
-    | TOK_BAR_MOD5  { $$ = M_MOD5; }
-    ;
-
-bar_font:
-    TOK_BAR_FONT STR
-    {
-        DLOG("font %s\n", $2);
-        FREE(current_bar.font);
-        current_bar.font = $2;
-    }
-    ;
-
-bar_workspace_buttons:
-    TOK_BAR_WORKSPACE_BUTTONS bool
-    {
-        DLOG("workspace_buttons = %d\n", $2);
-        /* We store this inverted to make the default setting right when
-         * initializing the struct with zero. */
-        current_bar.hide_workspace_buttons = !($2);
-    }
-    ;
-
-bar_verbose:
-    TOK_BAR_VERBOSE bool
-    {
-        DLOG("verbose = %d\n", $2);
-        current_bar.verbose = $2;
-    }
-    ;
-
-bar_socket_path:
-    TOK_BAR_SOCKET_PATH STR
-    {
-        DLOG("socket_path = %s\n", $2);
-        FREE(current_bar.socket_path);
-        current_bar.socket_path = $2;
-    }
-    ;
-
-bar_colors:
-    TOK_BAR_COLORS '{' barlines '}'
-    {
-        /* At the moment, the TOK_BAR_COLORS token is only to make the config
-         * friendlier for humans. We might change this in the future if it gets
-         * more complex. */
-    }
-    ;
-
-bar_color_background:
-    TOK_BAR_COLOR_BACKGROUND HEXCOLOR
-    {
-        DLOG("background = %s\n", $2);
-        current_bar.colors.background = $2;
-    }
-    ;
-
-bar_color_statusline:
-    TOK_BAR_COLOR_STATUSLINE HEXCOLOR
-    {
-        DLOG("statusline = %s\n", $2);
-        current_bar.colors.statusline = $2;
-    }
-    ;
-
-bar_color_focused_workspace:
-    TOK_BAR_COLOR_FOCUSED_WORKSPACE HEXCOLOR HEXCOLOR
-    {
-        /* Old syntax: text / background */
-        DLOG("focused_ws = %s, %s (old)\n", $2, $3);
-        current_bar.colors.focused_workspace_bg = $3;
-        current_bar.colors.focused_workspace_text = $2;
-    }
-    | TOK_BAR_COLOR_FOCUSED_WORKSPACE HEXCOLOR HEXCOLOR HEXCOLOR
-    {
-        /* New syntax: border / background / text */
-        DLOG("focused_ws = %s, %s and %s\n", $2, $3, $4);
-        current_bar.colors.focused_workspace_border = $2;
-        current_bar.colors.focused_workspace_bg = $3;
-        current_bar.colors.focused_workspace_text = $4;
-    }
-    ;
-
-bar_color_active_workspace:
-    TOK_BAR_COLOR_ACTIVE_WORKSPACE HEXCOLOR HEXCOLOR
-    {
-        /* Old syntax: text / background */
-        DLOG("active_ws = %s, %s (old)\n", $2, $3);
-        current_bar.colors.active_workspace_bg = $3;
-        current_bar.colors.active_workspace_text = $2;
-    }
-    | TOK_BAR_COLOR_ACTIVE_WORKSPACE HEXCOLOR HEXCOLOR HEXCOLOR
-    {
-        /* New syntax: border / background / text */
-        DLOG("active_ws = %s, %s and %s\n", $2, $3, $4);
-        current_bar.colors.active_workspace_border = $2;
-        current_bar.colors.active_workspace_bg = $3;
-        current_bar.colors.active_workspace_text = $4;
-    }
-    ;
-
-bar_color_inactive_workspace:
-    TOK_BAR_COLOR_INACTIVE_WORKSPACE HEXCOLOR HEXCOLOR
-    {
-        /* Old syntax: text / background */
-        DLOG("inactive_ws = %s, %s (old)\n", $2, $3);
-        current_bar.colors.inactive_workspace_bg = $3;
-        current_bar.colors.inactive_workspace_text = $2;
-    }
-    | TOK_BAR_COLOR_INACTIVE_WORKSPACE HEXCOLOR HEXCOLOR HEXCOLOR
-    {
-        DLOG("inactive_ws = %s, %s and %s\n", $2, $3, $4);
-        current_bar.colors.inactive_workspace_border = $2;
-        current_bar.colors.inactive_workspace_bg = $3;
-        current_bar.colors.inactive_workspace_text = $4;
-    }
-    ;
-
-bar_color_urgent_workspace:
-    TOK_BAR_COLOR_URGENT_WORKSPACE HEXCOLOR HEXCOLOR
-    {
-        /* Old syntax: text / background */
-        DLOG("urgent_ws = %s, %s (old)\n", $2, $3);
-        current_bar.colors.urgent_workspace_bg = $3;
-        current_bar.colors.urgent_workspace_text = $2;
-    }
-    | TOK_BAR_COLOR_URGENT_WORKSPACE HEXCOLOR HEXCOLOR HEXCOLOR
-    {
-        DLOG("urgent_ws = %s, %s and %s\n", $2, $3, $4);
-        current_bar.colors.urgent_workspace_border = $2;
-        current_bar.colors.urgent_workspace_bg = $3;
-        current_bar.colors.urgent_workspace_text = $4;
-    }
-    ;
-
-floating_maximum_size:
-    TOKFLOATING_MAXIMUM_SIZE NUMBER WORD NUMBER
-    {
-        printf("floating_maximum_width = %d\n", $2);
-        printf("floating_maximum_height = %d\n", $4);
-        config.floating_maximum_width = $2;
-        config.floating_maximum_height = $4;
-    }
-    ;
-
-floating_minimum_size:
-    TOKFLOATING_MINIMUM_SIZE NUMBER WORD NUMBER
-    {
-        printf("floating_minimum_width = %d\n", $2);
-        printf("floating_minimum_height = %d\n", $4);
-        config.floating_minimum_width = $2;
-        config.floating_minimum_height = $4;
-    }
-    ;
-
-floating_modifier:
-    TOKFLOATING_MODIFIER binding_modifiers
-    {
-        DLOG("floating modifier = %d\n", $2);
-        config.floating_modifier = $2;
-    }
-    ;
-
-orientation:
-    TOK_ORIENTATION direction
-    {
-        DLOG("New containers should start with split direction %d\n", $2);
-        config.default_orientation = $2;
-    }
-    ;
-
-direction:
-    TOK_HORIZ       { $$ = HORIZ; }
-    | TOK_VERT      { $$ = VERT; }
-    | TOK_AUTO      { $$ = NO_ORIENTATION; }
-    ;
-
-workspace_layout:
-    TOK_WORKSPACE_LAYOUT layout_mode
-    {
-        DLOG("new containers will be in mode %d\n", $2);
-        config.default_layout = $2;
-
-#if 0
-        /* We also need to change the layout of the already existing
-         * workspaces here. Workspaces may exist at this point because
-         * of the other directives which are modifying workspaces
-         * (setting the preferred screen or name). While the workspace
-         * objects are already created, they have never been used.
-         * Thus, the user very likely awaits the default container mode
-         * to trigger in this case, regardless of where it is inside
-         * his configuration file. */
-        Workspace *ws;
-        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                if (ws->table == NULL)
-                        continue;
-                switch_layout_mode(global_conn,
-                                   ws->table[0][0],
-                                   config.container_mode);
-        }
-#endif
-    }
-    | TOK_WORKSPACE_LAYOUT TOKSTACKLIMIT TOKSTACKLIMIT NUMBER
-    {
-        DLOG("stack-limit %d with val %d\n", $3, $4);
-        config.container_stack_limit = $3;
-        config.container_stack_limit_value = $4;
-
-#if 0
-        /* See the comment above */
-        Workspace *ws;
-        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                if (ws->table == NULL)
-                        continue;
-                Container *con = ws->table[0][0];
-                con->stack_limit = config.container_stack_limit;
-                con->stack_limit_value = config.container_stack_limit_value;
-        }
-#endif
-    }
-    ;
-
-layout_mode:
-    TOK_DEFAULT       { $$ = L_DEFAULT; }
-    | TOK_STACKING    { $$ = L_STACKED; }
-    | TOK_TABBED      { $$ = L_TABBED; }
-    ;
-
-new_window:
-    TOKNEWWINDOW border_style
-    {
-        DLOG("new windows should start with border style %d\n", $2);
-        config.default_border = $2;
-    }
-    ;
-
-new_float:
-    TOKNEWFLOAT border_style
-    {
-       DLOG("new floating windows should start with border style %d\n", $2);
-       config.default_floating_border = $2;
-    }
-    ;
-
-border_style:
-    TOK_NORMAL optional_border_width
-    {
-        /* FIXME: the whole border_style thing actually screws up when new_float is used because it overwrites earlier values :-/ */
-        config.default_border_width = $2;
-        $$ = BS_NORMAL;
-    }
-    | TOK_1PIXEL
-    {
-        config.default_border_width = 1;
-        $$ = BS_PIXEL;
-    }
-    | TOK_NONE
-    {
-        config.default_border_width = 0;
-        $$ = BS_NONE;
-    }
-    | TOK_PIXEL optional_border_width
-    {
-        config.default_border_width = $2;
-        $$ = BS_PIXEL;
-    }
-    ;
-
-bool:
-    NUMBER
-    {
-        $$ = ($1 == 1);
-    }
-    | WORD
-    {
-        DLOG("checking word \"%s\"\n", $1);
-        $$ = (strcasecmp($1, "yes") == 0 ||
-              strcasecmp($1, "true") == 0 ||
-              strcasecmp($1, "on") == 0 ||
-              strcasecmp($1, "enable") == 0 ||
-              strcasecmp($1, "active") == 0);
-    }
-    ;
-
-hide_edge_borders:
-    TOK_HIDE_EDGE_BORDERS edge_hiding_mode
-    {
-        DLOG("hide edge borders = %d\n", $2);
-        config.hide_edge_borders = $2;
-    }
-    ;
-
-edge_hiding_mode:
-    TOK_NONE        { $$ = ADJ_NONE; }
-    | TOK_VERT      { $$ = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE; }
-    | TOK_HORIZ     { $$ = ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE; }
-    | TOK_BOTH      { $$ = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE | ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE; }
-    | bool          { $$ = ($1 ? ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE : ADJ_NONE); }
-    ;
-
-focus_follows_mouse:
-    TOKFOCUSFOLLOWSMOUSE bool
-    {
-        DLOG("focus follows mouse = %d\n", $2);
-        config.disable_focus_follows_mouse = !($2);
-    }
-    ;
-
-force_focus_wrapping:
-    TOK_FORCE_FOCUS_WRAPPING bool
-    {
-        DLOG("force focus wrapping = %d\n", $2);
-        config.force_focus_wrapping = $2;
-    }
-    ;
-
-force_xinerama:
-    TOK_FORCE_XINERAMA bool
-    {
-        DLOG("force xinerama = %d\n", $2);
-        config.force_xinerama = $2;
-    }
-    ;
-
-fake_outputs:
-    TOK_FAKE_OUTPUTS STR
-    {
-        DLOG("fake outputs = %s\n", $2);
-        config.fake_outputs = $2;
-    }
-    ;
-
-workspace_back_and_forth:
-    TOK_WORKSPACE_AUTO_BAF bool
-    {
-        DLOG("automatic workspace back-and-forth = %d\n", $2);
-        config.workspace_auto_back_and_forth = $2;
-    }
-    ;
-
-force_display_urgency_hint:
-    TOK_WORKSPACE_URGENCY_TIMER duration
-    {
-        DLOG("workspace urgency_timer = %f\n", atoi($2) / 1000.0);
-        config.workspace_urgency_timer = atoi($2) / 1000.0;
-    }
-    ;
-
-workspace_bar:
-    TOKWORKSPACEBAR bool
-    {
-        DLOG("workspace bar = %d\n", $2);
-        config.disable_workspace_bar = !($2);
-    }
-    ;
-
-workspace:
-    TOKWORKSPACE qstring_or_number TOKOUTPUT OUTPUT optional_workspace_name
-    {
-        char *ws_name = $2;
-
-        if ($5 != NULL) {
-            ELOG("The old (v3) syntax workspace <number> output <output> <name> is deprecated.\n");
-            ELOG("Please use the new syntax: workspace \"<workspace>\" output <output>\n");
-            ELOG("In your case, the following should work:\n");
-            ELOG("    workspace \"%s\" output %s\n", $5, $4);
-            ws_name = $5;
-            context->has_warnings = true;
-        }
-
-        DLOG("Assigning workspace \"%s\" to output \"%s\"\n", ws_name, $4);
-        /* Check for earlier assignments of the same workspace so that we
-         * don’t have assignments of a single workspace to different
-         * outputs */
-        struct Workspace_Assignment *assignment;
-        bool duplicate = false;
-        TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-            if (strcasecmp(assignment->name, ws_name) == 0) {
-                ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
-                     ws_name);
-                assignment->output = $4;
-                duplicate = true;
-            }
-        }
-        if (!duplicate) {
-            assignment = scalloc(sizeof(struct Workspace_Assignment));
-            assignment->name = ws_name;
-            assignment->output = $4;
-            TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
-        }
-    }
-    | TOKWORKSPACE NUMBER workspace_name
-    {
-        int ws_num = $2;
-        if (ws_num < 1) {
-            DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
-        } else {
-            DLOG("workspace name to: %s\n", $3);
-#if 0
-            if ($<string>3 != NULL) {
-                    workspace_set_name(workspace_get(ws_num - 1), $<string>3);
-                    free($<string>3);
-            }
-#endif
-        }
-    }
-    ;
-
-optional_workspace_name:
-    /* empty */          { $$ = NULL; }
-    | workspace_name     { $$ = $1; }
-    ;
-
-workspace_name:
-    QUOTEDSTRING         { $$ = $1; }
-    | STR                { $$ = $1; }
-    | WORD               { $$ = $1; }
-    ;
-
-assign:
-    TOKASSIGN window_class STR
-    {
-        /* This is the old, deprecated form of assignments. It’s provided for
-         * compatibility in version (4.1, 4.2, 4.3) and will be removed
-         * afterwards. It triggers an i3-nagbar warning starting from 4.1. */
-        ELOG("You are using the old assign syntax (without criteria). "
-             "Please see the User's Guide for the new syntax and fix "
-             "your config file.\n");
-        context->has_warnings = true;
-        printf("assignment of %s to *%s*\n", $2, $3);
-        char *workspace = $3;
-        char *criteria = $2;
-
-        Assignment *assignment = scalloc(sizeof(Assignment));
-        Match *match = &(assignment->match);
-        match_init(match);
-
-        char *separator = NULL;
-        if ((separator = strchr(criteria, '/')) != NULL) {
-            *(separator++) = '\0';
-            char *pattern;
-            sasprintf(&pattern, "(?i)%s", separator);
-            match->title = regex_new(pattern);
-            free(pattern);
-            printf("  title = %s\n", separator);
-        }
-        if (*criteria != '\0') {
-            char *pattern;
-            sasprintf(&pattern, "(?i)%s", criteria);
-            match->class = regex_new(pattern);
-            free(pattern);
-            printf("  class = %s\n", criteria);
-        }
-        free(criteria);
-
-        /* Compatibility with older versions: If the assignment target starts
-         * with ~, we create the equivalent of:
-         *
-         * for_window [class="foo"] floating enable
-         */
-        if (*workspace == '~') {
-            workspace++;
-            if (*workspace == '\0') {
-                /* This assignment was *only* for floating */
-                assignment->type = A_COMMAND;
-                assignment->dest.command = sstrdup("floating enable");
-                TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
-                break;
-            } else {
-                /* Create a new assignment and continue afterwards */
-                Assignment *floating = scalloc(sizeof(Assignment));
-                match_copy(&(floating->match), match);
-                floating->type = A_COMMAND;
-                floating->dest.command = sstrdup("floating enable");
-                TAILQ_INSERT_TAIL(&assignments, floating, assignments);
-            }
-        }
-
-        assignment->type = A_TO_WORKSPACE;
-        assignment->dest.workspace = workspace;
-        TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
-    }
-    | TOKASSIGN match STR
-    {
-        if (match_is_empty(&current_match)) {
-            ELOG("Match is empty, ignoring this assignment\n");
-            break;
-        }
-        printf("new assignment, using above criteria, to workspace %s\n", $3);
-        Assignment *assignment = scalloc(sizeof(Assignment));
-        assignment->match = current_match;
-        assignment->type = A_TO_WORKSPACE;
-        assignment->dest.workspace = $3;
-        TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
-    }
-    ;
-
-window_class:
-    QUOTEDSTRING
-    | STR_NG
-    ;
-
-ipcsocket:
-    TOKIPCSOCKET STR
-    {
-        config.ipc_socket_path = $2;
-    }
-    ;
-
-restart_state:
-    TOKRESTARTSTATE STR
-    {
-        config.restart_state_path = $2;
-    }
-    ;
-
-exec:
-    TOKEXEC optional_no_startup_id STR
-    {
-        struct Autostart *new = smalloc(sizeof(struct Autostart));
-        new->command = $3;
-        new->no_startup_id = $2;
-        TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
-    }
-    ;
-
-exec_always:
-    TOKEXEC_ALWAYS optional_no_startup_id STR
-    {
-        struct Autostart *new = smalloc(sizeof(struct Autostart));
-        new->command = $3;
-        new->no_startup_id = $2;
-        TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always);
-    }
-    ;
-
-optional_border_width:
-    /* empty */ { $$ = 2; } // 2 pixels is the default value for any type of border
-    | NUMBER  { $$ = $1; }
-    ;
-
-optional_no_startup_id:
-    /* empty */ { $$ = false; }
-    | TOK_NO_STARTUP_ID  { $$ = true; }
-    ;
-
-terminal:
-    TOKTERMINAL STR
-    {
-        ELOG("The terminal option is DEPRECATED and has no effect. "
-            "Please remove it from your configuration file.\n");
-    }
-    ;
-
-font:
-    TOKFONT STR
-    {
-        config.font = load_font($2, true);
-        set_font(&config.font);
-        printf("font %s\n", $2);
-        FREE(font_pattern);
-        font_pattern = $2;
-    }
-    ;
-
-single_color:
-    TOKSINGLECOLOR colorpixel
-    {
-        uint32_t *dest = $1;
-        *dest = $2;
-    }
-    ;
-
-color:
-    TOKCOLOR colorpixel colorpixel colorpixel
-    {
-        struct Colortriple *dest = $1;
-
-        dest->border = $2;
-        dest->background = $3;
-        dest->text = $4;
-    }
-    | TOKCOLOR colorpixel colorpixel colorpixel colorpixel
-    {
-        struct Colortriple *dest = $1;
-
-        dest->border = $2;
-        dest->background = $3;
-        dest->text = $4;
-        dest->indicator = $5;
-    }
-    ;
-
-colorpixel:
-    HEXCOLOR
-    {
-        $$ = get_colorpixel($1);
-        free($1);
-    }
-    ;
-
-
-binding_modifiers:
-    /* NULL */                               { $$ = 0; }
-    | binding_modifier
-    | binding_modifiers '+' binding_modifier { $$ = $1 | $3; }
-    | binding_modifiers '+'                  { $$ = $1; }
-    ;
-
-binding_modifier:
-    MODIFIER        { $$ = $1; }
-    | TOKCONTROL    { $$ = BIND_CONTROL; }
-    | TOKSHIFT      { $$ = BIND_SHIFT; }
-    ;
-
-popup_during_fullscreen:
-    TOK_POPUP_DURING_FULLSCREEN popup_setting
-    {
-        DLOG("popup_during_fullscreen setting: %d\n", $2);
-        config.popup_during_fullscreen = $2;
-    }
-    ;
-
-popup_setting:
-    TOK_IGNORE              { $$ = PDF_IGNORE; }
-    | TOK_LEAVE_FULLSCREEN  { $$ = PDF_LEAVE_FULLSCREEN; }
-    ;
index 2ca8387c91e7387ec0835672c4a5866caa28c89e..2404a811785591fd996c8ed37b815e0bdc252a37 100644 (file)
@@ -55,23 +55,15 @@ static bool definitelyGreaterThan(float a, float b, float epsilon) {
 static Output *get_output_from_string(Output *current_output, const char *output_str) {
     Output *output;
 
-    if (strcasecmp(output_str, "left") == 0) {
-        output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
-        if (!output)
-            output = get_output_most(D_RIGHT, current_output);
-    } else if (strcasecmp(output_str, "right") == 0) {
-        output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
-        if (!output)
-            output = get_output_most(D_LEFT, current_output);
-    } else if (strcasecmp(output_str, "up") == 0) {
-        output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
-        if (!output)
-            output = get_output_most(D_DOWN, current_output);
-    } else if (strcasecmp(output_str, "down") == 0) {
-        output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
-        if (!output)
-            output = get_output_most(D_UP, current_output);
-    } else output = get_output_by_name(output_str);
+    if (strcasecmp(output_str, "left") == 0)
+        output = get_output_next_wrap(D_LEFT, current_output);
+    else if (strcasecmp(output_str, "right") == 0)
+        output = get_output_next_wrap(D_RIGHT, current_output);
+    else if (strcasecmp(output_str, "up") == 0)
+        output = get_output_next_wrap(D_UP, current_output);
+    else if (strcasecmp(output_str, "down") == 0)
+        output = get_output_next_wrap(D_DOWN, current_output);
+    else output = get_output_by_name(output_str);
 
     return output;
 }
@@ -596,10 +588,14 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin
         return;
 
     if (strcmp(direction, "up") == 0) {
-        floating_con->rect.y -= px;
+        floating_con->rect.y -= (floating_con->rect.height - old_rect.height);
     } else if (strcmp(direction, "left") == 0) {
-        floating_con->rect.x -= px;
+        floating_con->rect.x -= (floating_con->rect.width - old_rect.width);
     }
+
+    /* If this is a scratchpad window, don't auto center it from now on. */
+    if (floating_con->scratchpad_state == SCRATCHPAD_FRESH)
+        floating_con->scratchpad_state = SCRATCHPAD_CHANGED;
 }
 
 static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) {
@@ -1052,13 +1048,13 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
 
     // TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser
     if (strcasecmp(name, "up") == 0)
-        output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
+        output = get_output_next_wrap(D_UP, current_output);
     else if (strcasecmp(name, "down") == 0)
-        output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
+        output = get_output_next_wrap(D_DOWN, current_output);
     else if (strcasecmp(name, "left") == 0)
-        output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
+        output = get_output_next_wrap(D_LEFT, current_output);
     else if (strcasecmp(name, "right") == 0)
-        output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
+        output = get_output_next_wrap(D_RIGHT, current_output);
     else
         output = get_output_by_name(name);
 
@@ -1410,6 +1406,7 @@ void cmd_focus(I3_CMD) {
         return;
     }
 
+    Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
     int count = 0;
     owindow *current;
     TAILQ_FOREACH(current, &owindows, owindows) {
@@ -1426,6 +1423,16 @@ void cmd_focus(I3_CMD) {
             return;
         }
 
+        /* In case this is a scratchpad window, call scratchpad_show(). */
+        if (ws == __i3_scratch) {
+            scratchpad_show(current->con);
+            count++;
+            /* While for the normal focus case we can change focus multiple
+             * times and only a single window ends up focused, we could show
+             * multiple scratchpad windows. So, rather break here. */
+            break;
+        }
+
         /* If the container is not on the current workspace,
          * workspace_show() will switch to a different workspace and (if
          * enabled) trigger a mouse pointer warp to the currently focused
@@ -1602,8 +1609,8 @@ void cmd_exit(I3_CMD) {
  */
 void cmd_reload(I3_CMD) {
     LOG("reloading\n");
-    kill_configerror_nagbar(false);
-    kill_commanderror_nagbar(false);
+    kill_nagbar(&config_error_nagbar_pid, false);
+    kill_nagbar(&command_error_nagbar_pid, false);
     load_configuration(conn, NULL, true);
     x_set_i3_atoms();
     /* Send an IPC event just in case the ws names have changed */
index ad5025a92057aa10aa16d4916065f42fcf884183..559c1375bc62a5ef13500449561aa01bbba0843e 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -762,24 +762,18 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
             con_focus(old_focus);
     }
 
-    /* 8: when moving to a visible workspace on a different output, we keep the
-     * con focused. Otherwise, we leave the focus on the current workspace as we
-     * don’t want to focus invisible workspaces */
-    if (source_output != dest_output &&
-        workspace_is_visible(workspace) &&
-        !con_is_internal(workspace)) {
-        DLOG("Moved to a different output, focusing target\n");
-    } else {
-        /* Descend focus stack in case focus_next is a workspace which can
-         * occur if we move to the same workspace.  Also show current workspace
-         * to ensure it is focused. */
-        workspace_show(current_ws);
-
-        /* Set focus only if con was on current workspace before moving.
-         * Otherwise we would give focus to some window on different workspace. */
-        if (source_ws == current_ws)
+    /* 8: when moving to another workspace, we leave the focus on the current
+     * workspace. (see also #809) */
+
+    /* Descend focus stack in case focus_next is a workspace which can
+     * occur if we move to the same workspace.  Also show current workspace
+     * to ensure it is focused. */
+    workspace_show(current_ws);
+
+    /* Set focus only if con was on current workspace before moving.
+     * Otherwise we would give focus to some window on different workspace. */
+    if (source_ws == current_ws)
             con_focus(con_descend_focused(focus_next));
-    }
 
     /* If anything within the container is associated with a startup sequence,
      * delete it so child windows won't be created on the old workspace. */
@@ -1181,7 +1175,7 @@ void con_set_border_style(Con *con, int border_style, int border_width) {
     con->current_border_width = border_width;
     bsr = con_border_style_rect(con);
     int deco_height =
-        (con->border_style == BS_NORMAL ? config.font.height + 5 : 0);
+        (con->border_style == BS_NORMAL ? render_deco_height() : 0);
 
     con->rect.x -= bsr.x;
     con->rect.y -= bsr.y;
@@ -1536,6 +1530,45 @@ void con_update_parents_urgency(Con *con) {
     }
 }
 
+/*
+ * Set urgency flag to the container, all the parent containers and the workspace.
+ *
+ */
+void con_set_urgency(Con *con, bool urgent) {
+    if (focused == con) {
+        DLOG("Ignoring urgency flag for current client\n");
+        con->window->urgent.tv_sec = 0;
+        con->window->urgent.tv_usec = 0;
+        return;
+    }
+
+    if (con->urgency_timer == NULL) {
+        con->urgent = urgent;
+    } else
+        DLOG("Discarding urgency WM_HINT because timer is running\n");
+
+    //CLIENT_LOG(con);
+    if (con->window) {
+        if (con->urgent) {
+            gettimeofday(&con->window->urgent, NULL);
+        } else {
+            con->window->urgent.tv_sec = 0;
+            con->window->urgent.tv_usec = 0;
+        }
+    }
+
+    con_update_parents_urgency(con);
+
+    if (con->urgent == urgent)
+        LOG("Urgency flag changed to %d\n", con->urgent);
+
+    Con *ws;
+    /* Set the urgency flag on the workspace, if a workspace could be found
+     * (for dock clients, that is not the case). */
+    if ((ws = con_get_workspace(con)) != NULL)
+        workspace_update_urgent_flag(ws);
+}
+
 /*
  * Create a string representing the subtree under con.
  *
@@ -1573,6 +1606,10 @@ char *con_get_tree_representation(Con *con) {
         buf = sstrdup("T[");
     else if (con->layout == L_STACKED)
         buf = sstrdup("S[");
+    else {
+        ELOG("BUG: Code not updated to account for new layout type\n");
+        assert(false);
+    }
 
     /* 2) append representation of children */
     Con *child;
index e5470a8f62c97553f12720f031695be36f1739e8..595aa435da66805e4baa9ea1f7f2b51ff0eace9c 100644 (file)
@@ -6,8 +6,8 @@
  * i3 - an improved dynamic tiling window manager
  * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * config.c: Configuration file (calling the parser (src/cfgparse.y) with the
- *           correct path, switching key bindings mode).
+ * config.c: Configuration file (calling the parser (src/config_parser.c) with
+ *           the correct path, switching key bindings mode).
  *
  */
 #include "all.h"
index 8b636c026547c4f910ee7ff1afa512b22401b19f..a7fa3500526e356321edde1db5429658e1756a6d 100644 (file)
@@ -392,8 +392,13 @@ CFGFUN(restart_state, const char *path) {
 }
 
 CFGFUN(popup_during_fullscreen, const char *value) {
-    config.popup_during_fullscreen =
-        (strcmp(value, "ignore") == 0 ? PDF_IGNORE : PDF_LEAVE_FULLSCREEN);
+    if (strcmp(value, "ignore") == 0) {
+        config.popup_during_fullscreen = PDF_IGNORE;
+    } else if (strcmp(value, "leave_fullscreen") == 0) {
+        config.popup_during_fullscreen = PDF_LEAVE_FULLSCREEN;
+    } else {
+        config.popup_during_fullscreen = PDF_SMART;
+    }
 }
 
 CFGFUN(color_single, const char *colorclass, const char *color) {
@@ -526,7 +531,10 @@ CFGFUN(bar_tray_output, const char *output) {
 CFGFUN(bar_color_single, const char *colorclass, const char *color) {
     if (strcmp(colorclass, "background") == 0)
         current_bar.colors.background = sstrdup(color);
-    else current_bar.colors.statusline = sstrdup(color);
+    else if (strcmp(colorclass, "separator") == 0)
+        current_bar.colors.separator = sstrdup(color);
+    else
+        current_bar.colors.statusline = sstrdup(color);
 }
 
 CFGFUN(bar_status_command, const char *command) {
index 80d436b1d44a522fd97d38b353fe682e91b09113..1cdc7acba8cd745fd1ba4803dbb41233996d5061 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * config_parser.c: hand-written parser to parse configuration directives.
  *
 #include <unistd.h>
 #include <stdbool.h>
 #include <stdint.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 
 #include "all.h"
 
 #define y(x, ...) yajl_gen_ ## x (command_output.json_gen, ##__VA_ARGS__)
 #define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char*)str, strlen(str))
 
+#ifndef TEST_PARSER
+pid_t config_error_nagbar_pid = -1;
+static struct context *context;
+#endif
+
 /*******************************************************************************
  * The data structures used for parsing. Essentially the current state and a
  * list of tokens for that state.
@@ -649,4 +658,441 @@ int main(int argc, char *argv[]) {
     context.filename = "<stdin>";
     parse_config(argv[1], &context);
 }
+
+#else
+
+/*
+ * Goes through each line of buf (separated by \n) and checks for statements /
+ * commands which only occur in i3 v4 configuration files. If it finds any, it
+ * returns version 4, otherwise it returns version 3.
+ *
+ */
+static int detect_version(char *buf) {
+    char *walk = buf;
+    char *line = buf;
+    while (*walk != '\0') {
+        if (*walk != '\n') {
+            walk++;
+            continue;
+        }
+
+        /* check for some v4-only statements */
+        if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
+            strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
+            strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
+            strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
+            printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
+            return 4;
+        }
+
+        /* if this is a bind statement, we can check the command */
+        if (strncasecmp(line, "bind", strlen("bind")) == 0) {
+            char *bind = strchr(line, ' ');
+            if (bind == NULL)
+                goto next;
+            while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
+                bind++;
+            if (*bind == '\0')
+                goto next;
+            if ((bind = strchr(bind, ' ')) == NULL)
+                goto next;
+            while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
+                bind++;
+            if (*bind == '\0')
+                goto next;
+            if (strncasecmp(bind, "layout", strlen("layout")) == 0 ||
+                strncasecmp(bind, "floating", strlen("floating")) == 0 ||
+                strncasecmp(bind, "workspace", strlen("workspace")) == 0 ||
+                strncasecmp(bind, "focus left", strlen("focus left")) == 0 ||
+                strncasecmp(bind, "focus right", strlen("focus right")) == 0 ||
+                strncasecmp(bind, "focus up", strlen("focus up")) == 0 ||
+                strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
+                strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
+                strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
+                strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 ||
+                strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 ||
+                strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 ||
+                strncasecmp(bind, "bar", strlen("bar")) == 0) {
+                printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
+                return 4;
+            }
+        }
+
+next:
+        /* advance to the next line */
+        walk++;
+        line = walk;
+    }
+
+    return 3;
+}
+
+/*
+ * Calls i3-migrate-config-to-v4 to migrate a configuration file (input
+ * buffer).
+ *
+ * Returns the converted config file or NULL if there was an error (for
+ * example the script could not be found in $PATH or the i3 executable’s
+ * directory).
+ *
+ */
+static char *migrate_config(char *input, off_t size) {
+    int writepipe[2];
+    int readpipe[2];
+
+    if (pipe(writepipe) != 0 ||
+        pipe(readpipe) != 0) {
+        warn("migrate_config: Could not create pipes");
+        return NULL;
+    }
+
+    pid_t pid = fork();
+    if (pid == -1) {
+        warn("Could not fork()");
+        return NULL;
+    }
+
+    /* child */
+    if (pid == 0) {
+        /* close writing end of writepipe, connect reading side to stdin */
+        close(writepipe[1]);
+        dup2(writepipe[0], 0);
+
+        /* close reading end of readpipe, connect writing side to stdout */
+        close(readpipe[0]);
+        dup2(readpipe[1], 1);
+
+        static char *argv[] = {
+            NULL, /* will be replaced by the executable path */
+            NULL
+        };
+        exec_i3_utility("i3-migrate-config-to-v4", argv);
+    }
+
+    /* parent */
+
+    /* close reading end of the writepipe (connected to the script’s stdin) */
+    close(writepipe[0]);
+
+    /* write the whole config file to the pipe, the script will read everything
+     * immediately */
+    int written = 0;
+    int ret;
+    while (written < size) {
+        if ((ret = write(writepipe[1], input + written, size - written)) < 0) {
+            warn("Could not write to pipe");
+            return NULL;
+        }
+        written += ret;
+    }
+    close(writepipe[1]);
+
+    /* close writing end of the readpipe (connected to the script’s stdout) */
+    close(readpipe[1]);
+
+    /* read the script’s output */
+    int conv_size = 65535;
+    char *converted = malloc(conv_size);
+    int read_bytes = 0;
+    do {
+        if (read_bytes == conv_size) {
+            conv_size += 65535;
+            converted = realloc(converted, conv_size);
+        }
+        ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
+        if (ret == -1) {
+            warn("Cannot read from pipe");
+            FREE(converted);
+            return NULL;
+        }
+        read_bytes += ret;
+    } while (ret > 0);
+
+    /* get the returncode */
+    int status;
+    wait(&status);
+    if (!WIFEXITED(status)) {
+        fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
+        return NULL;
+    }
+
+    int returncode = WEXITSTATUS(status);
+    if (returncode != 0) {
+        fprintf(stderr, "Migration process exit code was != 0\n");
+        if (returncode == 2) {
+            fprintf(stderr, "could not start the migration script\n");
+            /* TODO: script was not found. tell the user to fix his system or create a v4 config */
+        } else if (returncode == 1) {
+            fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n");
+            fprintf(stderr, "# i3 config file (v4)\n");
+            /* TODO: nag the user with a message to include a hint for i3 in his config file */
+        }
+        return NULL;
+    }
+
+    return converted;
+}
+
+/*
+ * Checks for duplicate key bindings (the same keycode or keysym is configured
+ * more than once). If a duplicate binding is found, a message is printed to
+ * stderr and the has_errors variable is set to true, which will start
+ * i3-nagbar.
+ *
+ */
+static void check_for_duplicate_bindings(struct context *context) {
+    Binding *bind, *current;
+    TAILQ_FOREACH(current, bindings, bindings) {
+        TAILQ_FOREACH(bind, bindings, bindings) {
+            /* Abort when we reach the current keybinding, only check the
+             * bindings before */
+            if (bind == current)
+                break;
+
+            /* Check if one is using keysym while the other is using bindsym.
+             * If so, skip. */
+            /* XXX: It should be checked at a later place (when translating the
+             * keysym to keycodes) if there are any duplicates */
+            if ((bind->symbol == NULL && current->symbol != NULL) ||
+                (bind->symbol != NULL && current->symbol == NULL))
+                continue;
+
+            /* If bind is NULL, current has to be NULL, too (see above).
+             * If the keycodes differ, it can't be a duplicate. */
+            if (bind->symbol != NULL &&
+                strcasecmp(bind->symbol, current->symbol) != 0)
+                continue;
+
+            /* Check if the keycodes or modifiers are different. If so, they
+             * can't be duplicate */
+            if (bind->keycode != current->keycode ||
+                bind->mods != current->mods ||
+                bind->release != current->release)
+                continue;
+
+            context->has_errors = true;
+            if (current->keycode != 0) {
+                ELOG("Duplicate keybinding in config file:\n  modmask %d with keycode %d, command \"%s\"\n",
+                     current->mods, current->keycode, current->command);
+            } else {
+                ELOG("Duplicate keybinding in config file:\n  modmask %d with keysym %s, command \"%s\"\n",
+                     current->mods, current->symbol, current->command);
+            }
+        }
+    }
+}
+
+/*
+ * Parses the given file by first replacing the variables, then calling
+ * parse_config and possibly launching i3-nagbar.
+ *
+ */
+void parse_file(const char *f) {
+    SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
+    int fd, ret, read_bytes = 0;
+    struct stat stbuf;
+    char *buf;
+    FILE *fstr;
+    char buffer[1026], key[512], value[512];
+
+    if ((fd = open(f, O_RDONLY)) == -1)
+        die("Could not open configuration file: %s\n", strerror(errno));
+
+    if (fstat(fd, &stbuf) == -1)
+        die("Could not fstat file: %s\n", strerror(errno));
+
+    buf = scalloc((stbuf.st_size + 1) * sizeof(char));
+    while (read_bytes < stbuf.st_size) {
+        if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
+            die("Could not read(): %s\n", strerror(errno));
+        read_bytes += ret;
+    }
+
+    if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
+        die("Could not lseek: %s\n", strerror(errno));
+
+    if ((fstr = fdopen(fd, "r")) == NULL)
+        die("Could not fdopen: %s\n", strerror(errno));
+
+    while (!feof(fstr)) {
+        if (fgets(buffer, 1024, fstr) == NULL) {
+            if (feof(fstr))
+                break;
+            die("Could not read configuration file\n");
+        }
+
+        /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
+        if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
+            key[0] == '#' || strlen(key) < 3)
+            continue;
+
+        if (strcasecmp(key, "set") == 0) {
+            if (value[0] != '$') {
+                ELOG("Malformed variable assignment, name has to start with $\n");
+                continue;
+            }
+
+            /* get key/value for this variable */
+            char *v_key = value, *v_value;
+            if (strstr(value, " ") == NULL && strstr(value, "\t") == NULL) {
+                ELOG("Malformed variable assignment, need a value\n");
+                continue;
+            }
+
+            if (!(v_value = strstr(value, " ")))
+                v_value = strstr(value, "\t");
+
+            *(v_value++) = '\0';
+            while (*v_value == '\t' || *v_value == ' ')
+                v_value++;
+
+            struct Variable *new = scalloc(sizeof(struct Variable));
+            new->key = sstrdup(v_key);
+            new->value = sstrdup(v_value);
+            SLIST_INSERT_HEAD(&variables, new, variables);
+            DLOG("Got new variable %s = %s\n", v_key, v_value);
+            continue;
+        }
+    }
+    fclose(fstr);
+
+    /* For every custom variable, see how often it occurs in the file and
+     * how much extra bytes it requires when replaced. */
+    struct Variable *current, *nearest;
+    int extra_bytes = 0;
+    /* We need to copy the buffer because we need to invalidate the
+     * variables (otherwise we will count them twice, which is bad when
+     * 'extra' is negative) */
+    char *bufcopy = sstrdup(buf);
+    SLIST_FOREACH(current, &variables, variables) {
+        int extra = (strlen(current->value) - strlen(current->key));
+        char *next;
+        for (next = bufcopy;
+             next < (bufcopy + stbuf.st_size) &&
+             (next = strcasestr(next, current->key)) != NULL;
+             next += strlen(current->key)) {
+            *next = '_';
+            extra_bytes += extra;
+        }
+    }
+    FREE(bufcopy);
+
+    /* Then, allocate a new buffer and copy the file over to the new one,
+     * but replace occurences of our variables */
+    char *walk = buf, *destwalk;
+    char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
+    destwalk = new;
+    while (walk < (buf + stbuf.st_size)) {
+        /* Find the next variable */
+        SLIST_FOREACH(current, &variables, variables)
+            current->next_match = strcasestr(walk, current->key);
+        nearest = NULL;
+        int distance = stbuf.st_size;
+        SLIST_FOREACH(current, &variables, variables) {
+            if (current->next_match == NULL)
+                continue;
+            if ((current->next_match - walk) < distance) {
+                distance = (current->next_match - walk);
+                nearest = current;
+            }
+        }
+        if (nearest == NULL) {
+            /* If there are no more variables, we just copy the rest */
+            strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
+            destwalk += (buf + stbuf.st_size) - walk;
+            *destwalk = '\0';
+            break;
+        } else {
+            /* Copy until the next variable, then copy its value */
+            strncpy(destwalk, walk, distance);
+            strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
+            walk += distance + strlen(nearest->key);
+            destwalk += distance + strlen(nearest->value);
+        }
+    }
+
+    /* analyze the string to find out whether this is an old config file (3.x)
+     * or a new config file (4.x). If it’s old, we run the converter script. */
+    int version = detect_version(buf);
+    if (version == 3) {
+        /* We need to convert this v3 configuration */
+        char *converted = migrate_config(new, stbuf.st_size);
+        if (converted != NULL) {
+            ELOG("\n");
+            ELOG("****************************************************************\n");
+            ELOG("NOTE: Automatically converted configuration file from v3 to v4.\n");
+            ELOG("\n");
+            ELOG("Please convert your config file to v4. You can use this command:\n");
+            ELOG("    mv %s %s.O\n", f, f);
+            ELOG("    i3-migrate-config-to-v4 %s.O > %s\n", f, f);
+            ELOG("****************************************************************\n");
+            ELOG("\n");
+            free(new);
+            new = converted;
+        } else {
+            printf("\n");
+            printf("**********************************************************************\n");
+            printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4\n");
+            printf("was not correctly installed on your system?\n");
+            printf("**********************************************************************\n");
+            printf("\n");
+        }
+    }
+
+
+    context = scalloc(sizeof(struct context));
+    context->filename = f;
+
+    struct ConfigResult *config_output = parse_config(new, context);
+    yajl_gen_free(config_output->json_gen);
+
+    check_for_duplicate_bindings(context);
+
+    if (context->has_errors || context->has_warnings) {
+        ELOG("FYI: You are using i3 version " I3_VERSION "\n");
+        if (version == 3)
+            ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
+
+        char *editaction,
+             *pageraction;
+        sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", f);
+        sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename);
+        char *argv[] = {
+            NULL, /* will be replaced by the executable path */
+            "-f",
+            config.font.pattern,
+            "-t",
+            (context->has_errors ? "error" : "warning"),
+            "-m",
+            (context->has_errors ?
+             "You have an error in your i3 config file!" :
+             "Your config is outdated. Please fix the warnings to make sure everything works."),
+            "-b",
+            "edit config",
+            editaction,
+            (errorfilename ? "-b" : NULL),
+            (context->has_errors ? "show errors" : "show warnings"),
+            pageraction,
+            NULL
+        };
+
+        start_nagbar(&config_error_nagbar_pid, argv);
+        free(editaction);
+        free(pageraction);
+    }
+
+    FREE(context->line_copy);
+    free(context);
+    free(new);
+    free(buf);
+
+    while (!SLIST_EMPTY(&variables)) {
+        current = SLIST_FIRST(&variables);
+        FREE(current->key);
+        FREE(current->value);
+        SLIST_REMOVE_HEAD(&variables, variables);
+        FREE(current);
+    }
+}
+
 #endif
index ac1a622c6ece3e90bf91b027304576177406652f..0901ae07b4cfea2cb391e5e5cd89899605e2c386 100644 (file)
@@ -101,15 +101,18 @@ void display_running_version(void) {
         err(EXIT_FAILURE, "IPC: write()");
 
     uint32_t reply_length;
+    uint32_t reply_type;
     uint8_t *reply;
     int ret;
-    if ((ret = ipc_recv_message(sockfd, I3_IPC_MESSAGE_TYPE_GET_VERSION,
-                                &reply_length, &reply)) != 0) {
+    if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
         if (ret == -1)
             err(EXIT_FAILURE, "IPC: read()");
         exit(EXIT_FAILURE);
     }
 
+    if (reply_type != I3_IPC_MESSAGE_TYPE_GET_VERSION)
+        errx(EXIT_FAILURE, "Got reply type %d, but expected %d (GET_VERSION)", reply_type, I3_IPC_MESSAGE_TYPE_GET_VERSION);
+
 #if YAJL_MAJOR >= 2
     yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL);
 #else
index 45d4e5fec19bff8578f40cb2cd4d3876fda211e1..9021e1c5c7a215b9a8799ee24607fdc71ba5e23a 100644 (file)
@@ -164,5 +164,5 @@ void ewmh_setup_hints(void) {
     /* I’m not entirely sure if we need to keep _NET_WM_NAME on root. */
     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
 
-    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 16, supported_atoms);
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 19, supported_atoms);
 }
index 761d207de39cfed6d1fed9e726082b828f4283ae..4dd44f574d98699cf896294643543d3761623c5b 100644 (file)
@@ -39,6 +39,35 @@ void floating_check_size(Con *floating_con) {
     const int floating_sane_min_height = 50;
     const int floating_sane_min_width = 75;
     Rect floating_sane_max_dimensions;
+    Con *focused_con = con_descend_focused(floating_con);
+
+    /* obey size increments */
+    if (focused_con->height_increment || focused_con->width_increment) {
+        Rect border_rect = con_border_style_rect(focused_con);
+
+        /* We have to do the opposite calculations that render_con() do
+         * to get the exact size we want. */
+        border_rect.width = -border_rect.width;
+        border_rect.width += 2 * focused_con->border_width;
+        border_rect.height = -border_rect.height;
+        border_rect.height += 2 * focused_con->border_width;
+        if (con_border_style(focused_con) == BS_NORMAL)
+            border_rect.height += render_deco_height();
+
+        if (focused_con->height_increment &&
+            floating_con->rect.height >= focused_con->base_height + border_rect.height) {
+            floating_con->rect.height -= focused_con->base_height + border_rect.height;
+            floating_con->rect.height -= floating_con->rect.height % focused_con->height_increment;
+            floating_con->rect.height += focused_con->base_height + border_rect.height;
+        }
+
+        if (focused_con->width_increment &&
+            floating_con->rect.width >= focused_con->base_width + border_rect.width) {
+            floating_con->rect.width -= focused_con->base_width + border_rect.width;
+            floating_con->rect.width -= floating_con->rect.width % focused_con->width_increment;
+            floating_con->rect.width += focused_con->base_width + border_rect.width;
+        }
+    }
 
     /* Unless user requests otherwise (-1), ensure width/height do not exceed
      * configured maxima or, if unconfigured, limit to combined width of all
@@ -165,7 +194,7 @@ void floating_enable(Con *con, bool automatic) {
     free(name);
 
     /* find the height for the decorations */
-    int deco_height = config.font.height + 5;
+    int deco_height = render_deco_height();
 
     DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height);
     DLOG("Geometry = (%d, %d) with %d x %d\n", con->geometry.x, con->geometry.y, con->geometry.width, con->geometry.height);
@@ -251,7 +280,7 @@ void floating_enable(Con *con, bool automatic) {
     /* 5: Subtract the deco_height in order to make the floating window appear
      * at precisely the position it specified in its original geometry (which
      * is what applications might remember). */
-    deco_height = (con->border_style == BS_NORMAL ? config.font.height + 5 : 0);
+    deco_height = (con->border_style == BS_NORMAL ? render_deco_height() : 0);
     nc->rect.y -= deco_height;
 
     DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height);
@@ -409,6 +438,11 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
 
     /* Drag the window */
     drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
+
+    /* If this is a scratchpad window, don't auto center it from now on. */
+    if (con->scratchpad_state == SCRATCHPAD_FRESH)
+        con->scratchpad_state = SCRATCHPAD_CHANGED;
+
     tree_render();
 }
 
@@ -447,26 +481,27 @@ DRAGGING_CB(resize_window_callback) {
         dest_height = old_rect->height - (new_y - event->root_y);
     else dest_height = old_rect->height + (new_y - event->root_y);
 
-    /* Obey minimum window size */
-    Rect minimum = con_minimum_size(con);
-    dest_width = max(dest_width, minimum.width);
-    dest_height = max(dest_height, minimum.height);
-
     /* User wants to keep proportions, so we may have to adjust our values */
     if (params->proportional) {
         dest_width = max(dest_width, (int) (dest_height * ratio));
         dest_height = max(dest_height, (int) (dest_width / ratio));
     }
 
+    con->rect = (Rect) { dest_x, dest_y, dest_width, dest_height };
+
+    /* Obey window size */
+    floating_check_size(con);
+
     /* If not the lower right corner is grabbed, we must also reposition
      * the client by exactly the amount we resized it */
     if (corner & BORDER_LEFT)
-        dest_x = old_rect->x + (old_rect->width - dest_width);
+        dest_x = old_rect->x + (old_rect->width - con->rect.width);
 
     if (corner & BORDER_TOP)
-        dest_y = old_rect->y + (old_rect->height - dest_height);
+        dest_y = old_rect->y + (old_rect->height - con->rect.height);
 
-    con->rect = (Rect) { dest_x, dest_y, dest_width, dest_height };
+    con->rect.x = dest_x;
+    con->rect.y = dest_y;
 
     /* TODO: don’t re-render the whole tree just because we change
      * coordinates of a floating window */
@@ -507,6 +542,10 @@ void floating_resize_window(Con *con, const bool proportional,
     struct resize_window_callback_params params = { corner, proportional, event };
 
     drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, &params);
+
+    /* If this is a scratchpad window, don't auto center it from now on. */
+    if (con->scratchpad_state == SCRATCHPAD_FRESH)
+        con->scratchpad_state = SCRATCHPAD_CHANGED;
 }
 
 /*
@@ -630,6 +669,11 @@ void floating_reposition(Con *con, Rect newrect) {
     con->rect = newrect;
 
     floating_maybe_reassign_ws(con);
+
+    /* If this is a scratchpad window, don't auto center it from now on. */
+    if (con->scratchpad_state == SCRATCHPAD_FRESH)
+        con->scratchpad_state = SCRATCHPAD_CHANGED;
+
     tree_render();
 }
 
index 1bfcce562b9af13084889cb07be8cd5a138aeb3d..f4782ca97dffece262160c27d956d43ba9ea0add 100644 (file)
@@ -339,7 +339,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
 
     if (fullscreen != con && con_is_floating(con) && con_is_leaf(con)) {
         /* find the height for the decorations */
-        int deco_height = config.font.height + 5;
+        int deco_height = con->deco_rect.height;
         /* we actually need to apply the size/position changes to the *parent*
          * container */
         Rect bsr = con_border_style_rect(con);
@@ -619,10 +619,10 @@ static void handle_client_message(xcb_client_message_event_t *event) {
 
     LOG("ClientMessage for window 0x%08x\n", event->window);
     if (event->type == A__NET_WM_STATE) {
-        if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) {
-            DLOG("atom in clientmessage is %d, fullscreen is %d\n",
-                    event->data.data32[1], A__NET_WM_STATE_FULLSCREEN);
-            DLOG("not about fullscreen atom\n");
+        if (event->format != 32 ||
+            (event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN &&
+             event->data.data32[1] != A__NET_WM_STATE_DEMANDS_ATTENTION)) {
+            DLOG("Unknown atom in clientmessage of type %d\n", event->data.data32[1]);
             return;
         }
 
@@ -632,15 +632,25 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             return;
         }
 
-        /* Check if the fullscreen state should be toggled */
-        if ((con->fullscreen_mode != CF_NONE &&
-             (event->data.data32[0] == _NET_WM_STATE_REMOVE ||
-              event->data.data32[0] == _NET_WM_STATE_TOGGLE)) ||
-            (con->fullscreen_mode == CF_NONE &&
-             (event->data.data32[0] == _NET_WM_STATE_ADD ||
-              event->data.data32[0] == _NET_WM_STATE_TOGGLE))) {
-            DLOG("toggling fullscreen\n");
-            con_toggle_fullscreen(con, CF_OUTPUT);
+        if (event->data.data32[1] == A__NET_WM_STATE_FULLSCREEN) {
+            /* Check if the fullscreen state should be toggled */
+            if ((con->fullscreen_mode != CF_NONE &&
+                 (event->data.data32[0] == _NET_WM_STATE_REMOVE ||
+                  event->data.data32[0] == _NET_WM_STATE_TOGGLE)) ||
+                (con->fullscreen_mode == CF_NONE &&
+                 (event->data.data32[0] == _NET_WM_STATE_ADD ||
+                  event->data.data32[0] == _NET_WM_STATE_TOGGLE))) {
+                DLOG("toggling fullscreen\n");
+                con_toggle_fullscreen(con, CF_OUTPUT);
+            }
+        } else if (event->data.data32[1] == A__NET_WM_STATE_DEMANDS_ATTENTION) {
+            /* Check if the urgent flag must be set or not */
+            if (event->data.data32[0] == _NET_WM_STATE_ADD)
+                con_set_urgency(con, true);
+            else if (event->data.data32[0] == _NET_WM_STATE_REMOVE)
+                con_set_urgency(con, false);
+            else if (event->data.data32[0] == _NET_WM_STATE_TOGGLE)
+                con_set_urgency(con, !con->urgent);
         }
 
         tree_render();
@@ -833,44 +843,12 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
     if (!xcb_icccm_get_wm_hints_from_reply(&hints, reply))
         return false;
 
-    if (!con->urgent && focused == con) {
-        DLOG("Ignoring urgency flag for current client\n");
-        con->window->urgent.tv_sec = 0;
-        con->window->urgent.tv_usec = 0;
-        goto end;
-    }
-
     /* Update the flag on the client directly */
     bool hint_urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
-
-    if (con->urgency_timer == NULL) {
-        con->urgent = hint_urgent;
-    } else
-        DLOG("Discarding urgency WM_HINT because timer is running\n");
-
-    //CLIENT_LOG(con);
-    if (con->window) {
-        if (con->urgent) {
-            gettimeofday(&con->window->urgent, NULL);
-        } else {
-            con->window->urgent.tv_sec = 0;
-            con->window->urgent.tv_usec = 0;
-        }
-    }
-
-    con_update_parents_urgency(con);
-
-    LOG("Urgency flag changed to %d\n", con->urgent);
-
-    Con *ws;
-    /* Set the urgency flag on the workspace, if a workspace could be found
-     * (for dock clients, that is not the case). */
-    if ((ws = con_get_workspace(con)) != NULL)
-        workspace_update_urgent_flag(ws);
+    con_set_urgency(con, hint_urgent);
 
     tree_render();
 
-end:
     if (con->window)
         window_update_hints(con->window, reply);
     else free(reply);
@@ -1094,7 +1072,7 @@ void handle_event(int type, xcb_generic_event_t *event) {
 
         /* Client message are sent to the root window. The only interesting
          * client message for us is _NET_WM_STATE, we honour
-         * _NET_WM_STATE_FULLSCREEN */
+         * _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_DEMANDS_ATTENTION */
         case XCB_CLIENT_MESSAGE:
             handle_client_message((xcb_client_message_event_t*)event);
             break;
index dedf4e27b91831f2f1dd0274473acb3a6ac146c9..36a24c8b23ce0e06de642fcd5798637f9da30ff1 100644 (file)
--- a/src/i3.mk
+++ b/src/i3.mk
@@ -2,7 +2,6 @@ ALL_TARGETS += i3
 INSTALL_TARGETS += install-i3
 CLEAN_TARGETS += clean-i3
 
-i3_SOURCES_GENERATED  = src/cfgparse.tab.c src/cfgparse.yy.c
 i3_SOURCES           := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c))
 i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h)
 i3_HEADERS           := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h))
@@ -37,14 +36,6 @@ src/%.o: src/%.c $(i3_HEADERS_DEP)
        echo "[i3] CC $<"
        $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(PCH_FLAGS) -c -o $@ ${canonical_path}/$<
 
-src/cfgparse.yy.c: src/cfgparse.l src/cfgparse.tab.o $(i3_HEADERS_DEP)
-       echo "[i3] LEX $<"
-       $(FLEX) -i -o $@ ${canonical_path}/$<
-
-src/cfgparse.tab.c: src/cfgparse.y $(i3_HEADERS_DEP)
-       echo "[i3] YACC $<"
-       $(BISON) --debug --verbose -b $(basename $< .y) -d ${canonical_path}/$<
-
 # This target compiles the command parser twice:
 # Once with -DTEST_PARSER, creating a stand-alone executable used for tests,
 # and once as an object file for i3.
@@ -96,4 +87,4 @@ install-i3: i3
 
 clean-i3:
        echo "[i3] Clean"
-       rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 src/*.gcno src/cfgparse.{output,dot,tab.h,y.o} src/cmdparse.*
+       rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 test.config_parser test.commands_parser src/*.gcno src/cfgparse.* src/cmdparse.*
index 17c15b9e91e723a128794e218694b8196e3475e9..cf2535733e46123fe8aa7a0540d6c18b052a4441 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -677,6 +677,7 @@ IPC_HANDLER(get_bar_config) {
         y(map_open);
         YSTR_IF_SET(background);
         YSTR_IF_SET(statusline);
+        YSTR_IF_SET(separator);
         YSTR_IF_SET(focused_workspace_border);
         YSTR_IF_SET(focused_workspace_bg);
         YSTR_IF_SET(focused_workspace_text);
@@ -802,18 +803,16 @@ handler_t handlers[8] = {
  *
  */
 static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
-    char buf[2048];
-    int n = read(w->fd, buf, sizeof(buf));
-
-    /* On error or an empty message, we close the connection */
-    if (n <= 0) {
-#if 0
-        /* FIXME: I get these when closing a client socket,
-         * therefore we just treat them as an error. Is this
-         * correct? */
-        if (errno == EAGAIN || errno == EWOULDBLOCK)
-                return;
-#endif
+    uint32_t message_type;
+    uint32_t message_length;
+    uint8_t *message;
+
+    int ret = ipc_recv_message(w->fd, &message_type, &message_length, &message);
+    /* EOF or other error */
+    if (ret < 0) {
+        /* Was this a spurious read? See ev(3) */
+        if (ret == -1 && errno == EAGAIN)
+            return;
 
         /* If not, there was some kind of error. We don’t bother
          * and close the connection */
@@ -841,51 +840,11 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
         return;
     }
 
-    /* Terminate the message correctly */
-    buf[n] = '\0';
-
-    /* Check if the message starts with the i3 IPC magic code */
-    if (n < strlen(I3_IPC_MAGIC)) {
-        DLOG("IPC: message too short, ignoring\n");
-        return;
-    }
-
-    if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
-        DLOG("IPC: message does not start with the IPC magic\n");
-        return;
-    }
-
-    uint8_t *message = (uint8_t*)buf;
-    while (n > 0) {
-        DLOG("IPC: n = %d\n", n);
-        message += strlen(I3_IPC_MAGIC);
-        n -= strlen(I3_IPC_MAGIC);
-
-        /* The next 32 bit after the magic are the message size */
-        uint32_t message_size;
-        memcpy(&message_size, (uint32_t*)message, sizeof(uint32_t));
-        message += sizeof(uint32_t);
-        n -= sizeof(uint32_t);
-
-        if (message_size > n) {
-            DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
-            return;
-        }
-
-        /* The last 32 bits of the header are the message type */
-        uint32_t message_type;
-        memcpy(&message_type, (uint32_t*)message, sizeof(uint32_t));
-        message += sizeof(uint32_t);
-        n -= sizeof(uint32_t);
-
-        if (message_type >= (sizeof(handlers) / sizeof(handler_t)))
-            DLOG("Unhandled message type: %d\n", message_type);
-        else {
-            handler_t h = handlers[message_type];
-            h(w->fd, message, n, message_size, message_type);
-        }
-        n -= message_size;
-        message += message_size;
+    if (message_type >= (sizeof(handlers) / sizeof(handler_t)))
+        DLOG("Unhandled message type: %d\n", message_type);
+    else {
+        handler_t h = handlers[message_type];
+        h(w->fd, message, 0, message_length, message_type);
     }
 }
 
index 5919e64c7d06ab2baac84f2af36c6b7163dfad17..2f77a2a4da31b56e2bfcb8311ea1f90380e2658a 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * key_press.c: key press handler
  *
@@ -19,169 +19,7 @@ static int current_nesting_level;
 static bool parse_error_key;
 static bool command_failed;
 
-/* XXX: I don’t want to touch too much of the nagbar code at once, but we
- * should refactor this with src/cfgparse.y into a clean generic nagbar
- * interface. It might come in handy in other situations within i3, too. */
-static char *pager_script_path;
-static pid_t nagbar_pid = -1;
-
-/*
- * Handler which will be called when we get a SIGCHLD for the nagbar, meaning
- * it exited (or could not be started, depending on the exit code).
- *
- */
-static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
-    ev_child_stop(EV_A_ watcher);
-
-    if (unlink(pager_script_path) != 0)
-        warn("Could not delete temporary i3-nagbar script %s", pager_script_path);
-
-    if (!WIFEXITED(watcher->rstatus)) {
-        fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
-        return;
-    }
-
-    int exitcode = WEXITSTATUS(watcher->rstatus);
-    printf("i3-nagbar process exited with status %d\n", exitcode);
-    if (exitcode == 2) {
-        fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
-    }
-
-    nagbar_pid = -1;
-}
-
-/* We need ev >= 4 for the following code. Since it is not *that* important (it
- * only makes sure that there are no i3-nagbar instances left behind) we still
- * support old systems with libev 3. */
-#if EV_VERSION_MAJOR >= 4
-/*
- * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
- * SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
- *
- */
-static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
-    if (nagbar_pid != -1) {
-        LOG("Sending SIGKILL (%d) to i3-nagbar with PID %d\n", SIGKILL, nagbar_pid);
-        kill(nagbar_pid, SIGKILL);
-    }
-}
-#endif
-
-/*
- * Writes the given command as a shell script to path.
- * Returns true unless something went wrong.
- *
- */
-static bool write_nagbar_script(const char *path, const char *command) {
-    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR);
-    if (fd == -1) {
-        warn("Could not create temporary script to store the nagbar command");
-        return false;
-    }
-    write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n"));
-    write(fd, command, strlen(command));
-    close(fd);
-    return true;
-}
-
-/*
- * Starts an i3-nagbar process which alerts the user that his configuration
- * file contains one or more errors. Also offers two buttons: One to launch an
- * $EDITOR on the config file and another one to launch a $PAGER on the error
- * logfile.
- *
- */
-static void start_commanderror_nagbar(void) {
-    if (nagbar_pid != -1) {
-        DLOG("i3-nagbar for command error already running, not starting again.\n");
-        return;
-    }
-
-    DLOG("Starting i3-nagbar due to command error\n");
-
-    /* We need to create a custom script containing our actual command
-     * since not every terminal emulator which is contained in
-     * i3-sensible-terminal supports -e with multiple arguments (and not
-     * all of them support -e with one quoted argument either).
-     *
-     * NB: The paths need to be unique, that is, don’t assume users close
-     * their nagbars at any point in time (and they still need to work).
-     * */
-    pager_script_path = get_process_filename("nagbar-cfgerror-pager");
-
-    nagbar_pid = fork();
-    if (nagbar_pid == -1) {
-        warn("Could not fork()");
-        return;
-    }
-
-    /* child */
-    if (nagbar_pid == 0) {
-        char *pager_command;
-        sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename);
-        if (!write_nagbar_script(pager_script_path, pager_command))
-            return;
-
-        char *pageraction;
-        sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path);
-        char *argv[] = {
-            NULL, /* will be replaced by the executable path */
-            "-t",
-            "error",
-            "-m",
-            "The configured command for this shortcut could not be run successfully.",
-            "-b",
-            "show errors",
-            pageraction,
-            NULL
-        };
-        exec_i3_utility("i3-nagbar", argv);
-    }
-
-    /* parent */
-    /* install a child watcher */
-    ev_child *child = smalloc(sizeof(ev_child));
-    ev_child_init(child, &nagbar_exited, nagbar_pid, 0);
-    ev_child_start(main_loop, child);
-
-/* We need ev >= 4 for the following code. Since it is not *that* important (it
- * only makes sure that there are no i3-nagbar instances left behind) we still
- * support old systems with libev 3. */
-#if EV_VERSION_MAJOR >= 4
-    /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
-     * still running) */
-    ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
-    ev_cleanup_init(cleanup, nagbar_cleanup);
-    ev_cleanup_start(main_loop, cleanup);
-#endif
-}
-
-/*
- * Kills the commanderror i3-nagbar process, if any.
- *
- * Called when reloading/restarting, since the user probably fixed his wrong
- * keybindings.
- *
- * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
- * ev is assumed to handle it (reloading).
- *
- */
-void kill_commanderror_nagbar(bool wait_for_it) {
-    if (nagbar_pid == -1)
-        return;
-
-    if (kill(nagbar_pid, SIGTERM) == -1)
-        warn("kill(configerror_nagbar) failed");
-
-    if (!wait_for_it)
-        return;
-
-    /* When restarting, we don’t enter the ev main loop anymore and after the
-     * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
-     * for us and we would end up with a <defunct> process. Therefore we
-     * waitpid() here. */
-    waitpid(nagbar_pid, NULL, 0);
-}
+pid_t command_error_nagbar_pid = -1;
 
 static int json_boolean(void *ctx, int boolval) {
     DLOG("Got bool: %d, parse_error_key %d, nesting_level %d\n", boolval, parse_error_key, current_nesting_level);
@@ -302,8 +140,25 @@ void handle_key_press(xcb_key_press_event_t *event) {
     if (state != yajl_status_ok) {
         ELOG("Could not parse my own reply. That's weird. reply is %.*s\n", (int)length, reply);
     } else {
-        if (command_failed)
-            start_commanderror_nagbar();
+        if (command_failed) {
+            char *pageraction;
+            sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename);
+            char *argv[] = {
+                NULL, /* will be replaced by the executable path */
+                "-f",
+                config.font.pattern,
+                "-t",
+                "error",
+                "-m",
+                "The configured command for this shortcut could not be run successfully.",
+                "-b",
+                "show errors",
+                pageraction,
+                NULL
+            };
+            start_nagbar(&command_error_nagbar_pid, argv);
+            free(pageraction);
+        }
     }
 
     yajl_free(handle);
index cb6767fbb761b8d2bd61a87e8ddfe191c88b1f31..a49241dda9d4ec39737c397caeed73ac830e727b 100644 (file)
@@ -374,8 +374,7 @@ int main(int argc, char *argv[]) {
                     fake_outputs = sstrdup(optarg);
                     break;
                 } else if (strcmp(long_options[option_index].name, "force-old-config-parser-v4.4-only") == 0) {
-                    LOG("FORCING OLD CONFIG PARSER!\n");
-                    force_old_config_parser = true;
+                    ELOG("You are passing --force-old-config-parser-v4.4-only, but that flag was removed by now.\n");
                     break;
                 }
                 /* fall-through */
@@ -461,14 +460,16 @@ int main(int argc, char *argv[]) {
             err(EXIT_FAILURE, "IPC: write()");
 
         uint32_t reply_length;
+        uint32_t reply_type;
         uint8_t *reply;
         int ret;
-        if ((ret = ipc_recv_message(sockfd, I3_IPC_MESSAGE_TYPE_COMMAND,
-                                    &reply_length, &reply)) != 0) {
+        if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
             if (ret == -1)
                 err(EXIT_FAILURE, "IPC: read()");
             return 1;
         }
+        if (reply_type != I3_IPC_MESSAGE_TYPE_COMMAND)
+            errx(EXIT_FAILURE, "IPC: received reply of type %d but expected %d (COMMAND)", reply_type, I3_IPC_MESSAGE_TYPE_COMMAND);
         printf("%.*s\n", reply_length, reply);
         return 0;
     }
@@ -542,17 +543,8 @@ int main(int argc, char *argv[]) {
             config.ipc_socket_path = sstrdup(config.ipc_socket_path);
     }
 
-    uint32_t mask = XCB_CW_EVENT_MASK;
-    uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
-                          XCB_EVENT_MASK_BUTTON_PRESS |
-                          XCB_EVENT_MASK_STRUCTURE_NOTIFY |         /* when the user adds a screen (e.g. video
-                                                                           projector), the root window gets a
-                                                                           ConfigureNotify */
-                          XCB_EVENT_MASK_POINTER_MOTION |
-                          XCB_EVENT_MASK_PROPERTY_CHANGE |
-                          XCB_EVENT_MASK_ENTER_WINDOW };
     xcb_void_cookie_t cookie;
-    cookie = xcb_change_window_attributes_checked(conn, root, mask, values);
+    cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ ROOT_EVENT_MASK });
     check_error(conn, cookie, "Another window manager seems to be running");
 
     xcb_get_geometry_reply_t *greply = xcb_get_geometry_reply(conn, gcookie, NULL);
@@ -603,13 +595,13 @@ int main(int argc, char *argv[]) {
         int i1;
         if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) {
             fprintf(stderr, "XKB not supported by X-server\n");
-            return 1;
+           xkb_supported = false;
         }
         /* end of ugliness */
 
-        if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
-                             XkbMapNotifyMask | XkbStateNotifyMask,
-                             XkbMapNotifyMask | XkbStateNotifyMask)) {
+        if (xkb_supported && !XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
+                                              XkbMapNotifyMask | XkbStateNotifyMask,
+                                              XkbMapNotifyMask | XkbStateNotifyMask)) {
             fprintf(stderr, "Could not set XKB event mask\n");
             return 1;
         }
index 2d102eebae0bbff9a826a02f4e0df4ccb47c48b8..ae3cef0fc2babb68b52533d750c7aef563fb72b3 100644 (file)
@@ -10,6 +10,9 @@
  *
  */
 #include "all.h"
+#include "yajl_utils.h"
+
+#include <yajl/yajl_gen.h>
 
 /*
  * Go through all existing windows (if the window manager is restarted) and manage them
@@ -64,8 +67,41 @@ void restore_geometry(void) {
                                 con->rect.x, con->rect.y);
         }
 
+    /* Strictly speaking, this line doesn’t really belong here, but since we
+     * are syncing, let’s un-register as a window manager first */
+    xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT });
+
     /* Make sure our changes reach the X server, we restart/exit now */
-    xcb_flush(conn);
+    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, "");
 }
 
 /*
@@ -349,10 +385,20 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
             con_toggle_fullscreen(fs, CF_OUTPUT);
         } else if (config.popup_during_fullscreen == PDF_SMART &&
                    fs != NULL &&
-                   fs->window != NULL &&
-                   fs->window->id == cwindow->transient_for) {
-            LOG("This floating window belongs to the fullscreen window (popup_during_fullscreen == smart)\n");
-            con_focus(nc);
+                   fs->window != NULL) {
+            i3Window *transient_win = cwindow;
+            while (transient_win != NULL &&
+                   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);
+                    break;
+                }
+                Con *next_transient = con_by_window_id(transient_win->transient_for);
+                if (next_transient == NULL)
+                    break;
+                transient_win = next_transient->window;
+            }
         }
     }
 
@@ -424,6 +470,9 @@ 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);
+
 geom_out:
     free(geom);
 out:
index fe8d49837adc0dc3c517850dc0dc705ade1f7694..1a1a5c460af0b5c6d1d9e01040785eeaa6434454 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * output.c: Output (monitor) related functions.
  *
@@ -22,6 +22,5 @@ Con *output_get_content(Con *output) {
         if (child->type == CT_CON)
             return child;
 
-    ELOG("output_get_content() called on non-output %p\n", output);
-    assert(false);
+    return NULL;
 }
index 267d6e41bf307628c058653325d94100888c7ad4..10b085cbb9825006a58ad44bbefd3e7bc1c910c6 100644 (file)
@@ -93,15 +93,30 @@ Output *get_output_containing(int x, int y) {
 }
 
 /*
- * Gets the output which is the last one in the given direction, for example
- * the output on the most bottom when direction == D_DOWN, the output most
- * right when direction == D_RIGHT and so on.
+ * Like get_output_next with close_far == CLOSEST_OUTPUT, but wraps.
  *
- * This function always returns a output.
+ * For example if get_output_next(D_DOWN, x, FARTHEST_OUTPUT) = NULL, then
+ * get_output_next_wrap(D_DOWN, x) will return the topmost output.
+ *
+ * This function always returns a output: if no active outputs can be found,
+ * current itself is returned.
  *
  */
-Output *get_output_most(direction_t direction, Output *current) {
-    Output *best = get_output_next(direction, current, FARTHEST_OUTPUT);
+Output *get_output_next_wrap(direction_t direction, Output *current) {
+    Output *best = get_output_next(direction, current, CLOSEST_OUTPUT);
+    /* If no output can be found, wrap */
+    if (!best) {
+        direction_t opposite;
+        if (direction == D_RIGHT)
+            opposite = D_LEFT;
+        else if (direction == D_LEFT)
+            opposite = D_RIGHT;
+        else if (direction == D_DOWN)
+            opposite = D_UP;
+        else
+            opposite = D_DOWN;
+        best = get_output_next(opposite, current, FARTHEST_OUTPUT);
+    }
     if (!best)
         best = current;
     DLOG("current = %s, best = %s\n", current->name, best->name);
@@ -111,6 +126,13 @@ Output *get_output_most(direction_t direction, Output *current) {
 /*
  * Gets the output which is the next one in the given direction.
  *
+ * If close_far == CLOSEST_OUTPUT, then the output next to the current one will
+ * selected. If close_far == FARTHEST_OUTPUT, the output which is the last one
+ * in the given direction will be selected.
+ *
+ * NULL will be returned when no active outputs are present in the direction
+ * specified (note that “current” counts as such an output).
+ *
  */
 Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far) {
     Rect *cur = &(current->rect),
index 1216241b49af184a8e2ef45cdf997768b6f54af0..16bfc55bb1f17c876b2a9582f181059b8a6cd9c7 100644 (file)
  * container (for debugging purposes) */
 static bool show_debug_borders = false;
 
+/*
+ * Returns the height for the decorations
+ */
+int render_deco_height(void) {
+    int deco_height = config.font.height + 4;
+    if (config.font.height & 0x01)
+        ++deco_height;
+    return deco_height;
+}
+
 /*
  * Renders a container with layout L_OUTPUT. In this layout, all CT_DOCKAREAs
  * get the height of their content and the remaining CT_CON gets the rest.
@@ -44,12 +54,19 @@ static void render_l_output(Con *con) {
         }
     }
 
-    assert(content != NULL);
+    if (content == NULL) {
+        DLOG("Skipping this output because it is currently being destroyed.\n");
+        return;
+    }
 
     /* We need to find out if there is a fullscreen con on the current workspace
      * and take the short-cut to render it directly (the user does not want to
      * see the dockareas in that case) */
     Con *ws = con_get_fullscreen_con(content, CF_OUTPUT);
+    if (!ws) {
+        DLOG("Skipping this output because it is currently being destroyed.\n");
+        return;
+    }
     Con *fullscreen = con_get_fullscreen_con(ws, CF_OUTPUT);
     if (fullscreen) {
         fullscreen->rect = con->rect;
@@ -196,12 +213,11 @@ void render_con(Con *con, bool render_fullscreen) {
     }
 
     /* find the height for the decorations */
-    int deco_height = config.font.height + 4;
-    if (config.font.height & 0x01)
-        ++deco_height;
+    int deco_height = render_deco_height();
 
     /* precalculate the sizes to be able to correct rounding errors */
     int sizes[children];
+    memset(sizes, 0, children*sizeof(int));
     if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && children > 0) {
         assert(!TAILQ_EMPTY(&con->nodes_head));
         Con *child;
@@ -244,6 +260,10 @@ void render_con(Con *con, bool render_fullscreen) {
                 continue;
             /* Get the active workspace of that output */
             Con *content = output_get_content(output);
+            if (!content || TAILQ_EMPTY(&(content->focus_head))) {
+                DLOG("Skipping this output because it is currently being destroyed.\n");
+                continue;
+            }
             Con *workspace = TAILQ_FIRST(&(content->focus_head));
             Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
             Con *child;
@@ -251,18 +271,28 @@ void render_con(Con *con, bool render_fullscreen) {
                 /* Don’t render floating windows when there is a fullscreen window
                  * on that workspace. Necessary to make floating fullscreen work
                  * correctly (ticket #564). */
-                if (fullscreen != NULL) {
+                if (fullscreen != NULL && fullscreen->window != NULL) {
                     Con *floating_child = con_descend_focused(child);
+                    Con *transient_con = floating_child;
+                    bool is_transient_for = false;
                     /* Exception to the above rule: smart
                      * popup_during_fullscreen handling (popups belonging to
                      * the fullscreen app will be rendered). */
-                    if (floating_child->window == NULL ||
-                        fullscreen->window == NULL ||
-                        floating_child->window->transient_for != fullscreen->window->id)
+                    while (transient_con != NULL &&
+                           transient_con->window != NULL &&
+                           transient_con->window->transient_for != XCB_NONE) {
+                        if (transient_con->window->transient_for == fullscreen->window->id) {
+                            is_transient_for = true;
+                            break;
+                        }
+                        transient_con = con_by_window_id(transient_con->window->transient_for);
+                    }
+
+                    if (!is_transient_for)
                         continue;
                     else {
                         DLOG("Rendering floating child even though in fullscreen mode: "
-                             "floating->transient_for (0x%08x) == fullscreen->id (0x%08x)\n",
+                             "floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n",
                              floating_child->window->transient_for, fullscreen->window->id);
                     }
                 }
@@ -297,16 +327,23 @@ void render_con(Con *con, bool render_fullscreen) {
             }
 
             /* first we have the decoration, if this is a leaf node */
-            if (con_is_leaf(child) && child->border_style == BS_NORMAL) {
-                /* TODO: make a function for relative coords? */
-                child->deco_rect.x = child->rect.x - con->rect.x;
-                child->deco_rect.y = child->rect.y - con->rect.y;
-
-                child->rect.y += deco_height;
-                child->rect.height -= deco_height;
-
-                child->deco_rect.width = child->rect.width;
-                child->deco_rect.height = deco_height;
+            if (con_is_leaf(child)) {
+                if (child->border_style == BS_NORMAL) {
+                    /* TODO: make a function for relative coords? */
+                    child->deco_rect.x = child->rect.x - con->rect.x;
+                    child->deco_rect.y = child->rect.y - con->rect.y;
+
+                    child->rect.y += deco_height;
+                    child->rect.height -= deco_height;
+
+                    child->deco_rect.width = child->rect.width;
+                    child->deco_rect.height = deco_height;
+                } else {
+                    child->deco_rect.x = 0;
+                    child->deco_rect.y = 0;
+                    child->deco_rect.width = 0;
+                    child->deco_rect.height = 0;
+                }
             }
         }
 
index c60b29eb2a6a5d2167add5b74c0e47e98bf4863b..a4d2950519619bd563f804c36854c666f6cfcbad 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * scratchpad.c: Moving windows to the scratchpad and making them visible again.
  *
@@ -53,7 +53,6 @@ void scratchpad_move(Con *con) {
 
     /* 2: Send the window to the __i3_scratch workspace, mainting its
      * coordinates and not warping the pointer. */
-    Con *focus_next = con_next_focused(con);
     con_move_to_workspace(con, __i3_scratch, true, true);
 
     /* 3: If this is the first time this window is used as a scratchpad, we set
@@ -63,11 +62,6 @@ void scratchpad_move(Con *con) {
         DLOG("This window was never used as a scratchpad before.\n");
         con->scratchpad_state = SCRATCHPAD_FRESH;
     }
-
-    /* 4: Fix focus. Normally, when moving a window to a different output, the
-     * destination output gets focused. In this case, we don’t want that. */
-    if (con_get_workspace(focus_next) == con_get_workspace(focused))
-        con_focus(focus_next);
 }
 
 /*
@@ -94,6 +88,41 @@ void scratchpad_show(Con *con) {
         con_toggle_fullscreen(focused, CF_OUTPUT);
     }
 
+    /* If this was 'scratchpad show' without criteria, we check if there is a
+     * unfocused scratchpad on the current workspace and focus it */
+    Con *walk_con;
+    Con *focused_ws = con_get_workspace(focused);
+    TAILQ_FOREACH(walk_con, &(focused_ws->floating_head), floating_windows) {
+        if ((floating = con_inside_floating(walk_con)) &&
+            floating->scratchpad_state != SCRATCHPAD_NONE &&
+            floating != con_inside_floating(focused)) {
+                DLOG("Found an unfocused scratchpad window on this workspace\n");
+                DLOG("Focusing it: %p\n", walk_con);
+                /* use con_descend_tiling_focused to get the last focused
+                 * window inside this scratch container in order to
+                 * keep the focus the same within this container */
+                con_focus(con_descend_tiling_focused(walk_con));
+                return;
+            }
+    }
+
+    /* If this was 'scratchpad show' without criteria, we check if there is a
+     * visible scratchpad window on another workspace. In this case we move it
+     * to the current workspace. */
+    focused_ws = con_get_workspace(focused);
+    TAILQ_FOREACH(walk_con, &all_cons, all_cons) {
+        Con *walk_ws = con_get_workspace(walk_con);
+        if (walk_ws &&
+            !con_is_internal(walk_ws) && focused_ws != walk_ws &&
+            (floating = con_inside_floating(walk_con)) &&
+            floating->scratchpad_state != SCRATCHPAD_NONE) {
+            DLOG("Found a visible scratchpad window on another workspace,\n");
+            DLOG("moving it to this workspace: con = %p\n", walk_con);
+            con_move_to_workspace(walk_con, focused_ws, true, false);
+            return;
+        }
+    }
+
     /* If this was 'scratchpad show' without criteria, we check if the
      * currently focused window is a scratchpad window and should be hidden
      * again. */
@@ -144,11 +173,11 @@ void scratchpad_show(Con *con) {
         Con *output = con_get_output(con);
         con->rect.width = output->rect.width * 0.5;
         con->rect.height = output->rect.height * 0.75;
+        floating_check_size(con);
         con->rect.x = output->rect.x +
                       ((output->rect.width / 2.0) - (con->rect.width / 2.0));
         con->rect.y = output->rect.y +
                       ((output->rect.height / 2.0) - (con->rect.height / 2.0));
-        con->scratchpad_state = SCRATCHPAD_CHANGED;
     }
 
     /* Activate active workspace if window is from another workspace to ensure
index 7a5fb9f010081c7a24f28fbac1673b0471a4583b..32bec965d0b5236c0e3338016b8854fddb8a5fb2 100644 (file)
@@ -200,6 +200,13 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
         was_mapped = _is_con_mapped(con);
     }
 
+    /* remove the urgency hint of the workspace (if set) */
+    if (con->urgent) {
+        con->urgent = false;
+        con_update_parents_urgency(con);
+        workspace_update_urgent_flag(con_get_workspace(con));
+    }
+
     /* Get the container which is next focused */
     Con *next = con_next_focused(con);
     DLOG("next = %p, focused = %p\n", next, focused);
@@ -251,15 +258,28 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
         free(con->window);
     }
 
-    /* kill the X11 part of this container */
-    x_con_kill(con);
+    Con *ws = con_get_workspace(con);
+
+    /* Figure out which container to focus next before detaching 'con'. */
+    if (con_is_floating(con)) {
+        if (con == focused) {
+            DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws);
+            next = con_next_focused(parent);
+
+            dont_kill_parent = true;
+            DLOG("Alright, focusing %p\n", next);
+        } else {
+            next = NULL;
+        }
+    }
 
+    /* Detach the container so that it will not be rendered anymore. */
     con_detach(con);
 
     /* disable urgency timer, if needed */
     if (con->urgency_timer != NULL) {
         DLOG("Removing urgency timer of con %p\n", con);
-        workspace_update_urgent_flag(con_get_workspace(con));
+        workspace_update_urgent_flag(ws);
         ev_timer_stop(main_loop, con->urgency_timer);
         FREE(con->urgency_timer);
     }
@@ -270,21 +290,24 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
         con_fix_percent(parent);
     }
 
+    /* Render the tree so that the surrounding containers take up the space
+     * which 'con' does no longer occupy. If we don’t render here, there will
+     * be a gap in our containers and that could trigger an EnterNotify for an
+     * underlying container, see ticket #660.
+     *
+     * Rendering has to be avoided when dont_kill_parent is set (when
+     * tree_close calls itself recursively) because the tree is in a
+     * non-renderable state during that time. */
+    if (!dont_kill_parent)
+        tree_render();
+
+    /* kill the X11 part of this container */
+    x_con_kill(con);
+
     if (con_is_floating(con)) {
-        Con *ws = con_get_workspace(con);
         DLOG("Container was floating, killing floating container\n");
         tree_close(parent, DONT_KILL_WINDOW, false, (con == focused));
         DLOG("parent container killed\n");
-        if (con == focused) {
-            DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws);
-            /* go down the focus stack as far as possible */
-            next = con_descend_focused(ws);
-
-            dont_kill_parent = true;
-            DLOG("Alright, focusing %p\n", next);
-        } else {
-            next = NULL;
-        }
     }
 
     free(con->name);
@@ -350,17 +373,23 @@ void tree_close_con(kill_window_t kill_window) {
  *
  */
 void tree_split(Con *con, orientation_t orientation) {
-    /* for a workspace, we just need to change orientation */
-    if (con->type == CT_WORKSPACE) {
-        DLOG("Workspace, simply changing orientation to %d\n", orientation);
-        con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
-        return;
-    }
-    else if (con->type == CT_FLOATING_CON) {
+    if (con->type == CT_FLOATING_CON) {
         DLOG("Floating containers can't be split.\n");
         return;
     }
 
+    if (con->type == CT_WORKSPACE) {
+        if (con_num_children(con) < 2) {
+            DLOG("Just changing orientation of workspace\n");
+            con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
+            return;
+        } else {
+            /* if there is more than one container on the workspace
+             * move them into a new container and handle this instead */
+            con = workspace_encapsulate(con);
+        }
+    }
+
     Con *parent = con->parent;
 
     /* Force re-rendering to make the indicator border visible. */
index e623ce81b822e0121d2641665145b2da2402f05d..e2df3ca9c0c9456f606d237bd90ce18d7a744793 100644 (file)
@@ -183,44 +183,6 @@ static char **append_argument(char **original, char *argument) {
     return result;
 }
 
-/*
- * Returns the name of a temporary file with the specified prefix.
- *
- */
-char *get_process_filename(const char *prefix) {
-    /* dir stores the directory path for this and all subsequent calls so that
-     * we only create a temporary directory once per i3 instance. */
-    static char *dir = NULL;
-    if (dir == NULL) {
-        /* Check if XDG_RUNTIME_DIR is set. If so, we use XDG_RUNTIME_DIR/i3 */
-        if ((dir = getenv("XDG_RUNTIME_DIR"))) {
-            char *tmp;
-            sasprintf(&tmp, "%s/i3", dir);
-            dir = tmp;
-            if (!path_exists(dir)) {
-                if (mkdir(dir, 0700) == -1) {
-                    perror("mkdir()");
-                    return NULL;
-                }
-            }
-        } else {
-            /* If not, we create a (secure) temp directory using the template
-             * /tmp/i3-<user>.XXXXXX */
-            struct passwd *pw = getpwuid(getuid());
-            const char *username = pw ? pw->pw_name : "unknown";
-            sasprintf(&dir, "/tmp/i3-%s.XXXXXX", username);
-            /* mkdtemp modifies dir */
-            if (mkdtemp(dir) == NULL) {
-                perror("mkdtemp()");
-                return NULL;
-            }
-        }
-    }
-    char *filename;
-    sasprintf(&filename, "%s/%s.%d", dir, prefix, getpid());
-    return filename;
-}
-
 #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
 #define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
 
@@ -304,8 +266,8 @@ char *store_restart_layout(void) {
 void i3_restart(bool forget_layout) {
     char *restart_filename = forget_layout ? NULL : store_restart_layout();
 
-    kill_configerror_nagbar(true);
-    kill_commanderror_nagbar(true);
+    kill_nagbar(&config_error_nagbar_pid, true);
+    kill_nagbar(&command_error_nagbar_pid, true);
 
     restore_geometry();
 
@@ -382,3 +344,103 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) {
 }
 
 #endif
+
+/*
+ * Handler which will be called when we get a SIGCHLD for the nagbar, meaning
+ * it exited (or could not be started, depending on the exit code).
+ *
+ */
+static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
+    ev_child_stop(EV_A_ watcher);
+
+    if (!WIFEXITED(watcher->rstatus)) {
+        ELOG("ERROR: i3-nagbar did not exit normally.\n");
+        return;
+    }
+
+    int exitcode = WEXITSTATUS(watcher->rstatus);
+    DLOG("i3-nagbar process exited with status %d\n", exitcode);
+    if (exitcode == 2) {
+        ELOG("ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
+    }
+
+    *((pid_t*)watcher->data) = -1;
+}
+
+/*
+ * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
+ * SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
+ *
+ */
+static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
+    pid_t *nagbar_pid = (pid_t*)watcher->data;
+    if (*nagbar_pid != -1) {
+        LOG("Sending SIGKILL (%d) to i3-nagbar with PID %d\n", SIGKILL, *nagbar_pid);
+        kill(*nagbar_pid, SIGKILL);
+    }
+}
+
+/*
+ * Starts an i3-nagbar instance with the given parameters. Takes care of
+ * handling SIGCHLD and killing i3-nagbar when i3 exits.
+ *
+ * The resulting PID will be stored in *nagbar_pid and can be used with
+ * kill_nagbar() to kill the bar later on.
+ *
+ */
+void start_nagbar(pid_t *nagbar_pid, char *argv[]) {
+    if (*nagbar_pid != -1) {
+        DLOG("i3-nagbar already running (PID %d), not starting again.\n", *nagbar_pid);
+        return;
+    }
+
+    *nagbar_pid = fork();
+    if (*nagbar_pid == -1) {
+        warn("Could not fork()");
+        return;
+    }
+
+    /* child */
+    if (*nagbar_pid == 0)
+        exec_i3_utility("i3-nagbar", argv);
+
+    DLOG("Starting i3-nagbar with PID %d\n", *nagbar_pid);
+
+    /* parent */
+    /* install a child watcher */
+    ev_child *child = smalloc(sizeof(ev_child));
+    ev_child_init(child, &nagbar_exited, *nagbar_pid, 0);
+    child->data = nagbar_pid;
+    ev_child_start(main_loop, child);
+
+    /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
+     * still running) */
+    ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
+    ev_cleanup_init(cleanup, nagbar_cleanup);
+    cleanup->data = nagbar_pid;
+    ev_cleanup_start(main_loop, cleanup);
+}
+
+/*
+ * Kills the i3-nagbar process, if *nagbar_pid != -1.
+ *
+ * If wait_for_it is set (restarting i3), this function will waitpid(),
+ * otherwise, ev is assumed to handle it (reloading).
+ *
+ */
+void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it) {
+    if (*nagbar_pid == -1)
+        return;
+
+    if (kill(*nagbar_pid, SIGTERM) == -1)
+        warn("kill(configerror_nagbar) failed");
+
+    if (!wait_for_it)
+        return;
+
+    /* When restarting, we don’t enter the ev main loop anymore and after the
+     * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
+     * for us and we would end up with a <defunct> process. Therefore we
+     * waitpid() here. */
+    waitpid(*nagbar_pid, NULL, 0);
+}
index 4ccf421c09f1b429e34152b175e395558b77269e..5a0913bfcc77852c1084fe2f2908097000714b8c 100644 (file)
@@ -397,7 +397,7 @@ static void _workspace_show(Con *workspace) {
      * the corresponding workspace is cleaned up.
      * NOTE: Internal cons such as __i3_scratch (when a scratchpad window is
      * focused) are skipped, see bug #868. */
-    if (current && !(current->name[0] == '_' && current->name[1] == '_')) {
+    if (current && !con_is_internal(current)) {
         FREE(previous_workspace_name);
         if (current) {
             previous_workspace_name = sstrdup(current->name);
diff --git a/src/x.c b/src/x.c
index 8f0ae37d538984efcf59dd5395d7f94c016c2ac2..fcb63c35bdef19765540800ecb610011ced1941a 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -465,7 +465,7 @@ void x_draw_decoration(Con *con) {
     xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height };
     xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect);
 
-    /* 5: draw two unconnected lines in border color */
+    /* 5: draw two unconnected horizontal lines in border color */
     xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
     Rect *dr = &(con->deco_rect);
     int deco_diff_l = 2;
@@ -539,19 +539,21 @@ after_title:
      * the right border again after rendering the text (and the unconnected
      * lines in border color). */
 
-    /* Draw a separator line after every tab (except the last one), so that
-     * tabs can be easily distinguished. */
-    if (parent->layout == L_TABBED && TAILQ_NEXT(con, nodes) != NULL) {
+    /* Draw a 1px separator line before and after every tab, so that tabs can
+     * be easily distinguished. */
+    if (parent->layout == L_TABBED) {
         xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
     } else {
         xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background });
     }
-    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, parent->pixmap, parent->pm_gc, 4,
+    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, parent->pixmap, parent->pm_gc, 6,
                   (xcb_point_t[]){
+                      { dr->x + dr->width, dr->y },
+                      { dr->x + dr->width, dr->y + dr->height },
                       { dr->x + dr->width - 1, dr->y },
                       { dr->x + dr->width - 1, dr->y + dr->height },
-                      { dr->x + dr->width - 2, dr->y },
-                      { dr->x + dr->width - 2, dr->y + dr->height }
+                      { dr->x, dr->y + dr->height },
+                      { dr->x, dr->y },
                   });
 
     xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
@@ -918,8 +920,12 @@ void x_push_changes(Con *con) {
 
             Output *current = get_output_containing(pointerreply->root_x, pointerreply->root_y);
             Output *target = get_output_containing(mid_x, mid_y);
-            if (current != target)
+            if (current != target) {
+                /* Ignore MotionNotify events generated by warping */
+                xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT });
                 xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
+                xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ ROOT_EVENT_MASK });
+            }
         }
         warp_to = NULL;
     }
@@ -955,7 +961,7 @@ void x_push_changes(Con *con) {
             }
 
             if (set_focus) {
-                DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name);
+                DLOG("Updating focus (focused: %p / %s) to X11 window 0x%08x\n", focused, focused->name, to_focus);
                 /* We remove XCB_EVENT_MASK_FOCUS_CHANGE from the event mask to get
                  * no focus change events for our own focus changes. We only want
                  * these generated by the clients. */
diff --git a/testcases/.gitignore b/testcases/.gitignore
new file mode 100644 (file)
index 0000000..c11c556
--- /dev/null
@@ -0,0 +1,10 @@
+testsuite-*
+latest
+Makefile
+Makefile.old
+.last_run_timings.json
+_Inline
+inc
+META.yml
+i3-cfg-for-*
+-
index b812180270aaa75866fa7ca3cdb930218cde0cc8..560dd4c3de0bc21f6c4126b7f152ef4b424d89fb 100755 (executable)
@@ -1,10 +1,11 @@
 #!/usr/bin/env perl
 # vim:ts=4:sw=4:expandtab
-# © 2010-2011 Michael Stapelberg and contributors
+# © 2010-2012 Michael Stapelberg and contributors
 package complete_run;
 use strict;
 use warnings;
 use v5.10;
+use utf8;
 # the following are modules which ship with Perl (>= 5.10):
 use Pod::Usage;
 use Cwd qw(abs_path);
@@ -29,6 +30,9 @@ use AnyEvent::I3 qw(:all);
 use X11::XCB::Connection;
 use JSON::XS; # AnyEvent::I3 depends on it, too.
 
+binmode STDOUT, ':utf8';
+binmode STDERR, ':utf8';
+
 # Close superfluous file descriptors which were passed by running in a VIM
 # subshell or situations like that.
 AnyEvent::Util::close_all_fds_except(0, 1, 2);
@@ -78,7 +82,7 @@ my @binaries = qw(
                );
 
 foreach my $binary (@binaries) {
-    die "$binary executable not found" unless -e $binary;
+    die "$binary executable not found, did you run “make”?" unless -e $binary;
     die "$binary is not an executable" unless -x $binary;
 }
 
index ea6ae97d52d88d33f6b8b7462817e91dc0eb3d51..228caaa620b3eb7f05edb8573355bd53907c4ef2 100644 (file)
@@ -53,6 +53,10 @@ sub activate_i3 {
         $ENV{LISTEN_FDS} = 1;
         delete $ENV{DESKTOP_STARTUP_ID};
         delete $ENV{I3SOCK};
+        # $SHELL could be set to fish, which will horribly break running shell
+        # commands via i3’s exec feature. This happened e.g. when having
+        # “set-option -g default-shell "/usr/bin/fish"” in ~/.tmux.conf
+        delete $ENV{SHELL};
         unless ($args{dont_create_temp_dir}) {
             $ENV{XDG_RUNTIME_DIR} = '/tmp/i3-testsuite/';
             mkdir $ENV{XDG_RUNTIME_DIR};
index 12f81ea162106189ae52b24fdb81ed66b9890c67..d6d71b23b7f49be099d60b2249c2ee53da8779af 100644 (file)
@@ -529,10 +529,19 @@ sub get_ws_content {
 
 Returns the container ID of the currently focused container on C<$workspace>.
 
+Note that the container ID is B<not> the X11 window ID, so comparing the result
+of C<get_focused> with a window's C<< ->{id} >> property does B<not> work.
+
   my $ws = fresh_workspace;
   my $first_window = open_window;
+  my $first_id = get_focused();
+
   my $second_window = open_window;
-  is(get_focused($ws), $second_window, 'second window focused');
+  my $second_id = get_focused();
+
+  cmd 'focus left';
+
+  is(get_focused($ws), $first_id, 'second window focused');
 
 =cut
 sub get_focused {
index 2849301e9b2ccb01007e8254a04fb2a9e878922c..c2546158267fa2b5a49bf75579b936fea7e9dd7a 100755 (executable)
@@ -79,6 +79,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 fake-outputs 1024x768+0+0,1024x768+1024+0
 EOT
 
+my $pid = launch_with_config($config);
 
 
 exit_gracefully($pid);
index ff44e0ea0bc263ed52d29b0a789212c97ae1809f..02f98af5829dd64d568dd2ae9ed728ce6adad3c2 100644 (file)
 use i3test i3_autostart => 0;
 use List::Util qw(first);
 
+my $_NET_WM_STATE_REMOVE = 0;
+my $_NET_WM_STATE_ADD = 1;
+my $_NET_WM_STATE_TOGGLE = 2;
+
+sub set_urgency {
+    my ($win, $urgent_flag, $type) = @_;
+    if ($type == 1) {
+        $win->add_hint('urgency') if ($urgent_flag);
+        $win->delete_hint('urgency') if (!$urgent_flag);
+    } elsif ($type == 2) {
+        my $msg = pack "CCSLLLLLL",
+            X11::XCB::CLIENT_MESSAGE, # response_type
+            32, # format
+            0, # sequence
+            $win->id, # window
+            $x->atom(name => '_NET_WM_STATE')->id, # message type
+            ($urgent_flag ? $_NET_WM_STATE_ADD : $_NET_WM_STATE_REMOVE), # data32[0]
+            $x->atom(name => '_NET_WM_STATE_DEMANDS_ATTENTION')->id, # data32[1]
+            0, # data32[2]
+            0, # data32[3]
+            0; # data32[4]
+
+        $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+    }
+}
+
 my $config = <<EOT;
 # i3 config file (v4)
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 
 force_display_urgency_hint 0ms
 EOT
-my $pid = launch_with_config($config);
 
-my $tmp = fresh_workspace;
+my $type;
+for ($type = 1; $type <= 2; $type++) {
+    my $pid = launch_with_config($config);
+    my $tmp = fresh_workspace;
 
 #####################################################################
 # Create two windows and put them in stacking mode
 #####################################################################
 
-cmd 'split v';
+    cmd 'split v';
 
-my $top = open_window;
-my $bottom = open_window;
+    my $top = open_window;
+    my $bottom = open_window;
 
-my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
-is(@urgent, 0, 'no window got the urgent flag');
+    my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
+    is(@urgent, 0, 'no window got the urgent flag');
 
 # cmd 'layout stacking';
 
 #####################################################################
 # Add the urgency hint, switch to a different workspace and back again
 #####################################################################
-$top->add_hint('urgency');
-sync_with_i3;
+    set_urgency($top, 1, $type);
+    sync_with_i3;
 
-my @content = @{get_ws_content($tmp)};
-@urgent = grep { $_->{urgent} } @content;
-my $top_info = first { $_->{window} == $top->id } @content;
-my $bottom_info = first { $_->{window} == $bottom->id } @content;
+    my @content = @{get_ws_content($tmp)};
+    @urgent = grep { $_->{urgent} } @content;
+    my $top_info = first { $_->{window} == $top->id } @content;
+    my $bottom_info = first { $_->{window} == $bottom->id } @content;
 
-ok($top_info->{urgent}, 'top window is marked urgent');
-ok(!$bottom_info->{urgent}, 'bottom window is not marked urgent');
-is(@urgent, 1, 'exactly one window got the urgent flag');
+    ok($top_info->{urgent}, 'top window is marked urgent');
+    ok(!$bottom_info->{urgent}, 'bottom window is not marked urgent');
+    is(@urgent, 1, 'exactly one window got the urgent flag');
 
-cmd '[id="' . $top->id . '"] focus';
+    cmd '[id="' . $top->id . '"] focus';
 
-@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
-is(@urgent, 0, 'no window got the urgent flag after focusing');
+    @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
+    is(@urgent, 0, 'no window got the urgent flag after focusing');
 
-$top->add_hint('urgency');
-sync_with_i3;
+    set_urgency($top, 1, $type);
+    sync_with_i3;
 
-@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
-is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint');
+    @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
+    is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint');
 
 #####################################################################
 # Check if the workspace urgency hint gets set/cleared correctly
 #####################################################################
 
-my $ws = get_ws($tmp);
-ok(!$ws->{urgent}, 'urgent flag not set on workspace');
+    my $ws = get_ws($tmp);
+    ok(!$ws->{urgent}, 'urgent flag not set on workspace');
 
-my $otmp = fresh_workspace;
+    my $otmp = fresh_workspace;
 
-$top->add_hint('urgency');
-sync_with_i3;
+    set_urgency($top, 1, $type);
+    sync_with_i3;
 
-$ws = get_ws($tmp);
-ok($ws->{urgent}, 'urgent flag set on workspace');
+    $ws = get_ws($tmp);
+    ok($ws->{urgent}, 'urgent flag set on workspace');
 
-cmd "workspace $tmp";
+    cmd "workspace $tmp";
 
-$ws = get_ws($tmp);
-ok(!$ws->{urgent}, 'urgent flag not set on workspace after switching');
+    $ws = get_ws($tmp);
+    ok(!$ws->{urgent}, 'urgent flag not set on workspace after switching');
 
 ################################################################################
 # Use the 'urgent' criteria to switch to windows which have the urgency hint set.
 ################################################################################
 
 # Go to a new workspace, open a different window, verify focus is on it.
-$otmp = fresh_workspace;
-my $different_window = open_window;
-is($x->input_focus, $different_window->id, 'new window focused');
+    $otmp = fresh_workspace;
+    my $different_window = open_window;
+    is($x->input_focus, $different_window->id, 'new window focused');
 
 # Add the urgency hint on the other window.
-$top->add_hint('urgency');
-sync_with_i3;
+    set_urgency($top, 1, $type);
+    sync_with_i3;
 
 # Now try to switch to that window and see if focus changes.
-cmd '[urgent=latest] focus';
-isnt($x->input_focus, $different_window->id, 'window no longer focused');
-is($x->input_focus, $top->id, 'urgent window focused');
+    cmd '[urgent=latest] focus';
+    isnt($x->input_focus, $different_window->id, 'window no longer focused');
+    is($x->input_focus, $top->id, 'urgent window focused');
 
 ################################################################################
 # Same thing, but with multiple windows and using the 'urgency=latest' criteria
 # (verify that it works in the correct order).
 ################################################################################
 
-cmd "workspace $otmp";
-is($x->input_focus, $different_window->id, 'new window focused again');
+    cmd "workspace $otmp";
+    is($x->input_focus, $different_window->id, 'new window focused again');
 
-$top->add_hint('urgency');
-sync_with_i3;
+    set_urgency($top, 1, $type);
+    sync_with_i3;
 
-$bottom->add_hint('urgency');
-sync_with_i3;
+    set_urgency($bottom, 1, $type);
+    sync_with_i3;
 
-cmd '[urgent=latest] focus';
-is($x->input_focus, $bottom->id, 'latest urgent window focused');
-$bottom->delete_hint('urgency');
-sync_with_i3;
+    cmd '[urgent=latest] focus';
+    is($x->input_focus, $bottom->id, 'latest urgent window focused');
+    set_urgency($bottom, 0, $type);
+    sync_with_i3;
 
-cmd '[urgent=latest] focus';
-is($x->input_focus, $top->id, 'second urgent window focused');
-$top->delete_hint('urgency');
-sync_with_i3;
+    cmd '[urgent=latest] focus';
+    is($x->input_focus, $top->id, 'second urgent window focused');
+    set_urgency($top, 0, $type);
+    sync_with_i3;
 
 ################################################################################
 # Same thing, but with multiple windows and using the 'urgency=oldest' criteria
 # (verify that it works in the correct order).
 ################################################################################
 
-cmd "workspace $otmp";
-is($x->input_focus, $different_window->id, 'new window focused again');
+    cmd "workspace $otmp";
+    is($x->input_focus, $different_window->id, 'new window focused again');
 
-$top->add_hint('urgency');
-sync_with_i3;
+    set_urgency($top, 1, $type);
+    sync_with_i3;
 
-$bottom->add_hint('urgency');
-sync_with_i3;
+    set_urgency($bottom, 1, $type);
+    sync_with_i3;
 
-cmd '[urgent=oldest] focus';
-is($x->input_focus, $top->id, 'oldest urgent window focused');
-$top->delete_hint('urgency');
-sync_with_i3;
+    cmd '[urgent=oldest] focus';
+    is($x->input_focus, $top->id, 'oldest urgent window focused');
+    set_urgency($top, 0, $type);
+    sync_with_i3;
 
-cmd '[urgent=oldest] focus';
-is($x->input_focus, $bottom->id, 'oldest urgent window focused');
-$bottom->delete_hint('urgency');
-sync_with_i3;
+    cmd '[urgent=oldest] focus';
+    is($x->input_focus, $bottom->id, 'oldest urgent window focused');
+    set_urgency($bottom, 0, $type);
+    sync_with_i3;
 
 ################################################################################
 # Check if urgent flag gets propagated to parent containers
 ################################################################################
 
-cmd 'split v';
+    cmd 'split v';
 
 
 
-sub count_urgent {
-    my ($con) = @_;
+    sub count_urgent {
+        my ($con) = @_;
 
-    my @children = (@{$con->{nodes}}, @{$con->{floating_nodes}});
-    my $urgent = grep { $_->{urgent} } @children;
-    $urgent += count_urgent($_) for @children;
-    return $urgent;
-}
+        my @children = (@{$con->{nodes}}, @{$con->{floating_nodes}});
+        my $urgent = grep { $_->{urgent} } @children;
+        $urgent += count_urgent($_) for @children;
+        return $urgent;
+    }
 
-$tmp = fresh_workspace;
+    $tmp = fresh_workspace;
 
-my $win1 = open_window;
-my $win2 = open_window;
-cmd 'layout stacked';
-cmd 'split vertical';
-my $win3 = open_window;
-my $win4 = open_window;
-cmd 'split horizontal' ;
-my $win5 = open_window;
-my $win6 = open_window;
+    my $win1 = open_window;
+    my $win2 = open_window;
+    cmd 'layout stacked';
+    cmd 'split vertical';
+    my $win3 = open_window;
+    my $win4 = open_window;
+    cmd 'split horizontal' ;
+    my $win5 = open_window;
+    my $win6 = open_window;
 
-sync_with_i3;
+    sync_with_i3;
 
 
-my $urgent = count_urgent(get_ws($tmp));
-is($urgent, 0, 'no window got the urgent flag');
+    my $urgent = count_urgent(get_ws($tmp));
+    is($urgent, 0, 'no window got the urgent flag');
 
-cmd '[id="' . $win2->id . '"] focus';
-sync_with_i3;
-$win5->add_hint('urgency');
-$win6->add_hint('urgency');
-sync_with_i3;
+    cmd '[id="' . $win2->id . '"] focus';
+    sync_with_i3;
+    set_urgency($win5, 1, $type);
+    set_urgency($win6, 1, $type);
+    sync_with_i3;
 
 # we should have 5 urgent cons. win5, win6 and their 3 split parents.
 
-$urgent = count_urgent(get_ws($tmp));
-is($urgent, 5, '2 windows and 3 split containers got the urgent flag');
+    $urgent = count_urgent(get_ws($tmp));
+    is($urgent, 5, '2 windows and 3 split containers got the urgent flag');
 
-cmd '[id="' . $win5->id . '"] focus';
-sync_with_i3;
+    cmd '[id="' . $win5->id . '"] focus';
+    sync_with_i3;
 
 # now win5 and still the split parents should be urgent.
-$urgent = count_urgent(get_ws($tmp));
-is($urgent, 4, '1 window and 3 split containers got the urgent flag');
+    $urgent = count_urgent(get_ws($tmp));
+    is($urgent, 4, '1 window and 3 split containers got the urgent flag');
 
-cmd '[id="' . $win6->id . '"] focus';
-sync_with_i3;
+    cmd '[id="' . $win6->id . '"] focus';
+    sync_with_i3;
 
 # now now window should be urgent.
-$urgent = count_urgent(get_ws($tmp));
-is($urgent, 0, 'All urgent flags got cleared');
+    $urgent = count_urgent(get_ws($tmp));
+    is($urgent, 0, 'All urgent flags got cleared');
 
 ################################################################################
 # Regression test: Check that urgent floating containers work properly (ticket
 # #821)
 ################################################################################
 
-$tmp = fresh_workspace;
-my $floating_win = open_floating_window;
+    $tmp = fresh_workspace;
+    my $floating_win = open_floating_window;
 
 # switch away
-fresh_workspace;
-
-$floating_win->add_hint('urgency');
-sync_with_i3;
-
-cmd "workspace $tmp";
-
-does_i3_live;
-
-exit_gracefully($pid);
+    fresh_workspace;
+
+    set_urgency($floating_win, 1, $type);
+    sync_with_i3;
+
+    cmd "workspace $tmp";
+
+    does_i3_live;
+
+###############################################################################
+# Check if the urgency hint is still set when the urgent window is killed
+###############################################################################
+
+    my $ws1 = fresh_workspace;
+    my $ws2 = fresh_workspace;
+    cmd "workspace $ws1";
+    my $w1 = open_window;
+    my $w2 = open_window;
+    cmd "workspace $ws2";
+    sync_with_i3;
+    set_urgency($w1, 1, $type);
+    sync_with_i3;
+    cmd '[id="' . $w1->id . '"] kill';
+    sync_with_i3;
+    my $w = get_ws($ws1);
+    is($w->{urgent}, 0, 'Urgent flag no longer set after killing the window ' .
+       'from another workspace');
+
+    exit_gracefully($pid);
+}
 
 done_testing;
index 01765e1e92a8dbeb9f09081c8812b0c705d041e4..361716c14d449f534e880d96c0c95b83b25d13d8 100644 (file)
@@ -158,4 +158,24 @@ is(get_output_content()->{layout}, 'splith', 'content container layout ok');
 cmd 'layout stacked';
 is(get_output_content()->{layout}, 'splith', 'content container layout still ok');
 
+######################################################################
+# Splitting a workspace that has more than one child
+######################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'open';
+cmd 'open';
+cmd 'focus parent';
+cmd 'split v';
+cmd 'open';
+
+my $content = get_ws_content($tmp);
+my $fst = $content->[0];
+my $snd = $content->[1];
+
+is(@{$content}, 2, 'two containers on workspace');
+is(@{$fst->{nodes}}, 2, 'first child has two children');
+is(@{$snd->{nodes}}, 0, 'second child has no children');
+
 done_testing;
index d09910397a81de46e0cbd50938177967bdbebd51..6367488a3b4b6495fe872c356049e7b10bba31d5 100644 (file)
@@ -23,6 +23,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
 $x->root->warp_pointer(0, 0);
+sync_with_i3;
 
 sub move_workspace_test {
     my ($movecmd) = @_;
index 6af13fa566eccb4ae7e658b41f99f07845f22a56..1cee1ebbeeed77f0b498c5c5d3b23e5fb0e02905 100644 (file)
@@ -98,6 +98,7 @@ my $workspaces = get_workspace_names;
 ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
 
 $window = open_special;
+sync_with_i3;
 
 ok(@{get_ws_content($tmp)} == 0, 'still no containers');
 ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists');
@@ -157,6 +158,7 @@ $workspaces = get_workspace_names;
 ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
 
 $window = open_special;
+sync_with_i3;
 
 my $content = get_ws($tmp);
 ok(@{$content->{nodes}} == 0, 'no tiling cons');
@@ -205,6 +207,7 @@ is(@docked, 0, 'one dock client yet');
 $window = open_special(
     window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
 );
+sync_with_i3;
 
 $content = get_ws($tmp);
 ok(@{$content->{nodes}} == 0, 'no tiling cons');
index f27ec0e4b625225796663d521597fdda550933be..aff3fe3a61c6f9711b26a5bf0c6056ae1f495f63 100644 (file)
@@ -73,6 +73,7 @@ ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 ok(get_ws($tmp)->{focused}, 'current workspace focused');
 
 my $window = open_special;
+sync_with_i3;
 
 ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
 ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
index 7160399d3387fddc66bfd62caa6746a2de343b0f..ee285ff74a6087e0108f9f9dca177f1b723352b9 100644 (file)
@@ -21,6 +21,10 @@ use i3test;
 use POSIX qw(mkfifo);
 use File::Temp qw(:POSIX);
 
+SKIP: {
+
+    skip "X11::XCB too old (need >= 0.07)", 24 if $X11::XCB::VERSION < 0.07;
+
 use ExtUtils::PkgConfig;
 
 # setup dependency on libstartup-notification using pkg-config
@@ -42,16 +46,10 @@ static SnDisplay *sndisplay;
 static SnLauncheeContext *ctx;
 static xcb_connection_t *conn;
 
-// TODO: this should use $x
-void init_ctx() {
-    int screen;
-    if ((conn = xcb_connect(NULL, &screen)) == NULL ||
-        xcb_connection_has_error(conn))
-        errx(1, "x11 conn failed");
-
-    printf("screen = %d\n", screen);
+void init_ctx(void *connptr) {
+    conn = (xcb_connection_t*)connptr;
     sndisplay = sn_xcb_display_new(conn, NULL, NULL);
-    ctx = sn_launchee_context_new_from_environment(sndisplay, screen);
+    ctx = sn_launchee_context_new_from_environment(sndisplay, 0);
 }
 
 const char *get_startup_id() {
@@ -101,7 +99,7 @@ isnt($startup_id, '', 'startup_id not empty');
 $ENV{DESKTOP_STARTUP_ID} = $startup_id;
 
 # Create a new libstartup-notification launchee context
-init_ctx();
+init_ctx($x->get_xcb_conn());
 
 # Make sure the context was set up successfully
 is(get_startup_id(), $startup_id, 'libstartup-notification returns the same id');
@@ -211,5 +209,6 @@ close($fh);
 unlink($tmp);
 
 is($startup_id, '', 'startup_id empty');
+}
 
 done_testing;
index dafe51e07efca0827f8b7831e9b253a702baa997..6ee877bd288dd22625888fec93b861903a0995b8 100644 (file)
@@ -93,8 +93,7 @@ is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');
 ################################################################################
 # 3: Verify that 'scratchpad toggle' sends a window to the __i3_scratch
 # workspace and sets the scratchpad flag to SCRATCHPAD_FRESH. The window’s size
-# and position will be changed (once!) on the next 'scratchpad show' and the
-# flag will be changed to SCRATCHPAD_CHANGED.
+# and position will be changed on the next 'scratchpad show'.
 ################################################################################
 
 my ($nodes, $focus) = get_ws_content($tmp);
@@ -165,10 +164,33 @@ $__i3_scratch = get_ws('__i3_scratch');
 @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
 is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
 
-is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed');
+################################################################################
+# 6: Resizing the window should disable auto centering on scratchpad show
+################################################################################
+
+cmd 'scratchpad show';
+
+$ws = get_ws($tmp);
+is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'fresh',
+   'scratchpad_state fresh');
+
+cmd 'resize grow width 10 px';
+cmd 'scratchpad show';
+cmd 'scratchpad show';
+
+$ws = get_ws($tmp);
+$scratchrect = $ws->{floating_nodes}->[0]->{rect};
+$outputrect = $output->{rect};
+
+is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'changed',
+   'scratchpad_state changed');
+is($scratchrect->{width}, $outputrect->{width} * 0.5 + 10, 'scratch width is 50% + 10px');
+
+cmd 'resize shrink width 10 px';
+cmd 'scratchpad show';
 
 ################################################################################
-# 6: Verify that repeated 'scratchpad show' cycle through the stack, that is,
+# 7: Verify that repeated 'scratchpad show' cycle through the stack, that is,
 # toggling a visible window should insert it at the bottom of the stack of the
 # __i3_scratch workspace.
 ################################################################################
@@ -216,39 +238,6 @@ cmd 'scratchpad show';
 
 isnt(get_focused($tmp), $fresh_id, 'focus changed');
 
-################################################################################
-# 7: Verify that using scratchpad show with criteria works as expected:
-# When matching a scratchpad window which is visible, it should hide it.
-# When matching a scratchpad window which is on __i3_scratch, it should show it.
-# When matching a non-scratchpad window, it should be a no-op.
-################################################################################
-
-# Verify that using 'scratchpad show' without any matching windows is a no-op.
-$old_focus = get_focused($tmp);
-
-cmd '[title="nomatch"] scratchpad show';
-
-is(get_focused($tmp), $old_focus, 'non-matching criteria have no effect');
-
-# Verify that we can use criteria to show a scratchpad window.
-cmd '[title="scratch-match"] scratchpad show';
-
-my $scratch_focus = get_focused($tmp);
-isnt($scratch_focus, $old_focus, 'matching criteria works');
-
-cmd '[title="scratch-match"] scratchpad show';
-
-isnt(get_focused($tmp), $scratch_focus, 'matching criteria works');
-is(get_focused($tmp), $old_focus, 'focus restored');
-
-# Verify that we cannot use criteria to show a non-scratchpad window.
-my $tmp2 = fresh_workspace;
-my $non_scratch_window = open_window(name => 'non-scratch');
-cmd "workspace $tmp";
-is(get_focused($tmp), $old_focus, 'focus still ok');
-cmd '[title="non-match"] scratchpad show';
-is(get_focused($tmp), $old_focus, 'focus unchanged');
-
 ################################################################################
 # 8: Show it, move it around, hide it. Verify that the position is retained
 # when showing it again.
@@ -369,6 +358,92 @@ verify_scratchpad_move_multiple_win(0);
 $tmp = fresh_workspace;
 verify_scratchpad_move_multiple_win(1);
 
+################################################################################
+# 12: open a scratchpad window on a workspace, switch to another workspace and
+# call 'scratchpad show' again
+################################################################################
+
+sub verify_scratchpad_move_with_visible_scratch_con {
+    my ($first, $second, $cross_output) = @_;
+
+    cmd "workspace $first";
+
+    my $window1 = open_window;
+    cmd 'move scratchpad';
+
+    my $window2 = open_window;
+    cmd 'move scratchpad';
+
+    # this should bring up window 1
+    cmd 'scratchpad show';
+
+    my $ws = get_ws($first);
+    is(scalar @{$ws->{floating_nodes}}, 1, 'one floating node on ws1');
+    is($x->input_focus, $window1->id, "showed the correct scratchpad window1");
+
+    # this should bring up window 1
+    cmd "workspace $second";
+    cmd 'scratchpad show';
+    is($x->input_focus, $window1->id, "showed the correct scratchpad window1");
+
+    my $ws2 = get_ws($second);
+    is(scalar @{$ws2->{floating_nodes}}, 1, 'one floating node on ws2');
+    unless ($cross_output) {
+        ok(!workspace_exists($first), 'ws1 was empty and therefore closed');
+    } else {
+        $ws = get_ws($first);
+        is(scalar @{$ws->{floating_nodes}}, 0, 'ws1 has no floating nodes');
+    }
+
+    # hide window 1 again
+    cmd 'move scratchpad';
+
+    # this should bring up window 2
+    cmd "workspace $first";
+    cmd 'scratchpad show';
+    is($x->input_focus, $window2->id, "showed the correct scratchpad window");
+}
+
+# let's clear the scratchpad first
+sub clear_scratchpad {
+    while (scalar @{get_ws('__i3_scratch')->{floating_nodes}}) {
+        cmd 'scratchpad show';
+        cmd 'kill';
+    }
+}
+
+clear_scratchpad;
+is (scalar @{get_ws('__i3_scratch')->{floating_nodes}}, 0, "scratchpad is empty");
+
+my ($first, $second);
+$first = fresh_workspace;
+$second = fresh_workspace;
+
+verify_scratchpad_move_with_visible_scratch_con($first, $second, 0);
+does_i3_live;
+
+
+################################################################################
+# 13: Test whether scratchpad show moves focus to the scratchpad window
+# when another window on the same workspace has focus
+################################################################################
+
+clear_scratchpad;
+my $ws = fresh_workspace;
+
+open_window;
+my $scratch = get_focused($ws);
+cmd 'move scratchpad';
+cmd 'scratchpad show';
+
+open_window;
+my $not_scratch = get_focused($ws);
+is(get_focused($ws), $not_scratch, 'not scratch window has focus');
+
+cmd 'scratchpad show';
+
+is(get_focused($ws), $scratch, 'scratchpad is focused');
+
 # TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint
 
 done_testing;
index 0d3d226808a782479c90bf0906431d5c194bb442..debbb0a21487742c4971f9e4e23f4dcee69929e1 100644 (file)
@@ -146,6 +146,7 @@ $window = open_floating_window(rect => [ 0, 0, 100, 100 ]);
 cmd 'border none';
 cmd 'resize shrink height 80px or 80ppt';
 cmd 'resize shrink width 80px or 80ppt';
+sync_with_i3;
 $rect = $window->rect;
 is($rect->{width}, 60, 'width = 60');
 is($rect->{height}, 50, 'height = 50');
@@ -170,6 +171,7 @@ $window = open_floating_window(rect => [ 200, 200, 50, 50 ]);
 cmd 'border none';
 cmd 'resize grow height 100px or 100ppt';
 cmd 'resize grow width 100px or 100ppt';
+sync_with_i3;
 $rect = $window->rect;
 is($rect->{width}, 100, 'width = 100');
 is($rect->{height}, 100, 'height = 100');
@@ -177,6 +179,7 @@ is($rect->{height}, 100, 'height = 100');
 my $old_x = $rect->{x};
 my $old_y = $rect->{y};
 cmd 'resize grow up 10px or 10ppt';
+sync_with_i3;
 $rect = $window->rect;
 is($rect->{x}, $old_x, 'window did not move when trying to resize');
 is($rect->{y}, $old_y, 'window did not move when trying to resize');
index 266d29593e37b923a932f7ed85c085f3e89cb335..6a49ff7844eddebfda36bec143ac450e8a83bec4 100644 (file)
@@ -26,7 +26,7 @@ sub parser_calls {
 
     my $stdout;
     run [ '../test.config_parser', $command ],
-        '>&-',
+        '>/dev/null',
         '2>', \$stdout;
     # TODO: use a timeout, so that we can error out if it doesn’t terminate
 
@@ -144,6 +144,27 @@ is(parser_calls($config),
    $expected,
    'floating_minimum_size ok');
 
+################################################################################
+# popup_during_fullscreen
+################################################################################
+
+$config = <<'EOT';
+popup_during_fullscreen ignore
+popup_during_fullscreen leave_fullscreen
+popup_during_fullscreen SMArt
+EOT
+
+$expected = <<'EOT';
+cfg_popup_during_fullscreen(ignore)
+cfg_popup_during_fullscreen(leave_fullscreen)
+cfg_popup_during_fullscreen(smart)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'popup_during_fullscreen ok');
+
+
 ################################################################################
 # floating_modifier
 ################################################################################
@@ -391,8 +412,11 @@ hide_edge_border both
 client.focused          #4c7899 #285577 #ffffff #2e9ef4
 EOT
 
-$expected = <<'EOT';
+my $expected_all_tokens = <<'EOT';
 ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
+EOT
+
+my $expected_end = <<'EOT';
 ERROR: CONFIG: (in file <stdin>)
 ERROR: CONFIG: Line   1: hide_edge_border both
 ERROR: CONFIG:           ^^^^^^^^^^^^^^^^^^^^^
@@ -400,6 +424,8 @@ ERROR: CONFIG: Line   2: client.focused          #4c7899 #285577 #ffffff #2e9ef4
 cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4)
 EOT
 
+$expected = $expected_all_tokens . $expected_end;
+
 is(parser_calls($config),
    $expected,
    'errors dont harm subsequent statements');
@@ -438,9 +464,11 @@ unknown qux
 # this should not show up
 EOT
 
-$expected = <<'EOT';
+my $expected_head = <<'EOT';
 cfg_font(foobar)
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
+EOT
+
+my $expected_tail = <<'EOT';
 ERROR: CONFIG: (in file <stdin>)
 ERROR: CONFIG: Line   3: font foobar
 ERROR: CONFIG: Line   4: 
@@ -450,6 +478,8 @@ ERROR: CONFIG: Line   6:
 ERROR: CONFIG: Line   7: # yay
 EOT
 
+$expected = $expected_head . $expected_all_tokens . $expected_tail;
+
 is(parser_calls($config),
    $expected,
    'error message (2+2 context) ok');
@@ -462,13 +492,14 @@ $config = <<'EOT';
 unknown qux
 EOT
 
-$expected = <<'EOT';
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
+$expected_tail = <<'EOT';
 ERROR: CONFIG: (in file <stdin>)
 ERROR: CONFIG: Line   1: unknown qux
 ERROR: CONFIG:           ^^^^^^^^^^^
 EOT
 
+$expected = $expected_all_tokens . $expected_tail;
+
 is(parser_calls($config),
    $expected,
    'error message (0+0 context) ok');
@@ -482,14 +513,15 @@ $config = <<'EOT';
 unknown qux
 EOT
 
-$expected = <<'EOT';
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
+$expected_tail = <<'EOT';
 ERROR: CONFIG: (in file <stdin>)
 ERROR: CONFIG: Line   1: # context before
 ERROR: CONFIG: Line   2: unknown qux
 ERROR: CONFIG:           ^^^^^^^^^^^
 EOT
 
+$expected = $expected_all_tokens . $expected_tail;
+
 is(parser_calls($config),
    $expected,
    'error message (1+0 context) ok');
@@ -503,14 +535,15 @@ unknown qux
 # context after
 EOT
 
-$expected = <<'EOT';
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
+$expected_tail = <<'EOT';
 ERROR: CONFIG: (in file <stdin>)
 ERROR: CONFIG: Line   1: unknown qux
 ERROR: CONFIG:           ^^^^^^^^^^^
 ERROR: CONFIG: Line   2: # context after
 EOT
 
+$expected = $expected_all_tokens . $expected_tail;
+
 is(parser_calls($config),
    $expected,
    'error message (0+1 context) ok');
@@ -525,8 +558,7 @@ unknown qux
 # context 2 after
 EOT
 
-$expected = <<'EOT';
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
+$expected_tail = <<'EOT';
 ERROR: CONFIG: (in file <stdin>)
 ERROR: CONFIG: Line   1: unknown qux
 ERROR: CONFIG:           ^^^^^^^^^^^
@@ -534,6 +566,8 @@ ERROR: CONFIG: Line   2: # context after
 ERROR: CONFIG: Line   3: # context 2 after
 EOT
 
+$expected = $expected_all_tokens . $expected_tail;
+
 is(parser_calls($config),
    $expected,
    'error message (0+2 context) ok');
diff --git a/testcases/t/202-scratchpad-criteria.t b/testcases/t/202-scratchpad-criteria.t
new file mode 100644 (file)
index 0000000..2603f65
--- /dev/null
@@ -0,0 +1,58 @@
+#!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 using criteria to address scratchpad windows works.
+use i3test;
+
+################################################################################
+# Verify that using scratchpad show with criteria works as expected:
+# When matching a scratchpad window which is visible, it should hide it.
+# When matching a scratchpad window which is on __i3_scratch, it should show it.
+# When matching a non-scratchpad window, it should be a no-op.
+################################################################################
+
+my $tmp = fresh_workspace;
+
+my $third_window = open_window(name => 'scratch-match');
+cmd 'move scratchpad';
+
+# Verify that using 'scratchpad show' without any matching windows is a no-op.
+my $old_focus = get_focused($tmp);
+
+cmd '[title="nomatch"] scratchpad show';
+
+is(get_focused($tmp), $old_focus, 'non-matching criteria have no effect');
+
+# Verify that we can use criteria to show a scratchpad window.
+cmd '[title="scratch-match"] scratchpad show';
+
+my $scratch_focus = get_focused($tmp);
+isnt($scratch_focus, $old_focus, 'matching criteria works');
+
+cmd '[title="scratch-match"] scratchpad show';
+
+isnt(get_focused($tmp), $scratch_focus, 'matching criteria works');
+is(get_focused($tmp), $old_focus, 'focus restored');
+
+# Verify that we cannot use criteria to show a non-scratchpad window.
+my $tmp2 = fresh_workspace;
+my $non_scratch_window = open_window(name => 'non-scratch');
+cmd "workspace $tmp";
+is(get_focused($tmp), $old_focus, 'focus still ok');
+cmd '[title="non-match"] scratchpad show';
+is(get_focused($tmp), $old_focus, 'focus unchanged');
+
+done_testing;
diff --git a/testcases/t/204-regress-scratchpad-move.t b/testcases/t/204-regress-scratchpad-move.t
new file mode 100644 (file)
index 0000000..8c307aa
--- /dev/null
@@ -0,0 +1,65 @@
+#!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)
+#
+# Moves the last window of a workspace to the scratchpad. The workspace will be
+# cleaned up and previously, the subsequent focusing of a destroyed container
+# would crash i3.
+# Ticket: #913
+# Bug still in: 4.4-97-gf767ac3
+use i3test;
+use X11::XCB qw(:all);
+
+# TODO: move to X11::XCB
+sub set_wm_class {
+    my ($id, $class, $instance) = @_;
+
+    # Add a _NET_WM_STRUT_PARTIAL hint
+    my $atomname = $x->atom(name => 'WM_CLASS');
+    my $atomtype = $x->atom(name => 'STRING');
+
+    $x->change_property(
+        PROP_MODE_REPLACE,
+        $id,
+        $atomname->id,
+        $atomtype->id,
+        8,
+        length($class) + length($instance) + 2,
+        "$instance\x00$class\x00"
+    );
+}
+
+sub open_special {
+    my %args = @_;
+    my $wm_class = delete($args{wm_class}) || 'special';
+
+    return open_window(
+        %args,
+        before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
+    );
+}
+
+my $tmp = fresh_workspace;
+
+# Open a new window which we can identify later on based on its WM_CLASS.
+my $scratch = open_special;
+
+my $tmp2 = fresh_workspace;
+
+cmd '[class="special"] move scratchpad';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/205-ipc-windows.t b/testcases/t/205-ipc-windows.t
new file mode 100644 (file)
index 0000000..aa679e2
--- /dev/null
@@ -0,0 +1,49 @@
+#!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 event
+################################
+
+# Events
+
+my $new = AnyEvent->condvar;
+$i3->subscribe({
+    window => sub {
+        my ($event) = @_;
+        $new->send($event->{change} eq 'new');
+    }
+})->recv;
+
+open_window;
+
+my $t;
+$t = AnyEvent->timer(after => 0.5, cb => sub { $new->send(0); });
+
+ok($new->recv, 'Window "new" event received');
+
+}
+
+done_testing;
index 20d4fd2ba1a0abca0c694c874f2de8db6678f355..c79643e3592705bb98441b7ee5ee10f56691c740 100644 (file)
@@ -61,8 +61,18 @@ open_window;
 # output 2: 2
 cmd 'workspace 1';
 cmd 'workspace next';
+# We need to sync after changing focus to a different output to wait for the
+# EnterNotify to be processed, otherwise it will be processed at some point
+# later in time and mess up our subsequent tests.
+sync_with_i3;
+
 is(focused_ws, '2', 'workspace 2 focused');
 cmd 'workspace next';
+# We need to sync after changing focus to a different output to wait for the
+# EnterNotify to be processed, otherwise it will be processed at some point
+# later in time and mess up our subsequent tests.
+sync_with_i3;
+
 is(focused_ws, '5', 'workspace 5 focused');
 
 ################################################################################
@@ -81,11 +91,9 @@ cmd 'workspace prev_on_output';
 is(focused_ws, '1', 'workspace 1 focused');
 
 cmd 'workspace 2';
-
-# XXX: This is to avoid EnterNotifies changing the focus. Not sure why they
-# appear sometimes in the first place. Only happens when running the full
-# testsuite.
-$x->root->warp_pointer(1025, 0);
+# We need to sync after changing focus to a different output to wait for the
+# EnterNotify to be processed, otherwise it will be processed at some point
+# later in time and mess up our subsequent tests.
 sync_with_i3;
 
 cmd 'workspace prev_on_output';
index c087f9f5ad117946b493383a5bbd893bf84922e3..018707e6cf5e522a8527b8b6db57b5e4acc2a600 100644 (file)
@@ -114,6 +114,16 @@ cmd 'move workspace to output left';
 ($x0, $x1) = workspaces_per_screen();
 ok('5' ~~ @$x0, 'workspace 5 back on fake-0');
 
+# Verify that wrapping works
+cmd 'move workspace to output left';
+($x0, $x1) = workspaces_per_screen();
+ok('5' ~~ @$x1, 'workspace 5 on fake-1');
+
+# Put workspace 5 where it should
+cmd 'move workspace to output left';
+($x0, $x1) = workspaces_per_screen();
+ok('5' ~~ @$x0, 'workspace 5 on fake-0 again');
+
 ################################################################################
 # Verify that coordinates of floating windows are fixed correctly when moving a
 # workspace to a different output.
diff --git a/testcases/t/508-move-workspace-focus.t b/testcases/t/508-move-workspace-focus.t
deleted file mode 100644 (file)
index 7d42ff4..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-#!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)
-#
-# Regression test: Verify that focus is correct after moving a floating window
-# to a workspace on a different visible output.
-# Bug still in: 4.3-83-ge89a25f
-use i3test i3_autostart => 0;
-
-# Ensure the pointer is at (0, 0) so that we really start on the first
-# (the left) workspace.
-$x->root->warp_pointer(0, 0);
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-
-fake-outputs 1024x768+0+0,1024x768+1024+0
-EOT
-my $pid = launch_with_config($config);
-
-my $left_ws = fresh_workspace(output => 0);
-open_window;
-
-my $right_ws = fresh_workspace(output => 1);
-open_window;
-my $right_float = open_floating_window;
-
-cmd "move workspace $left_ws";
-is($x->input_focus, $right_float->id, 'floating window still focused');
-
-exit_gracefully($pid);
-
-done_testing;
index 7f68a2d50abe0c20063e25794cbb0d7f58de4895..2c6fd396ff5471e0e55c0faee59152978ee81b44 100644 (file)
@@ -39,22 +39,22 @@ cmd 'floating toggle';
 
 # Focus screen 1
 $x->root->warp_pointer(1025, 0);
-my $s1_ws = fresh_workspace;
 sync_with_i3;
+my $s1_ws = fresh_workspace;
 
 my $fourth = open_window;
 
 # Focus screen 2
 $x->root->warp_pointer(0, 769);
-my $s2_ws = fresh_workspace;
 sync_with_i3;
+my $s2_ws = fresh_workspace;
 
 my $fifth = open_window;
 
 # Focus screen 3
 $x->root->warp_pointer(1025, 769);
-my $s3_ws = fresh_workspace;
 sync_with_i3;
+my $s3_ws = fresh_workspace;
 
 my $sixth = open_window;
 my $seventh = open_window;
diff --git a/testcases/t/512-move-wraps.t b/testcases/t/512-move-wraps.t
new file mode 100644 (file)
index 0000000..82d5a7c
--- /dev/null
@@ -0,0 +1,56 @@
+#!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 moving containers wraps across outputs.
+# E.g. when you have a container on the right output and you move it to the
+# right, it should appear on the left output.
+# Bug still in: 4.4-106-g3cd4b8c
+use i3test i3_autostart => 0;
+
+# Ensure the pointer is at (0, 0) so that we really start on the first
+# (the left) workspace.
+$x->root->warp_pointer(0, 0);
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
+
+my $pid = launch_with_config($config);
+
+my $right = fresh_workspace(output => 1);
+my $left = fresh_workspace(output => 0);
+
+my $win = open_window;
+
+is_num_children($left, 1, 'one container on left workspace');
+
+cmd 'move container to output right';
+cmd 'focus output right';
+
+is_num_children($left, 0, 'no containers on left workspace');
+is_num_children($right, 1, 'one container on right workspace');
+
+cmd 'move container to output right';
+
+is_num_children($left, 1, 'one container on left workspace');
+is_num_children($right, 0, 'no containers on right workspace');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/513-move-workspace.t b/testcases/t/513-move-workspace.t
new file mode 100644 (file)
index 0000000..17c8de7
--- /dev/null
@@ -0,0 +1,97 @@
+#!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 whether moving workspaces between outputs works correctly.
+use i3test i3_autostart => 0;
+use List::Util qw(first);
+
+# Ensure the pointer is at (0, 0) so that we really start on the first
+# (the left) workspace.
+$x->root->warp_pointer(0, 0);
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
+
+my $pid = launch_with_config($config);
+
+sub workspaces_per_screen {
+    my $i3 = i3(get_socket_path());
+    my $tree = $i3->get_tree->recv;
+    my @outputs = @{$tree->{nodes}};
+
+    my $fake0 = first { $_->{name} eq 'fake-0' } @outputs;
+    my $fake0_content = first { $_->{type} == 2 } @{$fake0->{nodes}};
+
+    my $fake1 = first { $_->{name} eq 'fake-1' } @outputs;
+    my $fake1_content = first { $_->{type} == 2 } @{$fake1->{nodes}};
+
+    my @fake0_workspaces = map { $_->{name} } @{$fake0_content->{nodes}};
+    my @fake1_workspaces = map { $_->{name} } @{$fake1_content->{nodes}};
+
+    return \@fake0_workspaces, \@fake1_workspaces;
+}
+
+# Switch to temporary workspaces on both outputs so the numbers are free.
+my $tmp_right = fresh_workspace(output => 1);
+my $tmp_left = fresh_workspace(output => 0);
+
+cmd 'workspace 1';
+# Keep that workspace open.
+my $win1 = open_window;
+
+cmd 'workspace 5';
+# Keep that workspace open.
+open_window;
+
+cmd "workspace $tmp_right";
+cmd 'workspace 2';
+# Keep that workspace open.
+open_window;
+
+my ($x0, $x1) = workspaces_per_screen();
+is_deeply($x0, [ '1', '5' ], 'workspace 1 and 5 on fake-0');
+is_deeply($x1, [ '2' ], 'workspace 2 on fake-1');
+
+cmd 'workspace 1';
+
+my ($nodes, $focus) = get_ws_content('1');
+is($nodes->[0]->{window}, $win1->id, 'window 1 on workspace 1');
+
+cmd 'move workspace next';
+cmd '[id="' . $win1->id . '"] focus';
+
+($nodes, $focus) = get_ws_content('2');
+is($nodes->[1]->{window}, $win1->id, 'window 1 on workspace 2 after moving');
+
+cmd 'move workspace prev';
+cmd '[id="' . $win1->id . '"] focus';
+
+($nodes, $focus) = get_ws_content('1');
+is($nodes->[0]->{window}, $win1->id, 'window 1 on workspace 1');
+
+cmd 'move workspace next_on_output';
+cmd '[id="' . $win1->id . '"] focus';
+
+($nodes, $focus) = get_ws_content('5');
+is($nodes->[1]->{window}, $win1->id, 'window 1 on workspace 5 after moving');
+
+exit_gracefully($pid);
+
+done_testing;