]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'master' into next
authorMichael Stapelberg <michael@stapelberg.de>
Mon, 29 Oct 2012 15:42:11 +0000 (16:42 +0100)
committerMichael Stapelberg <michael@stapelberg.de>
Mon, 29 Oct 2012 15:42:11 +0000 (16:42 +0100)
128 files changed:
.gitignore
RELEASE-NOTES-4.3
debian/changelog
debian/patches/manpage-x-terminal-emulator.patch
debian/patches/use-x-terminal-emulator.patch
debian/rules
docs/i3bar-protocol
docs/ipc
docs/userguide
generate-command-parser.pl
i3-config-wizard/xcb.h
i3-input/i3-input.h
i3-nagbar/i3-nagbar.h
i3.config.keycodes
i3bar/include/child.h
i3bar/include/common.h
i3bar/include/determine_json_version.h [deleted file]
i3bar/include/parse_json_header.h [new file with mode: 0644]
i3bar/include/util.h
i3bar/include/xcb.h
i3bar/src/child.c
i3bar/src/determine_json_version.c [deleted file]
i3bar/src/ipc.c
i3bar/src/parse_json_header.c [new file with mode: 0644]
i3bar/src/xcb.c
include/all.h
include/assignments.h
include/click.h
include/cmdparse.h
include/commands.h
include/commands_parser.h
include/con.h
include/config.h
include/config_directives.h [new file with mode: 0644]
include/config_parser.h [new file with mode: 0644]
include/data.h
include/debug.h
include/display_version.h
include/ewmh.h
include/fake_outputs.h
include/floating.h
include/handlers.h
include/i3.h
include/i3/ipc.h
include/ipc.h
include/key_press.h
include/libi3.h
include/load_layout.h
include/log.h
include/manage.h
include/match.h
include/move.h
include/output.h
include/randr.h
include/regex.h
include/render.h
include/resize.h
include/scratchpad.h
include/shmlog.h
include/sighandler.h
include/startup.h
include/tree.h
include/util.h
include/window.h
include/workspace.h
include/x.h
include/xcb.h
include/xcb_compat.h
include/xcursor.h
include/xinerama.h
man/i3-msg.man
parser-specs/commands.spec
parser-specs/config.spec [new file with mode: 0644]
parser-specs/highlighting.vim
src/cfgparse.l
src/cfgparse.y
src/click.c
src/commands.c
src/commands_parser.c
src/con.c
src/config.c
src/config_directives.c [new file with mode: 0644]
src/config_parser.c [new file with mode: 0644]
src/floating.c
src/handlers.c
src/i3.mk
src/ipc.c
src/load_layout.c
src/log.c
src/main.c
src/manage.c
src/randr.c
src/render.c
src/resize.c
src/scratchpad.c
src/startup.c
src/tree.c
src/workspace.c
src/x.c
src/xcursor.c
testcases/Makefile.PL
testcases/complete-run.pl
testcases/new-test [new file with mode: 0755]
testcases/t/100-fullscreen.t
testcases/t/113-urgent.t
testcases/t/116-nestedcons.t
testcases/t/117-workspace.t
testcases/t/132-move-workspace.t
testcases/t/135-floating-focus.t
testcases/t/141-resize.t
testcases/t/156-fullscreen-focus.t
testcases/t/161-regress-borders-restart.t
testcases/t/166-assign.t
testcases/t/169-border-toggle.t
testcases/t/173-regress-focus-assign.t
testcases/t/174-border-config.t
testcases/t/175-startup-notification.t
testcases/t/176-workspace-baf.t
testcases/t/185-scratchpad.t
testcases/t/198-regression-scratchpad-crash.t
testcases/t/199-ipc-mode-event.t [new file with mode: 0644]
testcases/t/200-urgency-timer.t [new file with mode: 0644]
testcases/t/201-config-parser.t [new file with mode: 0644]
testcases/t/504-move-workspace-to-output.t
testcases/t/506-focus-right.t [new file with mode: 0644]
testcases/t/507-workspace-move-crash.t [new file with mode: 0644]
testcases/t/508-move-workspace-focus.t [new file with mode: 0644]
testcases/t/510-focus-across-outputs.t [new file with mode: 0644]

index e50eb4fb1fb9b7881e66913b854e9b6b0cb09f00..26c170f2b4e4e0177afd1b171e4b059f83b3aefc 100644 (file)
@@ -14,6 +14,7 @@ testcases/_Inline
 testcases/inc
 testcases/META.yml
 test.commands_parser
+test.config_parser
 *.output
 *.tab.*
 *.yy.c
index ca77397aaf95ab63a8b8055013e1bec8ea4e737f..68fce10a41a588178dfc0d7850f2cddaddb75ef7 100644 (file)
@@ -22,7 +22,7 @@ We also made the orientation (horizontal/vertical) part of the layout
 
   To change a splith container into a splitv container, use either "layout
   splitv" or "layout toggle split". The latter command is used in the
-  default config as mod+l (formerly "layout default"). In case you have
+  default config as mod+e (formerly "layout default"). In case you have
   "layout default" in your config file, it is recommended to just replace
   it by "layout toggle split", which will work as "layout default" did
   before when pressing it once, but toggle between horizontal/vertical
index 89184c130a22b5480d9ec1779b731cd570f0649c..c4c2681c47ffa227ff076968796f354fc1554d25 100644 (file)
@@ -1,8 +1,14 @@
-i3-wm (4.2.1-0) unstable; urgency=low
+i3-wm (4.3.1-0) unstable; urgency=low
 
   * NOT YET RELEASED
 
- -- Michael Stapelberg <michael@stapelberg.de>  Fri, 27 Jan 2012 19:34:11 +0000
+ -- Michael Stapelberg <michael@stapelberg.de>  Wed, 19 Sep 2012 18:13:15 +0200
+
+i3-wm (4.3-1) experimental; urgency=low
+
+  * New upstream release
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Wed, 19 Sep 2012 18:13:40 +0200
 
 i3-wm (4.2-1) unstable; urgency=low
 
index ed6cb465f542b1c1cf5b764513c76d56744f0be4..17b8ee4cb9ba7522e3e38f717b021ca1c4363a3d 100644 (file)
@@ -1,3 +1,11 @@
+Description: list x-terminal-emulator as one of i3-sensible-terminal’s choices
+Author: Michael Stapelberg <stapelberg@debian.org>
+Origin: vendor
+Forwarded: not-needed
+Last-Update: 2011-12-28
+
+---
+
 Index: i3-4.1.1/man/i3-sensible-terminal.man
 ===================================================================
 --- i3-4.1.1.orig/man/i3-sensible-terminal.man 2011-12-28 23:56:55.487581000 +0100
index 0d71f7c4d3fe14f8dee694d500782ae12a7188eb..9616162493dd78f3b3c400f934fa3f5def731545 100644 (file)
@@ -1,7 +1,15 @@
-Index: i3-4.1.1/i3-sensible-terminal
+Description: i3-sensible-terminal: try x-terminal-emulator first
+Author: Michael Stapelberg <stapelberg@debian.org>
+Origin: vendor
+Forwarded: not-needed
+Last-Update: 2012-09-19
+
+---
+
+Index: i3-4.3/i3-sensible-terminal
 ===================================================================
---- i3-4.1.1.orig/i3-sensible-terminal 2011-12-28 23:51:52.455610236 +0100
-+++ i3-4.1.1/i3-sensible-terminal      2011-12-28 23:52:00.826775027 +0100
+--- i3-4.3.orig/i3-sensible-terminal   2012-09-19 18:08:09.000000000 +0200
++++ i3-4.3/i3-sensible-terminal        2012-09-19 18:32:06.393883488 +0200
 @@ -4,11 +4,7 @@
  #
  # This script tries to exec a terminal emulator by trying some known terminal
@@ -10,8 +18,8 @@ Index: i3-4.1.1/i3-sensible-terminal
 -# Distributions/packagers should enhance this script with a
 -# distribution-specific mechanism to find the preferred terminal emulator. On
 -# Debian, there is the x-terminal-emulator symlink for example.
--for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do
-+for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do
+-for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do
++for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do
      if which $terminal > /dev/null 2>&1; then
          exec $terminal "$@"
      fi
index 6bae8165b42102cafac987fa6ecc07165830a1d9..011119d0188950ad4f868af90cfa89b7d8b6d150 100755 (executable)
@@ -38,7 +38,7 @@ override_dh_auto_build:
        $(MAKE) -C docs
 
 override_dh_installchangelogs:
-       dh_installchangelogs RELEASE-NOTES-4.2
+       dh_installchangelogs RELEASE-NOTES-4.3
 
 override_dh_install:
        $(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install
index 21ba9aa0f8e350252e1fdbc8f7a8f76b9d125056..29ce571346985f499b1215bace0ea01f979547d2 100644 (file)
@@ -44,11 +44,16 @@ understand the old protocol version, but in order to use the new one, you need
 to provide the correct version. The header block is terminated by a newline and
 consists of a single JSON hash:
 
-*Example*:
+*Minimal example*:
 ----------------
 { "version": 1 }
 ----------------
 
+*All features example*:
+----------------
+{ "version": 1, "stop_signal": 10, "cont_signal": 12 }
+----------------
+
 (Note that before i3 v4.3 the precise format had to be +{"version":1}+,
 byte-for-byte.)
 
@@ -93,6 +98,19 @@ You can find an example of a shell script which can be used as your
 +status_command+ in the bar configuration at
 http://code.stapelberg.de/git/i3/tree/contrib/trivial-bar-script.sh?h=next
 
+=== Header in detail
+
+version::
+       The version number (as an integer) of the i3bar protocol you will use.
+stop_signal::
+       Specify to i3bar the signal (as an integer) to send to stop your
+       processing.
+       The default value (if none is specified) is SIGSTOP.
+cont_signal::
+       Specify to i3bar the signal (as an integer)to send to continue your
+       processing.
+       The default value (if none is specified) is SIGCONT.
+
 === Blocks in detail
 
 full_text::
index f8dfa78e4ca0fdb117f0dd4b79365cd98bdba850..6bdccd0b650c4629df430fc760a01d2f1ba3780d 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -1,7 +1,7 @@
 IPC interface (interprocess communication)
 ==========================================
 Michael Stapelberg <michael@i3wm.org>
-August 2012
+October 2012
 
 This document describes how to interface with i3 from a separate process. This
 is useful for example to remote-control i3 (to write test cases for example) or
@@ -19,6 +19,13 @@ calling +i3 --get-socketpath+.
 All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+
 X11 property, stored on the X11 root window.
 
+[WARNING]
+.Use an existing library!
+There are existing libraries for many languages. You can have a look at
+<<libraries>> or search the web if your language of choice is not mentioned.
+Usually, it is not necessary to implement low-level communication with i3
+directly.
+
 == Establishing a connection
 
 To establish a connection, simply open the IPC socket. The following code
@@ -82,7 +89,7 @@ So, a typical message could look like this:
 Or, as a hexdump:
 ------------------------------------------------------------------------------
 00000000  69 33 2d 69 70 63 04 00  00 00 00 00 00 00 65 78  |i3-ipc........ex|
-00000010  69 74 0a                                          |it.|
+00000010  69 74                                             |it|
 ------------------------------------------------------------------------------
 
 To generate and send such a message, you could use the following code in Perl:
@@ -274,6 +281,8 @@ name (string)::
 border (string)::
        Can be either "normal", "none" or "1pixel", dependending on the
        container’s border style.
+current_border_width (integer)::
+       Number of pixels of the border width.
 layout (string)::
        Can be either "splith", "splitv", "stacked", "tabbed", "dockarea" or
        "output".
@@ -610,6 +619,8 @@ workspace (0)::
 output (1)::
        Sent when RandR issues a change notification (of either screens,
        outputs, CRTCs or output properties).
+mode (2)::
+       Sent whenever i3 changes its binding mode.
 
 *Example:*
 --------------------------------------------------------------------
@@ -651,7 +662,21 @@ This event consists of a single serialized map containing a property
 { "change": "unspecified" }
 ---------------------------
 
-== See also
+=== mode event
+
+This event consists of a single serialized map containing a property
++change (string)+ which holds the name of current mode in use. The name
+is the same as specified in config when creating a mode. The default
+mode is simply named default.
+
+*Example:*
+---------------------------
+{ "change": "default" }
+---------------------------
+
+== See also (existing libraries)
+
+[[libraries]]
 
 For some languages, libraries are available (so you don’t have to implement
 all this on your own). This list names some (if you wrote one, please let me
index 2214f016522764598a3f7517cee285cf735c228e..72555b53d1fe0505712c7d29e7ee362a65b3d8b2 100644 (file)
@@ -12,28 +12,28 @@ contact us on IRC (preferred) or post your question(s) on the mailing list.
 For the "too long; didn’t read" people, here is an overview of the default
 keybindings (click to see the full size image):
 
-*Keys to use with mod (alt):*
+*Keys to use with $mod (Alt):*
 
-image:keyboard-layer1.png["Keys to use with mod (alt)",width=600,link="keyboard-layer1.png"]
+image:keyboard-layer1.png["Keys to use with $mod (Alt)",width=600,link="keyboard-layer1.png"]
 
-*Keys to use with Shift+mod:*
+*Keys to use with Shift+$mod:*
 
-image:keyboard-layer2.png["Keys to use with Shift+mod",width=600,link="keyboard-layer2.png"]
+image:keyboard-layer2.png["Keys to use with Shift+$mod",width=600,link="keyboard-layer2.png"]
 
 The red keys are the modifiers you need to press (by default), the blue keys
 are your homerow.
 
 == Using i3
 
-Throughout this guide, the keyword +mod+ will be used to refer to the
-configured modifier. This is the alt key (Mod1) by default, with windows (Mod4)
+Throughout this guide, the keyword +$mod+ will be used to refer to the
+configured modifier. This is the Alt key (Mod1) by default, with windows (Mod4)
 being a popular alternative.
 
 === Opening terminals and moving around
 
 One very basic operation is opening a new terminal. By default, the keybinding
-for this is mod+Enter, that is Alt+Enter in the default configuration. By
-pressing mod+Enter, a new terminal will be opened.  It will fill the whole
+for this is $mod+Enter, that is Alt+Enter in the default configuration. By
+pressing $mod+Enter, a new terminal will be opened.  It will fill the whole
 space available on your screen.
 
 image:single_terminal.png[Single terminal]
@@ -48,9 +48,9 @@ image:two_terminals.png[Two terminals]
 To move the focus between the two terminals, you can use the direction keys
 which you may know from the editor +vi+. However, in i3, your homerow is used
 for these keys (in +vi+, the keys are shifted to the left by one for
-compatibility with most keyboard layouts). Therefore, +mod+J+ is left, +mod+K+
-is down, +mod+L+ is up and `mod+;` is right. So, to switch between the
-terminals, use +mod+K+ or +mod+L+. Of course, you can also use the arrow keys.
+compatibility with most keyboard layouts). Therefore, +$mod+J+ is left, +$mod+K+
+is down, +$mod+L+ is up and `$mod+;` is right. So, to switch between the
+terminals, use +$mod+K+ or +$mod+L+. Of course, you can also use the arrow keys.
 
 At the moment, your workspace is split (it contains two terminals) in a
 specific direction (horizontal by default). Every window can be split
@@ -61,8 +61,8 @@ windows.
 
 TODO: picture of the tree
 
-To split a window vertically, press +mod+v+ before you create the new window.
-To split it horizontally, press +mod+h+.
+To split a window vertically, press +$mod+v+ before you create the new window.
+To split it horizontally, press +$mod+h+.
 
 === Changing the container layout
 
@@ -80,15 +80,15 @@ tabbed::
 The same principle as +stacking+, but the list of windows at the top is only
 a single line which is vertically split.
 
-To switch modes, press +mod+e+ for splith/splitv (it toggles), +mod+s+ for
-stacking and +mod+w+ for tabbed.
+To switch modes, press +$mod+e+ for splith/splitv (it toggles), +$mod+s+ for
+stacking and +$mod+w+ for tabbed.
 
 image:modes.png[Container modes]
 
 === Toggling fullscreen mode for a window
 
 To display a window in fullscreen mode or to go out of fullscreen mode again,
-press +mod+f+.
+press +$mod+f+.
 
 There is also a global fullscreen mode in i3 in which the client will span all
 available outputs (the command is +fullscreen global+).
@@ -96,7 +96,7 @@ available outputs (the command is +fullscreen global+).
 === Opening other applications
 
 Aside from opening applications from a terminal, you can also use the handy
-+dmenu+ which is opened by pressing +mod+d+ by default. Just type the name
++dmenu+ which is opened by pressing +$mod+d+ by default. Just type the name
 (or a part of it) of the application which you want to open. The corresponding
 application has to be in your +$PATH+ for this to work.
 
@@ -108,7 +108,7 @@ create a keybinding for starting the application directly. See the section
 
 If an application does not provide a mechanism for closing (most applications
 provide a menu, the escape key or a shortcut like +Control+W+ to close), you
-can press +mod+Shift+q+ to kill a window. For applications which support
+can press +$mod+Shift+q+ to kill a window. For applications which support
 the WM_DELETE protocol, this will correctly close the application (saving
 any modifications or doing other cleanup). If the application doesn’t support
 the WM_DELETE protocol your X server will kill the window and the behaviour
@@ -118,7 +118,7 @@ depends on the application.
 
 Workspaces are an easy way to group a set of windows. By default, you are on
 the first workspace, as the bar on the bottom left indicates. To switch to
-another workspace, press +mod+num+ where +num+ is the number of the workspace
+another workspace, press +$mod+num+ where +num+ is the number of the workspace
 you want to use. If the workspace does not exist yet, it will be created.
 
 A common paradigm is to put the web browser on one workspace, communication
@@ -132,7 +132,7 @@ focus to that screen.
 
 === Moving windows to workspaces
 
-To move a window to another workspace, simply press +mod+Shift+num+ where
+To move a window to another workspace, simply press +$mod+Shift+num+ where
 +num+ is (like when switching workspaces) the number of the target workspace.
 Similarly to switching workspaces, the target workspace will be created if
 it does not yet exist.
@@ -148,11 +148,11 @@ columns/rows with your keyboard.
 === Restarting i3 inplace
 
 To restart i3 inplace (and thus get into a clean state if there is a bug, or
-to upgrade to a newer version of i3) you can use +mod+Shift+r+.
+to upgrade to a newer version of i3) you can use +$mod+Shift+r+.
 
 === Exiting i3
 
-To cleanly exit i3 without killing your X server, you can use +mod+Shift+e+.
+To cleanly exit i3 without killing your X server, you can use +$mod+Shift+e+.
 
 === Floating
 
@@ -162,7 +162,7 @@ paradigm but can be useful for some corner cases like "Save as" dialog
 windows, or toolbar windows (GIMP or similar). Those windows usually set the
 appropriate hint and are opened in floating mode by default.
 
-You can toggle floating mode for a window by pressing +mod+Shift+Space+. By
+You can toggle floating mode for a window by pressing +$mod+Shift+Space+. By
 dragging the window’s titlebar with your mouse you can move the window
 around. By grabbing the borders and moving them you can resize the window. You
 can also do that by using the <<floating_modifier>>.
@@ -202,7 +202,7 @@ orientation (horizontal, vertical or unspecified) and the orientation depends
 on the layout the container is in (vertical for splitv and stacking, horizontal
 for splith and tabbed). So, in our example with the workspace, the default
 layout of the workspace +Container+ is splith (most monitors are widescreen
-nowadays). If you change the layout to splitv (+mod+l+ in the default config)
+nowadays). If you change the layout to splitv (+$mod+l+ in the default config)
 and *then* open two terminals, i3 will configure your windows like this:
 
 image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"]
@@ -212,8 +212,8 @@ Let’s assume you have two terminals on a workspace (with splith layout, that i
 horizontal orientation), focus is on the right terminal. Now you want to open
 another terminal window below the current one. If you would just open a new
 terminal window, it would show up to the right due to the splith layout.
-Instead, press +mod+v+ to split the container with the splitv layout (to
-open a +Horizontal Split Container+, use +mod+h+). Now you can open a new
+Instead, press +$mod+v+ to split the container with the splitv layout (to
+open a +Horizontal Split Container+, use +$mod+h+). Now you can open a new
 terminal and it will open below the current one:
 
 image::tree-layout1.png["Layout",float="right"]
@@ -248,7 +248,7 @@ single workspace on which you open three terminal windows. All these terminal
 windows are directly attached to one node inside i3’s layout tree, the
 workspace node. By default, the workspace node’s orientation is +horizontal+.
 
-Now you move one of these terminals down (+mod+k+ by default). The workspace
+Now you move one of these terminals down (+$mod+k+ by default). The workspace
 node’s orientation will be changed to +vertical+. The terminal window you moved
 down is directly attached to the workspace and appears on the bottom of the
 screen. A new (horizontal) container was created to accomodate the other two
@@ -362,10 +362,10 @@ bindcode [--release] [Modifiers+]keycode command
 *Examples*:
 --------------------------------
 # Fullscreen
-bindsym mod+f fullscreen
+bindsym $mod+f fullscreen
 
 # Restart
-bindsym mod+Shift+r restart
+bindsym $mod+Shift+r restart
 
 # Notebook-specific hotkeys
 bindcode 214 exec --no-startup-id /home/michael/toggle_beamer.sh
@@ -483,14 +483,26 @@ This option determines which border style new windows will have. The default is
 
 *Syntax*:
 ---------------------------------------------
-new_window <normal|1pixel|none>
+new_window <normal|1pixel|none|pixel>
 ---------------------------------------------
-
 *Example*:
 ---------------------
 new_window 1pixel
 ---------------------
 
+The "normal" and "pixel" border styles support an optional border width in
+pixels:
+
+*Example*:
+---------------------
+# The same as new_window none
+new_window pixel 0
+
+# A 3 px border
+new_window pixel 3
+---------------------
+
+
 === Hiding vertical borders
 
 You can hide vertical borders adjacent to the screen edges using
@@ -790,21 +802,23 @@ focus_follows_mouse no
 When you are in fullscreen mode, some applications still open popup windows
 (take Xpdf for example). This is because these applications may not be aware
 that they are in fullscreen mode (they do not check the corresponding hint).
-There are two things which are possible to do in this situation:
+There are three things which are possible to do in this situation:
 
-1. Just ignore the popup (don’t map it). This won’t interrupt you while you are
+1. Display the popup if it belongs to the fullscreen application only. This is
+   the default and should be reasonable behavior for most users.
+2. Just ignore the popup (don’t map it). This won’t interrupt you while you are
    in fullscreen. However, some apps might react badly to this (deadlock until
    you go out of fullscreen).
-2. Leave fullscreen mode. This is the default.
+3. Leave fullscreen mode.
 
 *Syntax*:
 -------------------------------------------------
-popup_during_fullscreen <ignore|leave_fullscreen>
+popup_during_fullscreen <smart|ignore|leave_fullscreen>
 -------------------------------------------------
 
 *Example*:
 ------------------------------
-popup_during_fullscreen ignore
+popup_during_fullscreen smart
 ------------------------------
 
 === Focus wrapping
@@ -862,7 +876,7 @@ This configuration directive enables automatic +workspace back_and_forth+ (see
 
 For instance: Assume you are on workspace "1: www" and switch to "2: IM" using
 mod+2 because somebody sent you a message. You don’t need to remember where you
-came from now, you can just press mod+2 again to switch back to "1: www".
+came from now, you can just press $mod+2 again to switch back to "1: www".
 
 *Syntax*:
 --------------------------------------
@@ -874,6 +888,30 @@ workspace_auto_back_and_forth <yes|no>
 workspace_auto_back_and_forth yes
 ---------------------------------
 
+=== Delaying urgency hint reset on workspace change
+
+If an application on another workspace sets an urgency hint, switching to this
+workspace may lead to immediate focus of the application, which also means the
+window decoration color would be immediately resetted to +client.focused+. This
+may make it unnecessarily hard to tell which window originally raised the
+event.
+
+In order to prevent this, you can tell i3 to delay resetting the urgency state
+by a certain time using the +force_display_urgency_hint+ directive. Setting the
+value to 0 disables this feature.
+
+The default is 500ms.
+
+*Syntax*:
+---------------------------------------
+force_display_urgency_hint <timeout> ms
+---------------------------------------
+
+*Example*:
+---------------------------------
+force_display_urgency_hint 500 ms
+---------------------------------
+
 == Configuring i3bar
 
 The bar at the bottom of your monitor is drawn by a separate process called
@@ -1170,7 +1208,7 @@ the following keybinding:
 
 *Example*:
 --------------------------------------------------------
-bindsym mod+x move container to workspace 3; workspace 3
+bindsym $mod+x move container to workspace 3; workspace 3
 --------------------------------------------------------
 
 [[command_criteria]]
@@ -1182,10 +1220,10 @@ which have the class Firefox, use:
 
 *Example*:
 ------------------------------------
-bindsym mod+x [class="Firefox"] kill
+bindsym $mod+x [class="Firefox"] kill
 
 # same thing, but case-insensitive
-bindsym mod+x [class="(?i)firefox"] kill
+bindsym $mod+x [class="(?i)firefox"] kill
 ------------------------------------
 
 The criteria which are currently implemented are:
@@ -1231,10 +1269,10 @@ exec [--no-startup-id] command
 *Example*:
 ------------------------------
 # Start the GIMP
-bindsym mod+g exec gimp
+bindsym $mod+g exec gimp
 
 # Start the terminal emulator urxvt which is not yet startup-notification-aware
-bindsym mod+Return exec --no-startup-id urxvt
+bindsym $mod+Return exec --no-startup-id urxvt
 ------------------------------
 
 The +--no-startup-id+ parameter disables startup-notification support for this
@@ -1265,8 +1303,8 @@ split <vertical|horizontal>
 
 *Example*:
 ------------------------------
-bindsym mod+v split vertical
-bindsym mod+h split horizontal
+bindsym $mod+v split vertical
+bindsym $mod+h split horizontal
 ------------------------------
 
 === Manipulating layout
@@ -1287,21 +1325,21 @@ layout toggle [split|all]
 
 *Examples*:
 --------------
-bindsym mod+s layout stacking
-bindsym mod+l layout toggle split
-bindsym mod+w layout tabbed
+bindsym $mod+s layout stacking
+bindsym $mod+l layout toggle split
+bindsym $mod+w layout tabbed
 
 # Toggle between stacking/tabbed/split:
-bindsym mod+x layout toggle
+bindsym $mod+x layout toggle
 
 # Toggle between stacking/tabbed/splith/splitv:
-bindsym mod+x layout toggle all
+bindsym $mod+x layout toggle all
 
 # Toggle fullscreen
-bindsym mod+f fullscreen
+bindsym $mod+f fullscreen
 
 # Toggle floating/tiling
-bindsym mod+t floating toggle
+bindsym $mod+t floating toggle
 --------------
 
 === Focusing/Moving containers
@@ -1343,36 +1381,36 @@ relevant for floating containers. The default amount is 10 pixels.
 *Examples*:
 ----------------------
 # Focus container on the left, bottom, top, right:
-bindsym mod+j focus left
-bindsym mod+k focus down
-bindsym mod+l focus up
-bindsym mod+semicolon focus right
+bindsym $mod+j focus left
+bindsym $mod+k focus down
+bindsym $mod+l focus up
+bindsym $mod+semicolon focus right
 
 # Focus parent container
-bindsym mod+u focus parent
+bindsym $mod+u focus parent
 
 # Focus last floating/tiling container
-bindsym mod+g focus mode_toggle
+bindsym $mod+g focus mode_toggle
 
 # Focus the output right to the current one
-bindsym mod+x focus output right
+bindsym $mod+x focus output right
 
 # Focus the big output
-bindsym mod+x focus output HDMI-2
+bindsym $mod+x focus output HDMI-2
 
 # Move container to the left, bottom, top, right:
-bindsym mod+j move left
-bindsym mod+k move down
-bindsym mod+l move up
-bindsym mod+semicolon move right
+bindsym $mod+j move left
+bindsym $mod+k move down
+bindsym $mod+l move up
+bindsym $mod+semicolon move right
 
 # Move container, but make floating containers
 # move more than the default
-bindsym mod+j move left 20 px
+bindsym $mod+j move left 20 px
 
 # Move floating container to the center
 # of all outputs
-bindsym mod+c move absolute position center
+bindsym $mod+c move absolute position center
 ----------------------
 
 === Changing (named) workspaces/moving to workspaces
@@ -1395,38 +1433,40 @@ RandR output.
 
 [[back_and_forth]]
 To switch back to the previously focused workspace, use +workspace
-back_and_forth+.
+back_and_forth+; likewise, you can move containers to the previously focused
+workspace using +move container to workspace back_and_forth+.
 
 *Syntax*:
 -----------------------------------
 workspace <next|prev|next_on_output|prev_on_output>
 workspace back_and_forth
 workspace <name>
-workspace number <number>
+workspace number <name>
 
 move [window|container] [to] workspace <name>
-move [window|container] [to] workspace number <number>
+move [window|container] [to] workspace number <name>
 move [window|container] [to] workspace <prev|next|current>
 -----------------------------------
 
 *Examples*:
 -------------------------
-bindsym mod+1 workspace 1
-bindsym mod+2 workspace 2
+bindsym $mod+1 workspace 1
+bindsym $mod+2 workspace 2
 ...
 
-bindsym mod+Shift+1 move container to workspace 1
-bindsym mod+Shift+2 move container to workspace 2
+bindsym $mod+Shift+1 move container to workspace 1
+bindsym $mod+Shift+2 move container to workspace 2
 ...
 
 # switch between the current and the previously focused one
-bindsym mod+b workspace back_and_forth
+bindsym $mod+b workspace back_and_forth
+bindsym $mod+Shift+b move container to workspace back_and_forth
 
 # move the whole workspace to the next output
-bindsym mod+x move workspace to output right
+bindsym $mod+x move workspace to output right
 
 # move firefox to current workspace
-bindsym mod+F1 [class="Firefox"] move workspace current
+bindsym $mod+F1 [class="Firefox"] move workspace current
 -------------------------
 
 ==== Named workspaces
@@ -1436,7 +1476,7 @@ workspace command, you can use an arbitrary name:
 
 *Example*:
 -------------------------
-bindsym mod+1 workspace mail
+bindsym $mod+1 workspace mail
 ...
 -------------------------
 
@@ -1445,8 +1485,8 @@ number, like this:
 
 *Example*:
 -------------------------
-bindsym mod+1 workspace 1: mail
-bindsym mod+2 workspace 2: www
+bindsym $mod+1 workspace 1: mail
+bindsym $mod+2 workspace 2: www
 ...
 -------------------------
 
@@ -1456,7 +1496,8 @@ workspaces are ordered the way they appeared. When they start with a number, i3
 will order them numerically. Also, you will be able to use +workspace number 1+
 to switch to the workspace which begins with number 1, regardless of which name
 it has. This is useful in case you are changing the workspace’s name
-dynamically.
+dynamically. To combine both commands you can use +workspace number 1: mail+ to
+specify a default name if there's currently no workspace starting with a "1".
 
 ==== Renaming workspaces
 
@@ -1494,10 +1535,10 @@ move workspace to output <<left|right|down|up>|<output>>
 --------------------------------------------------------
 # Move the current workspace to the next output
 # (effectively toggles when you only have two outputs)
-bindsym mod+x move workspace to output right
+bindsym $mod+x move workspace to output right
 
 # Put this window on the presentation output.
-bindsym mod+x move container to output VGA1
+bindsym $mod+x move container to output VGA1
 --------------------------------------------------------
 
 [[resizingconfig]]
@@ -1548,7 +1589,7 @@ mode "resize" {
 }
 
 # Enter resize mode
-bindsym mod+r mode "resize"
+bindsym $mod+r mode "resize"
 ----------------------------------------------------------------------
 
 === Jumping to specific windows
@@ -1569,7 +1610,7 @@ with criteria for that.
 *Examples*:
 ------------------------------------------------
 # Get me to the next open VIM instance
-bindsym mod+a [class="urxvt" title="VIM"] focus
+bindsym $mod+a [class="urxvt" title="VIM"] focus
 ------------------------------------------------
 
 === VIM-like marks (mark/goto)
@@ -1605,10 +1646,10 @@ TODO: make i3-input replace %s
 *Examples*:
 ---------------------------------------
 # Read 1 character and mark the current window with this character
-bindsym mod+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
+bindsym $mod+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
 
 # Read 1 character and go to the window with the character
-bindsym mod+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
+bindsym $mod+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
 ---------------------------------------
 
 Alternatively, if you do not want to mess with +i3-input+, you could create
@@ -1625,9 +1666,9 @@ There is also +border toggle+ which will toggle the different border styles.
 
 *Examples*:
 ----------------------------
-bindsym mod+t border normal
-bindsym mod+y border 1pixel
-bindsym mod+u border none
+bindsym $mod+t border normal
+bindsym $mod+y border 1pixel
+bindsym $mod+u border none
 ----------------------------
 
 [[stack-limit]]
@@ -1672,9 +1713,9 @@ however you don’t need to (simply killing your X session is fine as well).
 
 *Examples*:
 ----------------------------
-bindsym mod+Shift+r restart
-bindsym mod+Shift+w reload
-bindsym mod+Shift+e exit
+bindsym $mod+Shift+r restart
+bindsym $mod+Shift+w reload
+bindsym $mod+Shift+e exit
 ----------------------------
 
 === Scratchpad
@@ -1685,7 +1726,9 @@ invisible until you show it again. There is no way to open that workspace.
 Instead, when using +scratchpad show+, the window will be shown again, as a
 floating window, centered on your current workspace (using +scratchpad show+ on
 a visible scratchpad window will make it hidden again, so you can have a
-keybinding to toggle).
+keybinding to toggle). Note that this is just a normal floating window, so if
+you want to "remove it from scratchpad", you can simple make it tiling again
+(+floating toggle+).
 
 As the name indicates, this is useful for having a window with your favorite
 editor always at hand. However, you can also use this for other permanently
@@ -1702,10 +1745,10 @@ scratchpad show
 *Examples*:
 ------------------------------------------------
 # Make the currently focused window a scratchpad
-bindsym mod+Shift+minus move scratchpad
+bindsym $mod+Shift+minus move scratchpad
 
 # Show the first scratchpad window
-bindsym mod+minus scratchpad show
+bindsym $mod+minus scratchpad show
 
 # Show the sup-mail scratchpad window, if any.
 bindsym mod4+s [title="^Sup ::"] scratchpad show
@@ -1818,6 +1861,8 @@ have more than one monitor:
 3. If you have many workspaces on many monitors, it might get hard to keep
    track of which window you put where. Thus, you can use vim-like marks to
    quickly switch between windows. See <<vim_like_marks>>.
+4. For information on how to move existing workspaces between monitors,
+   see <<_moving_containers_workspaces_to_randr_outputs>>.
 
 == i3 and the rest of your software world
 
index 01cbe462ec64d05c7a51c2d80da8e7b710c5820c..66e44b6cd83a020924d7e7ae327d6f6eaa7e9831 100755 (executable)
 use strict;
 use warnings;
 use Data::Dumper;
+use Getopt::Long;
 use v5.10;
 
+my $input = '';
+my $prefix = '';
+my $result = GetOptions(
+    'input=s' => \$input,
+    'prefix=s' => \$prefix
+);
+
+die qq|Input file "$input" does not exist!| unless -e $input;
+
 # reads in a whole file
 sub slurp {
     open my $fh, '<', shift;
@@ -24,8 +34,6 @@ sub slurp {
 # Stores the different states.
 my %states;
 
-# XXX: don’t hardcode input and output
-my $input = '../parser-specs/commands.spec';
 my @raw_lines = split("\n", slurp($input));
 my @lines;
 
@@ -101,24 +109,30 @@ for my $line (@lines) {
 # Second step: Generate the enum values for all states.
 
 # It is important to keep the order the same, so we store the keys once.
-my @keys = keys %states;
+# We sort descendingly by length to be able to replace occurences of the state
+# name even when one state’s name is included in another one’s (like FOR_WINDOW
+# is in FOR_WINDOW_COMMAND).
+my @keys = sort { length($b) <=> length($a) } keys %states;
 
-open(my $enumfh, '>', 'GENERATED_enums.h');
+open(my $enumfh, '>', "GENERATED_${prefix}_enums.h");
 
 # XXX: we might want to have a way to do this without a trailing comma, but gcc
 # seems to eat it.
+my %statenum;
 say $enumfh 'typedef enum {';
 my $cnt = 0;
 for my $state (@keys, '__CALL') {
     say $enumfh "    $state = $cnt,";
+    $statenum{$state} = $cnt;
     $cnt++;
 }
 say $enumfh '} cmdp_state;';
 close($enumfh);
 
 # Third step: Generate the call function.
-open(my $callfh, '>', 'GENERATED_call.h');
-say $callfh 'static void GENERATED_call(const int call_identifier, struct CommandResult *result) {';
+open(my $callfh, '>', "GENERATED_${prefix}_call.h");
+my $resultname = uc(substr($prefix, 0, 1)) . substr($prefix, 1) . 'Result';
+say $callfh "static void GENERATED_call(const int call_identifier, struct $resultname *result) {";
 say $callfh '    switch (call_identifier) {';
 my $call_id = 0;
 for my $state (@keys) {
@@ -132,13 +146,23 @@ for my $state (@keys) {
         $next_state ||= 'INITIAL';
         my $fmt = $cmd;
         # Replace the references to identified literals (like $workspace) with
-        # calls to get_string().
+        # calls to get_string(). Also replaces state names (like FOR_WINDOW)
+        # with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.).
+        $cmd =~ s/$_/$statenum{$_}/g for @keys;
         $cmd =~ s/\$([a-z_]+)/get_string("$1")/g;
-        # Used only for debugging/testing.
+        $cmd =~ s/\&([a-z_]+)/get_long("$1")/g;
+        # For debugging/testing, we print the call using printf() and thus need
+        # to generate a format string. The format uses %d for <number>s,
+        # literal numbers or state IDs and %s for NULL, <string>s and literal
+        # strings.
+        $fmt =~ s/$_/%d/g for @keys;
         $fmt =~ s/\$([a-z_]+)/%s/g;
+        $fmt =~ s/\&([a-z_]+)/%ld/g;
         $fmt =~ s/"([a-z0-9_]+)"/%s/g;
+        $fmt =~ s/(?:-?|\b)[0-9]+\b/%d/g;
 
         say $callfh "         case $call_id:";
+        say $callfh "             result->next_state = $next_state;";
         say $callfh '#ifndef TEST_PARSER';
         my $real_cmd = $cmd;
         if ($real_cmd =~ /\(\)/) {
@@ -152,9 +176,14 @@ for my $state (@keys) {
         $cmd =~ s/[^(]+\(//;
         $cmd =~ s/\)$//;
         $cmd = ", $cmd" if length($cmd) > 0;
+        $cmd =~ s/, NULL//g;
         say $callfh qq|           fprintf(stderr, "$fmt\\n"$cmd);|;
+        # The cfg_criteria functions have side-effects which are important for
+        # testing. They are implemented as stubs in the test parser code.
+        if ($real_cmd =~ /^cfg_criteria/) {
+            say $callfh qq|       $real_cmd;|;
+        }
         say $callfh '#endif';
-        say $callfh "             state = $next_state;";
         say $callfh "             break;";
         $token->{next_state} = "call $call_id";
         $call_id++;
@@ -162,17 +191,19 @@ for my $state (@keys) {
 }
 say $callfh '        default:';
 say $callfh '            printf("BUG in the parser. state = %d\n", call_identifier);';
+say $callfh '            assert(false);';
 say $callfh '    }';
+say $callfh '    state = result->next_state;';
 say $callfh '}';
 close($callfh);
 
 # Fourth step: Generate the token datastructures.
 
-open(my $tokfh, '>', 'GENERATED_tokens.h');
+open(my $tokfh, '>', "GENERATED_${prefix}_tokens.h");
 
 for my $state (@keys) {
     my $tokens = $states{$state};
-    say $tokfh 'cmdp_token tokens_' . $state . '[' . scalar @$tokens . '] = {';
+    say $tokfh 'static cmdp_token tokens_' . $state . '[' . scalar @$tokens . '] = {';
     for my $token (@$tokens) {
         my $call_identifier = 0;
         my $token_name = $token->{token};
@@ -192,7 +223,7 @@ for my $state (@keys) {
     say $tokfh '};';
 }
 
-say $tokfh 'cmdp_token_ptr tokens[' . scalar @keys . '] = {';
+say $tokfh 'static cmdp_token_ptr tokens[' . scalar @keys . '] = {';
 for my $state (@keys) {
     my $tokens = $states{$state};
     say $tokfh '    { tokens_' . $state . ', ' . scalar @$tokens . ' },';
index 4ed182c4af900ae450ef28e588a2423264395e74..372ed161c775461bf25cffe0535e774194de0eb1 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef _XCB_H
-#define _XCB_H
+#ifndef I3_XCB_H
+#define I3_XCB_H
 
 /* from X11/keysymdef.h */
 #define XCB_NUM_LOCK                    0xff7f
index f494cbd56704c260c4806e486a17fd301948c4b8..f1d5f077f4cf421d184aae3f6fb897bba8a5fafd 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef _I3_INPUT
-#define _I3_INPUT
+#ifndef I3_INPUT
+#define I3_INPUT
 
 #include <err.h>
 
index 5a21226bb532a8f9681bc328a7fd2342163a8c94..379a7f6f7e4b67a49f0a1c0652234447cc258bc1 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef _I3_NAGBAR
-#define _I3_NAGBAR
+#ifndef I3_NAGBAR
+#define I3_NAGBAR
 
 #include <err.h>
 
index 162660d3342e83ea7b33b5effa9e4ebb7b9bf1c9..890afcb73d07174323bb7e049ae2f7aced230d04 100644 (file)
@@ -79,7 +79,7 @@ bindcode $mod+65 focus mode_toggle
 bindcode $mod+38 focus parent
 
 # focus the child container
-#bindcode $mod+d focus child
+#bindsym $mod+d focus child
 
 # switch to workspace
 bindcode $mod+10 workspace 1
index c0b56a013aba1fb1efee566ed6e583bf421591c9..d1c46890bc28e6534fdebf7987da54572c1e5bc0 100644 (file)
 #ifndef CHILD_H_
 #define CHILD_H_
 
+#include <stdbool.h>
+
 #define STDIN_CHUNK_SIZE 1024
 
+typedef struct {
+    pid_t pid;
+
+    /**
+     * The version number is an uint32_t to avoid machines with different sizes of
+     * 'int' to allow different values here. It’s highly unlikely we ever exceed
+     * even an int8_t, but still…
+     */
+    uint32_t version;
+
+    bool stopped;
+    /**
+     * The signal requested by the client to inform it of the hidden state of i3bar
+     */
+    int stop_signal;
+    /**
+     * The signal requested by the client to inform it of theun hidden state of i3bar
+     */
+    int cont_signal;
+} i3bar_child;
+
 /*
  * Start a child-process with the specified command and reroute stdin.
  * We actually start a $SHELL to execute the command so we don't have to care
index 6f8a7b2db8ae87b378e34ce19160e8a29c686ed6..e2582a02053bab03caacbc486774a47a27166cfa 100644 (file)
@@ -34,6 +34,8 @@ struct status_block {
 
     char *color;
 
+    bool urgent;
+
     /* The amount of pixels necessary to render this block. This variable is
      * only temporarily used in refresh_statusline(). */
     uint32_t width;
@@ -52,6 +54,6 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head;
 #include "xcb.h"
 #include "config.h"
 #include "libi3.h"
-#include "determine_json_version.h"
+#include "parse_json_header.h"
 
 #endif
diff --git a/i3bar/include/determine_json_version.h b/i3bar/include/determine_json_version.h
deleted file mode 100644 (file)
index 52c6f5d..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
- *
- * determine_json_version.c: Determines the JSON protocol version based on the
- *                           first line of input from a child program.
- *
- */
-#ifndef DETERMINE_JSON_VERSION_H_
-#define DETERMINE_JSON_VERSION_H_
-
-#include <stdint.h>
-
-/*
- * Determines the JSON i3bar protocol version from the given buffer. In case
- * the buffer does not contain valid JSON, or no version field is found, this
- * function returns -1. The amount of bytes consumed by parsing the header is
- * returned in *consumed (if non-NULL).
- *
- * The return type is an int32_t to avoid machines with different sizes of
- * 'int' to allow different values here. It’s highly unlikely we ever exceed
- * even an int8_t, but still…
- *
- */
-int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed);
-
-#endif
diff --git a/i3bar/include/parse_json_header.h b/i3bar/include/parse_json_header.h
new file mode 100644 (file)
index 0000000..79efddc
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ *
+ * parse_json_header.c: Parse the JSON protocol header to determine
+ *                      protocol version and features.
+ *
+ */
+#ifndef PARSE_JSON_HEADER_H_
+#define PARSE_JSON_HEADER_H_
+
+#include <stdint.h>
+
+/**
+ * Parse the JSON protocol header to determine protocol version and features.
+ * In case the buffer does not contain a valid header (invalid JSON, or no
+ * version field found), the 'correct' field of the returned header is set to
+ * false. The amount of bytes consumed by parsing the header is returned in
+ * *consumed (if non-NULL).
+ *
+ */
+void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed);
+
+#endif
index 43c56c58d3e93794d6f56becc86ae128a73d84bb..6ae97815384049ec87d95ec94fd6788c19b648d4 100644 (file)
@@ -16,6 +16,8 @@
 #undef MIN
 #define MIN(x,y) ((x) < (y) ? (x) : (y))
 
+#define STARTS_WITH(string, len, needle) ((len >= strlen(needle)) && strncasecmp(string, needle, strlen(needle)) == 0)
+
 /* Securely free p */
 #define FREE(p) do { \
     if (p != NULL) { \
index 6c7bc567e4b07d7385667f7476cfa91980f2f922..dcc4d78116e9a6b1f9a4ca954acb251e2d65e153 100644 (file)
@@ -110,7 +110,7 @@ void reconfig_windows(void);
  * Render the bars, with buttons and statusline
  *
  */
-void draw_bars(void);
+void draw_bars(bool force_unhide);
 
 /*
  * Redraw the bars, i.e. simply copy the buffer to the barwindow
index 058ddb7a1f31c0518b20ae4afc6e38a6c65db85b..9a89d3c63eb608bf977138c04ecef25813d5b58d 100644 (file)
 #include "common.h"
 
 /* Global variables for child_*() */
-pid_t child_pid;
+i3bar_child child = { 0 };
 
 /* stdin- and sigchild-watchers */
 ev_io    *stdin_io;
 ev_child *child_sig;
 
 /* JSON parser for stdin */
-bool first_line = true;
-bool plaintext = false;
 yajl_callbacks callbacks;
 yajl_handle parser;
 
 typedef struct parser_ctx {
+    /* True if one of the parsed blocks was urgent */
+    bool has_urgent;
+
     /* A copy of the last JSON map key. */
     char *last_map_key;
 
@@ -69,6 +70,8 @@ void cleanup(void) {
         ev_child_stop(main_loop, child_sig);
         FREE(child_sig);
     }
+
+    memset(&child, 0, sizeof(i3bar_child));
 }
 
 /*
@@ -109,6 +112,14 @@ static int stdin_map_key(void *context, const unsigned char *key, unsigned int l
     return 1;
 }
 
+static int stdin_boolean(void *context, int val) {
+    parser_ctx *ctx = context;
+    if (strcasecmp(ctx->last_map_key, "urgent") == 0) {
+        ctx->block.urgent = val;
+    }
+    return 1;
+}
+
 #if YAJL_MAJOR >= 2
 static int stdin_string(void *context, const unsigned char *val, size_t len) {
 #else
@@ -132,6 +143,8 @@ static int stdin_end_map(void *context) {
      * i3bar doesn’t crash and the user gets an annoying message. */
     if (!new_block->full_text)
         new_block->full_text = i3string_from_utf8("SPEC VIOLATION (null)");
+    if (new_block->urgent)
+        ctx->has_urgent = true;
     TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
     return 1;
 }
@@ -148,11 +161,10 @@ static int stdin_end_array(void *context) {
 }
 
 /*
- * Callbalk for stdin. We read a line from stdin and store the result
- * in statusline
+ * Helper function to read stdin
  *
  */
-void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
     int fd = watcher->fd;
     int n = 0;
     int rec = 0;
@@ -173,8 +185,9 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
             /* end of file, kill the watcher */
             ELOG("stdin: received EOF\n");
             cleanup();
-            draw_bars();
-            return;
+            draw_bars(false);
+            *ret_buffer_len = -1;
+            return NULL;
         }
         rec += n;
 
@@ -185,51 +198,94 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
     }
     if (*buffer == '\0') {
         FREE(buffer);
-        return;
+        rec = -1;
     }
+    *ret_buffer_len = rec;
+    return buffer;
+}
 
-    unsigned char *json_input = buffer;
-    if (first_line) {
-        DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
-        /* Detect whether this is JSON or plain text. */
-        unsigned int consumed = 0;
-        /* At the moment, we don’t care for the version. This might change
-         * in the future, but for now, we just discard it. */
-        plaintext = (determine_json_version(buffer, buffer_len, &consumed) == -1);
-        if (plaintext) {
-            /* In case of plaintext, we just add a single block and change its
-             * full_text pointer later. */
-            struct status_block *new_block = scalloc(sizeof(struct status_block));
-            TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
-        } else {
-            json_input += consumed;
-            rec -= consumed;
-        }
-        first_line = false;
-    }
-    if (!plaintext) {
-        yajl_status status = yajl_parse(parser, json_input, rec);
+static void read_flat_input(char *buffer, int length) {
+    struct status_block *first = TAILQ_FIRST(&statusline_head);
+    /* Clear the old buffer if any. */
+    I3STRING_FREE(first->full_text);
+    /* Remove the trailing newline and terminate the string at the same
+     * time. */
+    if (buffer[length-1] == '\n' || buffer[length-1] == '\r')
+        buffer[length-1] = '\0';
+    else buffer[length] = '\0';
+    first->full_text = i3string_from_utf8(buffer);
+}
+
+static bool read_json_input(unsigned char *input, int length) {
+    yajl_status status = yajl_parse(parser, input, length);
+    bool has_urgent = false;
 #if YAJL_MAJOR >= 2
-        if (status != yajl_status_ok) {
+    if (status != yajl_status_ok) {
 #else
-        if (status != yajl_status_ok && status != yajl_status_insufficient_data) {
+    if (status != yajl_status_ok && status != yajl_status_insufficient_data) {
 #endif
-            fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n",
-                    status, rec, json_input);
+        fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n",
+                status, length, input);
+    } else if (parser_context.has_urgent) {
+        has_urgent = true;
+    }
+    return has_urgent;
+}
+
+/*
+ * Callbalk for stdin. We read a line from stdin and store the result
+ * in statusline
+ *
+ */
+void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+    int rec;
+    unsigned char *buffer = get_buffer(watcher, &rec);
+    if (buffer == NULL)
+        return;
+    bool has_urgent = false;
+    if (child.version > 0) {
+        has_urgent = read_json_input(buffer, rec);
+    } else {
+        read_flat_input((char*)buffer, rec);
+    }
+    free(buffer);
+    draw_bars(has_urgent);
+}
+
+/*
+ * Callbalk for stdin first line. We read the first line to detect
+ * whether this is JSON or plain text
+ *
+ */
+void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+    int rec;
+    unsigned char *buffer = get_buffer(watcher, &rec);
+    if (buffer == NULL)
+        return;
+    DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
+    /* Detect whether this is JSON or plain text. */
+    unsigned int consumed = 0;
+    /* At the moment, we don’t care for the version. This might change
+     * in the future, but for now, we just discard it. */
+    parse_json_header(&child, buffer, rec, &consumed);
+    if (child.version > 0) {
+        /* If hide-on-modifier is set, we start of by sending the
+         * child a SIGSTOP, because the bars aren't mapped at start */
+        if (config.hide_on_modifier) {
+            stop_child();
         }
+        read_json_input(buffer + consumed, rec - consumed);
     } else {
-        struct status_block *first = TAILQ_FIRST(&statusline_head);
-        /* Clear the old buffer if any. */
-        I3STRING_FREE(first->full_text);
-        /* Remove the trailing newline and terminate the string at the same
-         * time. */
-        if (buffer[rec-1] == '\n' || buffer[rec-1] == '\r')
-            buffer[rec-1] = '\0';
-        else buffer[rec] = '\0';
-        first->full_text = i3string_from_utf8((const char *)buffer);
+        /* In case of plaintext, we just add a single block and change its
+         * full_text pointer later. */
+        struct status_block *new_block = scalloc(sizeof(struct status_block));
+        TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
+        read_flat_input((char*)buffer, rec);
     }
     free(buffer);
-    draw_bars();
+    ev_io_stop(main_loop, stdin_io);
+    ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ);
+    ev_io_start(main_loop, stdin_io);
 }
 
 /*
@@ -240,7 +296,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
  */
 void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
     ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
-           child_pid,
+           child.pid,
            watcher->rstatus);
     cleanup();
 }
@@ -255,6 +311,7 @@ void start_child(char *command) {
     /* Allocate a yajl parser which will be used to parse stdin. */
     memset(&callbacks, '\0', sizeof(yajl_callbacks));
     callbacks.yajl_map_key = stdin_map_key;
+    callbacks.yajl_boolean = stdin_boolean;
     callbacks.yajl_string = stdin_string;
     callbacks.yajl_start_array = stdin_start_array;
     callbacks.yajl_end_array = stdin_end_array;
@@ -268,14 +325,13 @@ void start_child(char *command) {
     parser = yajl_alloc(&callbacks, NULL, &parser_context);
 #endif
 
-    child_pid = 0;
     if (command != NULL) {
         int fd[2];
         if (pipe(fd) == -1)
             err(EXIT_FAILURE, "pipe(fd)");
 
-        child_pid = fork();
-        switch (child_pid) {
+        child.pid = fork();
+        switch (child.pid) {
             case -1:
                 ELOG("Couldn't fork(): %s\n", strerror(errno));
                 exit(EXIT_FAILURE);
@@ -298,12 +354,6 @@ void start_child(char *command) {
 
                 dup2(fd[0], STDIN_FILENO);
 
-                /* If hide-on-modifier is set, we start of by sending the
-                 * child a SIGSTOP, because the bars aren't mapped at start */
-                if (config.hide_on_modifier) {
-                    stop_child();
-                }
-
                 break;
         }
     }
@@ -312,12 +362,12 @@ void start_child(char *command) {
     fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
 
     stdin_io = smalloc(sizeof(ev_io));
-    ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ);
+    ev_io_init(stdin_io, &stdin_io_first_line_cb, STDIN_FILENO, EV_READ);
     ev_io_start(main_loop, stdin_io);
 
     /* We must cleanup, if the child unexpectedly terminates */
     child_sig = smalloc(sizeof(ev_child));
-    ev_child_init(child_sig, &child_sig_cb, child_pid, 0);
+    ev_child_init(child_sig, &child_sig_cb, child.pid, 0);
     ev_child_start(main_loop, child_sig);
 
     atexit(kill_child_at_exit);
@@ -328,9 +378,10 @@ void start_child(char *command) {
  *
  */
 void kill_child_at_exit(void) {
-    if (child_pid != 0) {
-        kill(child_pid, SIGCONT);
-        kill(child_pid, SIGTERM);
+    if (child.pid > 0) {
+        if (child.cont_signal > 0 && child.stopped)
+            kill(child.pid, child.cont_signal);
+        kill(child.pid, SIGTERM);
     }
 }
 
@@ -340,12 +391,12 @@ void kill_child_at_exit(void) {
  *
  */
 void kill_child(void) {
-    if (child_pid != 0) {
-        kill(child_pid, SIGCONT);
-        kill(child_pid, SIGTERM);
+    if (child.pid > 0) {
+        if (child.cont_signal > 0 && child.stopped)
+            kill(child.pid, child.cont_signal);
+        kill(child.pid, SIGTERM);
         int status;
-        waitpid(child_pid, &status, 0);
-        child_pid = 0;
+        waitpid(child.pid, &status, 0);
         cleanup();
     }
 }
@@ -355,8 +406,9 @@ void kill_child(void) {
  *
  */
 void stop_child(void) {
-    if (child_pid != 0) {
-        kill(child_pid, SIGSTOP);
+    if (child.stop_signal > 0 && !child.stopped) {
+        child.stopped = true;
+        kill(child.pid, child.stop_signal);
     }
 }
 
@@ -365,7 +417,8 @@ void stop_child(void) {
  *
  */
 void cont_child(void) {
-    if (child_pid != 0) {
-        kill(child_pid, SIGCONT);
+    if (child.cont_signal > 0 && child.stopped) {
+        child.stopped = false;
+        kill(child.pid, child.cont_signal);
     }
 }
diff --git a/i3bar/src/determine_json_version.c b/i3bar/src/determine_json_version.c
deleted file mode 100644 (file)
index abd4303..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
- *
- * determine_json_version.c: Determines the JSON protocol version based on the
- *                           first line of input from a child program.
- *
- */
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <string.h>
-#include <errno.h>
-#include <err.h>
-#include <ev.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <yajl/yajl_common.h>
-#include <yajl/yajl_parse.h>
-#include <yajl/yajl_version.h>
-
-static bool version_key;
-static int32_t version_number;
-
-#if YAJL_MAJOR >= 2
-static int version_integer(void *ctx, long long val) {
-#else
-static int version_integer(void *ctx, long val) {
-#endif
-    if (version_key)
-        version_number = (uint32_t)val;
-    return 1;
-}
-
-#if YAJL_MAJOR >= 2
-static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
-#else
-static int version_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) {
-#endif
-    version_key = (stringlen == strlen("version") &&
-                   strncmp((const char*)stringval, "version", strlen("version")) == 0);
-    return 1;
-}
-
-static yajl_callbacks version_callbacks = {
-    NULL, /* null */
-    NULL, /* boolean */
-    &version_integer,
-    NULL, /* double */
-    NULL, /* number */
-    NULL, /* string */
-    NULL, /* start_map */
-    &version_map_key,
-    NULL, /* end_map */
-    NULL, /* start_array */
-    NULL /* end_array */
-};
-
-/*
- * Determines the JSON i3bar protocol version from the given buffer. In case
- * the buffer does not contain valid JSON, or no version field is found, this
- * function returns -1. The amount of bytes consumed by parsing the header is
- * returned in *consumed (if non-NULL).
- *
- * The return type is an int32_t to avoid machines with different sizes of
- * 'int' to allow different values here. It’s highly unlikely we ever exceed
- * even an int8_t, but still…
- *
- */
-int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed) {
-#if YAJL_MAJOR >= 2
-    yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL);
-    /* Allow trailing garbage. yajl 1 always behaves that way anyways, but for
-     * yajl 2, we need to be explicit. */
-    yajl_config(handle, yajl_allow_trailing_garbage, 1);
-#else
-    yajl_parser_config parse_conf = { 0, 0 };
-
-    yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, NULL);
-#endif
-
-    version_key = false;
-    version_number = -1;
-
-    yajl_status state = yajl_parse(handle, buffer, length);
-    if (state != yajl_status_ok) {
-        version_number = -1;
-        if (consumed != NULL)
-            *consumed = 0;
-    } else {
-        if (consumed != NULL)
-            *consumed = yajl_get_bytes_consumed(handle);
-    }
-
-    yajl_free(handle);
-
-    return version_number;
-}
index 2cc80cf7fac320cdf60af546e94331c6abe56d92..fc8c6492d9b9759211142aa7450740ddf1225e5d 100644 (file)
@@ -42,7 +42,7 @@ void got_command_reply(char *reply) {
 void got_workspace_reply(char *reply) {
     DLOG("Got Workspace-Data!\n");
     parse_workspaces_json(reply);
-    draw_bars();
+    draw_bars(false);
 }
 
 /*
@@ -71,7 +71,7 @@ void got_output_reply(char *reply) {
         kick_tray_clients(o_walk);
     }
 
-    draw_bars();
+    draw_bars(false);
 }
 
 /*
diff --git a/i3bar/src/parse_json_header.c b/i3bar/src/parse_json_header.c
new file mode 100644 (file)
index 0000000..80ec5af
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ *
+ * parse_json_header.c: Parse the JSON protocol header to determine
+ *                      protocol version and features.
+ *
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+#include <ev.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <yajl/yajl_common.h>
+#include <yajl/yajl_parse.h>
+#include <yajl/yajl_version.h>
+
+#include "common.h"
+
+static enum {
+    KEY_VERSION,
+    KEY_STOP_SIGNAL,
+    KEY_CONT_SIGNAL,
+    NO_KEY
+} current_key;
+
+#if YAJL_MAJOR >= 2
+static int header_integer(void *ctx, long long val) {
+#else
+static int header_integer(void *ctx, long val) {
+#endif
+    i3bar_child *child = ctx;
+
+    switch (current_key) {
+        case KEY_VERSION:
+            child->version = val;
+            break;
+        case KEY_STOP_SIGNAL:
+            child->stop_signal = val;
+            break;
+        case KEY_CONT_SIGNAL:
+            child->cont_signal = val;
+            break;
+        default:
+            break;
+    }
+    return 1;
+}
+
+#define CHECK_KEY(name) (stringlen == strlen(name) && \
+                         STARTS_WITH((const char*)stringval, stringlen, name))
+
+#if YAJL_MAJOR >= 2
+static int header_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
+#else
+static int header_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) {
+#endif
+    if (CHECK_KEY("version")) {
+        current_key = KEY_VERSION;
+    } else if (CHECK_KEY("stop_signal")) {
+        current_key = KEY_STOP_SIGNAL;
+    } else if (CHECK_KEY("cont_signal")) {
+        current_key = KEY_CONT_SIGNAL;
+    }
+    return 1;
+}
+
+static yajl_callbacks version_callbacks = {
+    NULL, /* null */
+    NULL, /* boolean */
+    &header_integer,
+    NULL, /* double */
+    NULL, /* number */
+    NULL, /* string */
+    NULL, /* start_map */
+    &header_map_key,
+    NULL, /* end_map */
+    NULL, /* start_array */
+    NULL /* end_array */
+};
+
+static void child_init(i3bar_child *child) {
+    child->version = 0;
+    child->stop_signal = SIGSTOP;
+    child->cont_signal = SIGCONT;
+}
+
+/*
+ * Parse the JSON protocol header to determine protocol version and features.
+ * In case the buffer does not contain a valid header (invalid JSON, or no
+ * version field found), the 'correct' field of the returned header is set to
+ * false. The amount of bytes consumed by parsing the header is returned in
+ * *consumed (if non-NULL).
+ *
+ */
+void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed) {
+    child_init(child);
+
+    current_key = NO_KEY;
+
+#if YAJL_MAJOR >= 2
+    yajl_handle handle = yajl_alloc(&version_callbacks, NULL, child);
+    /* Allow trailing garbage. yajl 1 always behaves that way anyways, but for
+     * yajl 2, we need to be explicit. */
+    yajl_config(handle, yajl_allow_trailing_garbage, 1);
+#else
+    yajl_parser_config parse_conf = { 0, 0 };
+
+    yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, child);
+#endif
+
+    yajl_status state = yajl_parse(handle, buffer, length);
+    if (state != yajl_status_ok) {
+        child_init(child);
+        if (consumed != NULL)
+            *consumed = 0;
+    } else {
+        if (consumed != NULL)
+            *consumed = yajl_get_bytes_consumed(handle);
+    }
+
+    yajl_free(handle);
+}
index 861925b96925f1aea7687dddd4eee8b97342c023..2c0d2a6a15e2e02ba7a83c836330706653add8a8 100644 (file)
@@ -302,16 +302,24 @@ void handle_button(xcb_button_press_event_t *event) {
             }
             break;
         case 4:
-            /* Mouse wheel down. We select the next ws */
-            if (cur_ws != TAILQ_FIRST(walk->workspaces)) {
-                cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
-            }
+            /* Mouse wheel up. We select the previous ws, if any.
+             * If there is no more workspace, don’t even send the workspace
+             * command, otherwise (with workspace auto_back_and_forth) we’d end
+             * up on the wrong workspace. */
+            if (cur_ws == TAILQ_FIRST(walk->workspaces))
+                return;
+
+            cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
             break;
         case 5:
-            /* Mouse wheel up. We select the previos ws */
-            if (cur_ws != TAILQ_LAST(walk->workspaces, ws_head)) {
-                cur_ws = TAILQ_NEXT(cur_ws, tailq);
-            }
+            /* Mouse wheel down. We select the next ws, if any.
+             * If there is no more workspace, don’t even send the workspace
+             * command, otherwise (with workspace auto_back_and_forth) we’d end
+             * up on the wrong workspace. */
+            if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head))
+                return;
+
+            cur_ws = TAILQ_NEXT(cur_ws, tailq);
             break;
     }
 
@@ -531,7 +539,7 @@ static void handle_client_message(xcb_client_message_event_t* event) {
             /* Trigger an update to copy the statusline text to the appropriate
              * position */
             configure_trayclients();
-            draw_bars();
+            draw_bars(false);
         }
     }
 }
@@ -559,7 +567,7 @@ static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
 
             /* Trigger an update, we now have more space for the statusline */
             configure_trayclients();
-            draw_bars();
+            draw_bars(false);
             return;
         }
     }
@@ -624,13 +632,13 @@ static void handle_property_notify(xcb_property_notify_event_t *event) {
             xcb_unmap_window(xcb_connection, trayclient->win);
             trayclient->mapped = map_it;
             configure_trayclients();
-            draw_bars();
+            draw_bars(false);
         } else if (!trayclient->mapped && map_it) {
             /* need to map the window */
             xcb_map_window(xcb_connection, trayclient->win);
             trayclient->mapped = map_it;
             configure_trayclients();
-            draw_bars();
+            draw_bars(false);
         }
         free(xembedr);
     }
@@ -1398,12 +1406,15 @@ void reconfig_windows(void) {
  * Render the bars, with buttons and statusline
  *
  */
-void draw_bars(void) {
+void draw_bars(bool unhide) {
     DLOG("Drawing Bars...\n");
     int i = 0;
 
     refresh_statusline();
 
+    static char *last_urgent_ws = NULL;
+    bool walks_away = true;
+
     i3_output *outputs_walk;
     SLIST_FOREACH(outputs_walk, outputs, slist) {
         if (!outputs_walk->active) {
@@ -1460,8 +1471,6 @@ void draw_bars(void) {
         }
 
         i3_ws *ws_walk;
-        static char *last_urgent_ws = NULL;
-        bool has_urgent = false, walks_away = true;
 
         TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
             DLOG("Drawing Button for WS %s at x = %d, len = %d\n", i3string_as_utf8(ws_walk->name), i, ws_walk->name_width);
@@ -1486,13 +1495,11 @@ void draw_bars(void) {
                 fg_color = colors.urgent_ws_fg;
                 bg_color = colors.urgent_ws_bg;
                 border_color = colors.urgent_ws_border;
-                has_urgent = true;
+                unhide = true;
                 if (!ws_walk->focused) {
                     FREE(last_urgent_ws);
                     last_urgent_ws = sstrdup(i3string_as_utf8(ws_walk->name));
                 }
-                /* The urgent-hint should get noticed, so we unhide the bars shortly */
-                unhide_bars();
             }
             uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
             uint32_t vals_border[] = { border_color, border_color };
@@ -1522,12 +1529,17 @@ void draw_bars(void) {
             i += 10 + ws_walk->name_width + 1;
         }
 
-        if (!has_urgent && !mod_pressed && walks_away) {
+        i = 0;
+    }
+
+    if (!mod_pressed) {
+        if (unhide) {
+            /* The urgent-hint should get noticed, so we unhide the bars shortly */
+            unhide_bars();
+        } else if (walks_away) {
             FREE(last_urgent_ws);
             hide_bars();
         }
-
-        i = 0;
     }
 
     redraw_bars();
index b83b9f4e07df2c876a7c0e216a25dd5988822bae..c9c4bbbe8acc3b478cdb65ac148a42ae4e540500 100644 (file)
@@ -10,8 +10,8 @@
  * compile-time.
  *
  */
-#ifndef _ALL_H
-#define _ALL_H
+#ifndef I3_ALL_H
+#define I3_ALL_H
 
 #include <assert.h>
 #include <stdbool.h>
@@ -79,6 +79,8 @@
 #include "scratchpad.h"
 #include "commands.h"
 #include "commands_parser.h"
+#include "config_directives.h"
+#include "config_parser.h"
 #include "fake_outputs.h"
 #include "display_version.h"
 
index f4ef8e88067afdb272fc1952d6101c272a37be2d..570375cf1874220a098f3212f9c0ac1faaecb6e9 100644 (file)
@@ -7,8 +7,8 @@
  * assignments.c: Assignments for specific windows (for_window).
  *
  */
-#ifndef _ASSIGNMENTS_H
-#define _ASSIGNMENTS_H
+#ifndef I3_ASSIGNMENTS_H
+#define I3_ASSIGNMENTS_H
 
 /**
  * Checks the list of assignments for the given window and runs all matching
index 6261613ba762c19dace78d9073f4f571d96a6f61..3c4d5288fdb8119a5e98086bdc1af15d1f625153 100644 (file)
@@ -7,8 +7,8 @@
  * click.c: Button press (mouse click) events.
  *
  */
-#ifndef _CLICK_H
-#define _CLICK_H
+#ifndef I3_CLICK_H
+#define I3_CLICK_H
 
 /**
  * The button press X callback. This function determines whether the floating
index d619b97c17932fe8966fb0b7f32c111b28df0926..4a87c39c8b266e73ca09002c3b1b4154d4738842 100644 (file)
@@ -7,8 +7,8 @@
  * cmdparse.y: the parser for commands you send to i3 (or bind on keys)
  *
  */
-#ifndef _CMDPARSE_H
-#define _CMDPARSE_H
+#ifndef I3_CMDPARSE_H
+#define I3_CMDPARSE_H
 
 char *parse_cmd(const char *new);
 
index 37ee98d9c6d797886b1500b5110b4da7300605c7..a517d83e09a04b71921993f3792c60325e0e0715 100644 (file)
@@ -7,26 +7,14 @@
  * commands.c: all command functions (see commands_parser.c)
  *
  */
-#ifndef _COMMANDS_H
-#define _COMMANDS_H
+#ifndef I3_COMMANDS_H
+#define I3_COMMANDS_H
 
 #include "commands_parser.h"
 
 /** The beginning of the prototype for every cmd_ function. */
 #define I3_CMD Match *current_match, struct CommandResult *cmd_output
 
-/*
- * Helper data structure for an operation window (window on which the operation
- * will be performed). Used to build the TAILQ owindows.
- *
- */
-typedef struct owindow {
-    Con *con;
-    TAILQ_ENTRY(owindow) owindows;
-} owindow;
-
-typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
-
 /**
  * Initializes the specified 'Match' data structure and the initial state of
  * commands.c for matching target windows of a command.
@@ -55,6 +43,12 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue);
  */
 void cmd_move_con_to_workspace(I3_CMD, char *which);
 
+/**
+ * Implementation of 'move [window|container] [to] workspace back_and_forth'.
+ *
+ */
+void cmd_move_con_to_workspace_back_and_forth(I3_CMD);
+
 /**
  * Implementation of 'move [window|container] [to] workspace <name>'.
  *
@@ -77,7 +71,7 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz
  * Implementation of 'border normal|none|1pixel|toggle'.
  *
  */
-void cmd_border(I3_CMD, char *border_style_str);
+void cmd_border(I3_CMD, char *border_style_str, char *border_width);
 
 /**
  * Implementation of 'nop <comment>'.
@@ -152,7 +146,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name);
 void cmd_split(I3_CMD, char *direction);
 
 /**
- * Implementaiton of 'kill [window|client]'.
+ * Implementation of 'kill [window|client]'.
  *
  */
 void cmd_kill(I3_CMD, char *kill_mode_str);
@@ -212,25 +206,25 @@ void cmd_layout(I3_CMD, char *layout_str);
 void cmd_layout_toggle(I3_CMD, char *toggle_mode);
 
 /**
- * Implementaiton of 'exit'.
+ * Implementation of 'exit'.
  *
  */
 void cmd_exit(I3_CMD);
 
 /**
- * Implementaiton of 'reload'.
+ * Implementation of 'reload'.
  *
  */
 void cmd_reload(I3_CMD);
 
 /**
- * Implementaiton of 'restart'.
+ * Implementation of 'restart'.
  *
  */
 void cmd_restart(I3_CMD);
 
 /**
- * Implementaiton of 'open'.
+ * Implementation of 'open'.
  *
  */
 void cmd_open(I3_CMD);
index 795cb0265715c44a5d41319a561d161682dc490f..fcc14ff58881f3ffbfb1096d9ff7b02afa673061 100644 (file)
@@ -7,8 +7,8 @@
  * commands.c: all command functions (see commands_parser.c)
  *
  */
-#ifndef _COMMANDS_PARSER_H
-#define _COMMANDS_PARSER_H
+#ifndef I3_COMMANDS_PARSER_H
+#define I3_COMMANDS_PARSER_H
 
 #include <yajl/yajl_gen.h>
 
@@ -27,6 +27,11 @@ struct CommandResult {
 
     /* Whether the command requires calling tree_render. */
     bool needs_tree_render;
+
+    /* The next state to transition to. Passed to the function so that we can
+     * determine the next state as a result of a function call, like
+     * cfg_criteria_pop_state() does. */
+    int next_state;
 };
 
 struct CommandResult *parse_command(const char *input);
index 20e83df935bdf7501ec0da3a8c7918245d8f36a1..5c104ebd687f045287732260803d03521c1bf5a3 100644 (file)
@@ -9,8 +9,8 @@
  *        …).
  *
  */
-#ifndef _CON_H
-#define _CON_H
+#ifndef I3_CON_H
+#define I3_CON_H
 
 /**
  * Create a new container (and attach it to the given parent, if not NULL).
@@ -33,6 +33,18 @@ void con_focus(Con *con);
  */
 bool con_is_leaf(Con *con);
 
+/*
+ * Returns true if a container should be considered split.
+ *
+ */
+bool con_is_split(Con *con);
+
+/**
+ * Returns true if this node has regular or floating children.
+ *
+ */
+bool con_has_children(Con *con);
+
 /**
  * Returns true if this node accepts a window (if the node swallows windows,
  * it might already have swallowed enough and cannot hold any more).
@@ -66,6 +78,12 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation);
  */
 Con *con_get_fullscreen_con(Con *con, int fullscreen_mode);
 
+/**
+ * Returns true if the container is internal, such as __i3_scratch
+ *
+ */
+bool con_is_internal(Con *con);
+
 /**
  * Returns true if the node is floating.
  *
@@ -244,7 +262,7 @@ int con_border_style(Con *con);
  * floating window.
  *
  */
-void con_set_border_style(Con *con, int border_style);
+void con_set_border_style(Con *con, int border_style, int border_width);
 
 /**
  * This function changes the layout of a given container. Use it to handle
@@ -293,4 +311,23 @@ Rect con_minimum_size(Con *con);
  */
 bool con_fullscreen_permits_focusing(Con *con);
 
+/**
+ * Checks if the given container has an urgent child.
+ *
+ */
+bool con_has_urgent_child(Con *con);
+
+/**
+ * Make all parent containers urgent if con is urgent or clear the urgent flag
+ * of all parent containers if there are no more urgent children left.
+ *
+ */
+void con_update_parents_urgency(Con *con);
+
+/**
+ * Create a string representing the subtree under con.
+ *
+ */
+char *con_get_tree_representation(Con *con);
+
 #endif
index 1a48016a386ef635d8232afda17638ba112f867c..04f1c85f65ded1e8b1e088afe6a9aa2c6e300489 100644 (file)
@@ -10,8 +10,8 @@
  * mode).
  *
  */
-#ifndef _CONFIG_H
-#define _CONFIG_H
+#ifndef I3_CONFIG_H
+#define I3_CONFIG_H
 
 #include <stdbool.h>
 #include "queue.h"
@@ -24,6 +24,8 @@ 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
@@ -98,6 +100,7 @@ struct Config {
     int default_layout;
     int container_stack_limit;
     int container_stack_limit_value;
+    int default_border_width;
 
     /** Default orientation for new containers */
     int default_orientation;
@@ -149,6 +152,13 @@ struct Config {
      * between two workspaces. */
     bool workspace_auto_back_and_forth;
 
+    /** By default, urgency is cleared immediately when switching to another
+     * workspace leads to focusing the con with the urgency hint. When having
+     * multiple windows on that workspace, the user needs to guess which
+     * application raised the event. To prevent this, the reset of the urgency
+     * flag can be delayed using an urgency timer. */
+    float workspace_urgency_timer;
+
     /** The default border style for new windows. */
     border_style_t default_border;
 
@@ -181,8 +191,15 @@ struct Config {
 
     /** What should happen when a new popup is opened during fullscreen mode */
     enum {
-        PDF_LEAVE_FULLSCREEN = 0,
-        PDF_IGNORE = 1
+        /* display (and focus) the popup when it belongs to the fullscreen
+         * window only. */
+        PDF_SMART = 0,
+
+        /* leave fullscreen mode unconditionally */
+        PDF_LEAVE_FULLSCREEN = 1,
+
+        /* just ignore the popup, that is, don’t map it */
+        PDF_IGNORE = 2,
     } popup_during_fullscreen;
 };
 
diff --git a/include/config_directives.h b/include/config_directives.h
new file mode 100644 (file)
index 0000000..1faaa97
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * config_directives.h: all config storing functions (see config_parser.c)
+ *
+ */
+#ifndef I3_CONFIG_DIRECTIVES_H
+#define I3_CONFIG_DIRECTIVES_H
+
+#include "config_parser.h"
+
+/** The beginning of the prototype for every cfg_ function. */
+#define I3_CFG Match *current_match, struct ConfigResult *result
+
+/* Defines a configuration function, that is, anything that can be called by
+ * using 'call cfg_foo()' in parser-specs/.*.spec. Useful so that we don’t need
+ * to repeat the definition all the time. */
+#define CFGFUN(name, ...) \
+    void cfg_ ## name (I3_CFG, ## __VA_ARGS__ )
+
+/* The following functions are called by the config parser, see
+ * parser-specs/config.spec. They get the parsed parameters and store them in
+ * our data structures, e.g. cfg_font gets a font name and stores it in
+ * config.font.
+ *
+ * Since they are so similar, individual comments were omitted. */
+
+CFGFUN(criteria_init, int _state);
+CFGFUN(criteria_add, const char *ctype, const char *cvalue);
+CFGFUN(criteria_pop_state);
+
+CFGFUN(font, const char *font);
+CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command);
+CFGFUN(for_window, const char *command);
+CFGFUN(floating_minimum_size, const long width, const long height);
+CFGFUN(floating_maximum_size, const long width, const long height);
+CFGFUN(default_orientation, const char *orientation);
+CFGFUN(workspace_layout, const char *layout);
+CFGFUN(workspace_back_and_forth, const char *value);
+CFGFUN(focus_follows_mouse, const char *value);
+CFGFUN(force_focus_wrapping, const char *value);
+CFGFUN(force_xinerama, const char *value);
+CFGFUN(fake_outputs, const char *outputs);
+CFGFUN(force_display_urgency_hint, const long duration_ms);
+CFGFUN(hide_edge_borders, const char *borders);
+CFGFUN(assign, const char *workspace);
+CFGFUN(ipc_socket, const char *path);
+CFGFUN(restart_state, const char *path);
+CFGFUN(popup_during_fullscreen, const char *value);
+CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator);
+CFGFUN(color_single, const char *colorclass, const char *color);
+CFGFUN(floating_modifier, const char *modifiers);
+CFGFUN(new_window, const char *windowtype, const char *border, const long width);
+CFGFUN(workspace, const char *workspace, const char *output);
+CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command);
+
+CFGFUN(enter_mode, const char *mode);
+CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command);
+
+CFGFUN(bar_font, const char *font);
+CFGFUN(bar_mode, const char *mode);
+CFGFUN(bar_output, const char *output);
+CFGFUN(bar_verbose, const char *verbose);
+CFGFUN(bar_modifier, const char *modifier);
+CFGFUN(bar_position, const char *position);
+CFGFUN(bar_i3bar_command, const char *i3bar_command);
+CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);
+CFGFUN(bar_socket_path, const char *socket_path);
+CFGFUN(bar_tray_output, const char *output);
+CFGFUN(bar_color_single, const char *colorclass, const char *color);
+CFGFUN(bar_status_command, const char *command);
+CFGFUN(bar_workspace_buttons, const char *value);
+CFGFUN(bar_finish);
+
+#endif
diff --git a/include/config_parser.h b/include/config_parser.h
new file mode 100644 (file)
index 0000000..0daf811
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * config_parser.h: config parser-related definitions
+ *
+ */
+#ifndef I3_CONFIG_PARSER_H
+#define I3_CONFIG_PARSER_H
+
+#include <yajl/yajl_gen.h>
+
+/*
+ * 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.
+ *
+ */
+struct ConfigResult {
+    /* The JSON generator to append a reply to. */
+    yajl_gen json_gen;
+
+    /* The next state to transition to. Passed to the function so that we can
+     * determine the next state as a result of a function call, like
+     * cfg_criteria_pop_state() does. */
+    int next_state;
+};
+
+struct ConfigResult *parse_config(const char *input, struct context *context);
+
+#endif
index fa3e031dbc90fa9ac95c6e51c6efe72a76f58d0f..63b18b280e8837b471b437d2e245857071e0ad50 100644 (file)
@@ -7,8 +7,8 @@
  * include/data.h: This file defines all data structures used by i3
  *
  */
-#ifndef _DATA_H
-#define _DATA_H
+#ifndef I3_DATA_H
+#define I3_DATA_H
 
 #define SN_API_NOT_YET_FROZEN 1
 #include <libsn/sn-launcher.h>
@@ -55,7 +55,7 @@ typedef struct Window i3Window;
  *****************************************************************************/
 typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t;
 typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t;
-typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 2 } border_style_t;
+typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_PIXEL = 2 } border_style_t;
 
 /** parameter to specify whether tree_close() and x_window_kill() should kill
  * only this specific window or the whole X11 client */
@@ -441,8 +441,6 @@ struct Assignment {
  */
 struct Con {
     bool mapped;
-    /** whether this is a split container or not */
-    bool split;
     enum {
         CT_ROOT = 0,
         CT_OUTPUT = 1,
@@ -486,6 +484,7 @@ struct Con {
 
     /* the x11 border pixel attribute */
     int border_width;
+    int current_border_width;
 
     /* minimum increment size specified for the window (in pixels) */
     int width_increment;
@@ -497,6 +496,9 @@ struct Con {
      * inside this container (if any) sets the urgency hint, for example. */
     bool urgent;
 
+    /* timer used for disabling urgency */
+    struct ev_timer *urgency_timer;
+
     /* ids/pixmap/graphics context for the frame window */
     xcb_window_t frame;
     xcb_pixmap_t pixmap;
index abf9c76dfe0b6692032c006312c2f3599bd0aefc..44c95c6d348627d9d9305e52360dd183d11d3ead 100644 (file)
@@ -8,8 +8,8 @@
  *          events.  This code is from xcb-util.
  *
  */
-#ifndef _DEBUG_H
-#define _DEBUG_H
+#ifndef I3_DEBUG_H
+#define I3_DEBUG_H
 
 int handle_event(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e);
 
index 97b3902c95652fa82c575d487eb50d590f11c22d..88a1abc1e9cc1f8013d64d0373a927cc35d51896 100644 (file)
@@ -7,8 +7,8 @@
  * display_version.c: displays the running i3 version, runs as part of
  *                    i3 --moreversion.
  */
-#ifndef _DISPLAY_VERSION_H
-#define _DISPLAY_VERSION_H
+#ifndef I3_DISPLAY_VERSION_H
+#define I3_DISPLAY_VERSION_H
 
 /**
  * Connects to i3 to find out the currently running version. Useful since it
index a786069a337dc9cb8171d00a6ef160231a771e92..07ef6614a420bf4393202164bcc2b4cf3b5ae884 100644 (file)
@@ -7,8 +7,8 @@
  * ewmh.c: Get/set certain EWMH properties easily.
  *
  */
-#ifndef _EWMH_C
-#define _EWMH_C
+#ifndef I3_EWMH_C
+#define I3_EWMH_C
 
 /**
  * Updates _NET_CURRENT_DESKTOP with the current desktop number.
index adb10a0d2c448095f8d3c788d43f14b3a556dfbf..bfeba292470af38e800a553eec644becf3385a2f 100644 (file)
@@ -8,8 +8,8 @@
  * which don’t support multi-monitor in a useful way) and for our testsuite.
  *
  */
-#ifndef _FAKE_OUTPUTS_H
-#define _FAKE_OUTPUTS_H
+#ifndef I3_FAKE_OUTPUTS_H
+#define I3_FAKE_OUTPUTS_H
 
 /**
  * Creates outputs according to the given specification.
index 43137c9c7ed47ca174b850b1f54296760b2050b2..884d3cf1fe9df848a5ffdb2f2b9dd8f4e70de1b9 100644 (file)
@@ -7,8 +7,8 @@
  * floating.c: Floating windows.
  *
  */
-#ifndef _FLOATING_H
-#define _FLOATING_H
+#ifndef I3_FLOATING_H
+#define I3_FLOATING_H
 
 #include "tree.h"
 
@@ -134,8 +134,8 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
  *
  */
 void drag_pointer(Con *con, const xcb_button_press_event_t *event,
-                  xcb_window_t confine_to, border_t border, callback_t callback,
-                  const void *extra);
+                  xcb_window_t confine_to, border_t border, int cursor,
+                  callback_t callback, const void *extra);
 
 /**
  * Repositions the CT_FLOATING_CON to have the coordinates specified by
index bcebf9ff0901e38cff023f484a335ca517ad71c2..b2e7ce2e9288c3751486c22d3f4964734f351297 100644 (file)
@@ -8,8 +8,8 @@
  *             …).
  *
  */
-#ifndef _HANDLERS_H
-#define _HANDLERS_H
+#ifndef I3_HANDLERS_H
+#define I3_HANDLERS_H
 
 #include <xcb/randr.h>
 
index bd40f16b9eb8e79937d562937fab9fc743394a90..1bc8b55da13bd81a3b5aa6f93a1ee84c98a69016 100644 (file)
@@ -7,8 +7,8 @@
  * i3.h: global variables that are used all over i3.
  *
  */
-#ifndef _I3_H
-#define _I3_H
+#ifndef I3_I3_H
+#define I3_I3_H
 
 #include <sys/time.h>
 #include <sys/resource.h>
index 0906b7f919bbb98d1e21598557aec29b00b2ad7b..93b2ae87392e43a43b000ae5cd9d0beeed83c6c6 100644 (file)
@@ -8,8 +8,8 @@
  * for the IPC interface to i3 (see docs/ipc for more information).
  *
  */
-#ifndef _I3_IPC_H
-#define _I3_IPC_H
+#ifndef I3_I3_IPC_H
+#define I3_I3_IPC_H
 
 /*
  * Messages from clients to i3
@@ -84,4 +84,7 @@
 /* The output event will be triggered upon changes in the output list */
 #define I3_IPC_EVENT_OUTPUT                     (I3_IPC_EVENT_MASK | 1)
 
+/* The output event will be triggered upon mode changes */
+#define I3_IPC_EVENT_MODE                       (I3_IPC_EVENT_MASK | 2)
+
 #endif
index af80fa4bd18116bc071cf87f360bd577d850bc8c..ef50ba8630da305dd977d7d2a7482d313bdae7da 100644 (file)
@@ -7,8 +7,8 @@
  * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).
  *
  */
-#ifndef _IPC_H
-#define _IPC_H
+#ifndef I3_IPC_H
+#define I3_IPC_H
 
 #include <ev.h>
 #include <stdbool.h>
index 4d469babe8bb2869ffede445d8efc5163c625e8e..417843a10fbea0c1a5e7c202539d7811e53f36e3 100644 (file)
@@ -7,8 +7,8 @@
  * key_press.c: key press handler
  *
  */
-#ifndef _KEY_PRESS_H
-#define _KEY_PRESS_H
+#ifndef I3_KEY_PRESS_H
+#define I3_KEY_PRESS_H
 
 /**
  * There was a key press. We compare this key code with our bindings table and pass
index d4df901fe4cde7400a895d4d2e6d585f93590a92..7547845b9bf2245a97242badd9bcd6d7c9ff8e7f 100644 (file)
@@ -8,8 +8,8 @@
  * as i3-msg, i3-config-wizard, …
  *
  */
-#ifndef _LIBI3_H
-#define _LIBI3_H
+#ifndef I3_LIBI3_H
+#define I3_LIBI3_H
 
 #include <stdbool.h>
 #include <stdarg.h>
index a2cd6d149d53f717ecdeb16ebbe3ee23171661f8..282512b2454e1be458e40d41538a2473817dd6d4 100644 (file)
@@ -8,8 +8,8 @@
  *                restart.
  *
  */
-#ifndef _LOAD_LAYOUT_H
-#define _LOAD_LAYOUT_H
+#ifndef I3_LOAD_LAYOUT_H
+#define I3_LOAD_LAYOUT_H
 
 void tree_append_json(const char *filename);
 
index 7822fba50f66b9ba51943e2eef62776bfc3e799d..6fabeca339fdd3283a35e039a6fd2c7922f1e697 100644 (file)
@@ -7,8 +7,8 @@
  * log.c: Logging functions.
  *
  */
-#ifndef _LOG_H
-#define _LOG_H
+#ifndef I3_LOG_H
+#define I3_LOG_H
 
 #include <stdarg.h>
 #include <stdbool.h>
index c16d295f6c3eabd53b0604a2fd1c81a39c80962a..d50f64d49b67efa9a7f538025edd2f5c09b1100e 100644 (file)
@@ -7,8 +7,8 @@
  * manage.c: Initially managing new windows (or existing ones on restart).
  *
  */
-#ifndef _MANAGE_H
-#define _MANAGE_H
+#ifndef I3_MANAGE_H
+#define I3_MANAGE_H
 
 #include "data.h"
 
index 6d9cb91539f4219cbfd5fc8ed9081dca2e5591db..e1d259040dfddbd10546bec6469e8df7a7543ee3 100644 (file)
@@ -11,8 +11,8 @@
  * match_matches_window() to find the windows affected by this command.
  *
  */
-#ifndef _MATCH_H
-#define _MATCH_H
+#ifndef I3_MATCH_H
+#define I3_MATCH_H
 
 /*
  * Initializes the Match data structure. This function is necessary because the
index 22b6e809ef05b558a475ea8a7bfa5fb476a6e015..d45e676e4d52b5e8b8b7c6cef919f17321dea2d9 100644 (file)
@@ -7,8 +7,8 @@
  * move.c: Moving containers into some direction.
  *
  */
-#ifndef _MOVE_H
-#define _MOVE_H
+#ifndef I3_MOVE_H
+#define I3_MOVE_H
 
 /**
  * Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT,
index d488ad30c4baef8b67c7654bd4da0ddccffead3b..e87da22e063b1314bffebb349b6764a079006013 100644 (file)
@@ -7,8 +7,8 @@
  * output.c: Output (monitor) related functions.
  *
  */
-#ifndef _OUTPUT_H
-#define _OUTPUT_H
+#ifndef I3_OUTPUT_H
+#define I3_OUTPUT_H
 
 /**
  * Returns the output container below the given output container.
index ac527bcc9cc86feb45336686dbd63959646a4a05..b5c02144dc729986d3ecebb502ac2517af33706d 100644 (file)
@@ -9,8 +9,8 @@
  * (take your time to read it completely, it answers all questions).
  *
  */
-#ifndef _RANDR_H
-#define _RANDR_H
+#ifndef I3_RANDR_H
+#define I3_RANDR_H
 
 #include "data.h"
 #include <xcb/randr.h>
 TAILQ_HEAD(outputs_head, xoutput);
 extern struct outputs_head outputs;
 
+typedef enum {
+    CLOSEST_OUTPUT = 0,
+    FARTHEST_OUTPUT = 1
+} output_close_far_t;
+
 /**
  * We have just established a connection to the X server and need the initial
  * XRandR information to setup workspaces for each screen.
@@ -96,6 +101,6 @@ Output *get_output_most(direction_t direction, Output *current);
  * Gets the output which is the next one in the given direction.
  *
  */
-Output *get_output_next(direction_t direction, Output *current);
+Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far);
 
 #endif
index fe1e9f9537f47dcb13d029618763362c2048d054..7403abefb9dda55cf5431fe20c9c5d9f0602775e 100644 (file)
@@ -7,8 +7,8 @@
  * regex.c: Interface to libPCRE (perl compatible regular expressions).
  *
  */
-#ifndef _REGEX_H
-#define _REGEX_H
+#ifndef I3_REGEX_H
+#define I3_REGEX_H
 
 /**
  * Creates a new 'regex' struct containing the given pattern and a PCRE
index 1f31fb0f962e2bbc367dbd1240bbf3307ecd0e9d..0a5949f9fab35dc029b076cdd3d1407f38edac2d 100644 (file)
@@ -8,8 +8,8 @@
  *           various rects. Needs to be pushed to X11 (see x.c) to be visible.
  *
  */
-#ifndef _RENDER_H
-#define _RENDER_H
+#ifndef I3_RENDER_H
+#define I3_RENDER_H
 
 /**
  * "Renders" the given container (and its children), meaning that all rects are
index 99646ea087a857b07d6412e24ec72dd74f6cf4d3..fa0216c88ac3597e45193a32d7b1376e874d9860 100644 (file)
@@ -7,8 +7,8 @@
  * resize.c: Interactive resizing.
  *
  */
-#ifndef _RESIZE_H
-#define _RESIZE_H
+#ifndef I3_RESIZE_H
+#define I3_RESIZE_H
 
 int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
 
index 4d5533273983b1a8a11b8bc6d357fcdbd569fac2..c6157052ff5c9017d38b057137ba91670ddfa125 100644 (file)
@@ -7,8 +7,8 @@
  * scratchpad.c: Scratchpad functions (TODO: more description)
  *
  */
-#ifndef _SCRATCHPAD_H
-#define _SCRATCHPAD_H
+#ifndef I3_SCRATCHPAD_H
+#define I3_SCRATCHPAD_H
 
 /**
  * Moves the specified window to the __i3_scratch workspace, making it floating
index e755d2f139cdc003e9d5fc655d7aec7c882bd79a..fd3f53ebfac73a0ef60ea9f688c9126498626140 100644 (file)
@@ -8,8 +8,8 @@
  * default (ringbuffer for storing the debug log).
  *
  */
-#ifndef _I3_SHMLOG_H
-#define _I3_SHMLOG_H
+#ifndef I3_I3_SHMLOG_H
+#define I3_I3_SHMLOG_H
 
 #include <stdint.h>
 #include <pthread.h>
index 571070b04230079a1ae606400d1346ae8bb688fc..25d3385bc1e1cfec27e1f5bcd053966ccba6184e 100644 (file)
@@ -9,8 +9,8 @@
  *               to restart inplace).
  *
  */
-#ifndef _SIGHANDLER_H
-#define _SIGHANDLER_H
+#ifndef I3_SIGHANDLER_H
+#define I3_SIGHANDLER_H
 
 /**
  * Setup signal handlers to safely handle SIGSEGV and SIGFPE
index 290c8d21cf4f72db53d72dc7650fa5c0ea6c1414..e39fe63b08f751dcd86d1cc26bf8c678bd486cff 100644 (file)
@@ -10,8 +10,8 @@
  *            the appropriate workspace.
  *
  */
-#ifndef _STARTUP_H
-#define _STARTUP_H
+#ifndef I3_STARTUP_H
+#define I3_STARTUP_H
 
 #define SN_API_NOT_YET_FROZEN 1
 #include <libsn/sn-monitor.h>
  */
 void start_application(const char *command, bool no_startup_id);
 
+/**
+ * Deletes a startup sequence, ignoring whether its timeout has elapsed.
+ * Useful when e.g. a window is moved between workspaces and its children
+ * shouldn't spawn on the original workspace.
+ *
+ */
+void startup_sequence_delete(struct Startup_Sequence *sequence);
+
 /**
  * Called by libstartup-notification when something happens
  *
  */
 void startup_monitor_event(SnMonitorEvent *event, void *userdata);
 
+/**
+ * Gets the stored startup sequence for the _NET_STARTUP_ID of a given window.
+ *
+ */
+struct Startup_Sequence *startup_sequence_get(i3Window *cwindow,
+    xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader);
+
 /**
  * Checks if the given window belongs to a startup notification by checking if
  * the _NET_STARTUP_ID property is set on the window (or on its leader, if it’s
index 8816b19a640a55d0fa84300bdd59295c56c24943..2799afee5fdf452b179dd8a597d3a539f40be929 100644 (file)
@@ -7,8 +7,8 @@
  * tree.c: Everything that primarily modifies the layout tree data structure.
  *
  */
-#ifndef _TREE_H
-#define _TREE_H
+#ifndef I3_TREE_H
+#define I3_TREE_H
 
 extern Con *croot;
 /* TODO: i am not sure yet how much access to the focused container should
index cd88863c282a90b976bd9ba02fb6b4c3c58ef3ca..e397a4e80ac37bbc77e230c6b497f12b3f33b638 100644 (file)
@@ -8,8 +8,8 @@
  *         also libi3).
  *
  */
-#ifndef _UTIL_H
-#define _UTIL_H
+#ifndef I3_UTIL_H
+#define I3_UTIL_H
 
 #include <err.h>
 
index 60198b878f3517e04addc9c33744d4e06ef16d49..59d3b1bbd97a3bc1401b6bda614bb23c89b6b11b 100644 (file)
@@ -7,8 +7,8 @@
  * window.c: Updates window attributes (X11 hints/properties).
  *
  */
-#ifndef _WINDOW_H
-#define _WINDOW_H
+#ifndef I3_WINDOW_H
+#define I3_WINDOW_H
 
 /**
  * Updates the WM_CLASS (consisting of the class and instance) for the
index 1b25b425f4d21b5e4fe0f5e5ab08fd34174f23bf..907e959f0347e8903eef96bee29498698570f718 100644 (file)
@@ -8,8 +8,8 @@
  *              workspaces.
  *
  */
-#ifndef _WORKSPACE_H
-#define _WORKSPACE_H
+#ifndef I3_WORKSPACE_H
+#define I3_WORKSPACE_H
 
 #include "data.h"
 #include "tree.h"
@@ -95,6 +95,12 @@ Con* workspace_prev_on_output(void);
  */
 void workspace_back_and_forth(void);
 
+/**
+ * Returns the previously focused workspace con, or NULL if unavailable.
+ *
+ */
+Con *workspace_back_and_forth_get(void);
+
 
 #if 0
 /**
@@ -168,4 +174,11 @@ void ws_force_orientation(Con *ws, orientation_t orientation);
  */
 Con *workspace_attach_to(Con *ws);
 
+/**
+ * Creates a new container and re-parents all of children from the given
+ * workspace into it.
+ *
+ * The container inherits the layout from the workspace.
+ */
+Con *workspace_encapsulate(Con *ws);
 #endif
index cb4a8a964d32823ab74dd1161c1469648ba5ea38..c3d4ffc7cc29f5bac0194aa9ca401f469ee70e8e 100644 (file)
@@ -8,8 +8,8 @@
  *      render.c). Basically a big state machine.
  *
  */
-#ifndef _X_H
-#define _X_H
+#ifndef I3_X_H
+#define I3_X_H
 
 /** Stores the X11 window ID of the currently focused window */
 extern xcb_window_t focused_id;
index 269038daef0926885056185f49005cbabcc61a0d..15d3e28fc4e1e81dbd8020fcd960ce6ba9b3f739 100644 (file)
@@ -7,8 +7,8 @@
  * xcb.c: Helper functions for easier usage of XCB
  *
  */
-#ifndef _XCB_H
-#define _XCB_H
+#ifndef I3_XCB_H
+#define I3_XCB_H
 
 #include "data.h"
 #include "xcursor.h"
index 9c2660b72b220d67d4e2d7a8d22a7ad3301e96b9..fc09a254323953afb13a7ed76147e0e988f6d719 100644 (file)
@@ -9,8 +9,8 @@
  *               older versions.
  *
  */
-#ifndef _XCB_COMPAT_H
-#define _XCB_COMPAT_H
+#ifndef I3_XCB_COMPAT_H
+#define I3_XCB_COMPAT_H
 
 #define xcb_icccm_get_wm_protocols_reply_t xcb_get_wm_protocols_reply_t
 #define xcb_icccm_get_wm_protocols xcb_get_wm_protocols
index c341f90ba6b77e6a2bfc028abb13528e2cc96b00..bfe37c3944cf098e22b91aa461cb92e7db5dde5c 100644 (file)
@@ -7,8 +7,8 @@
  * xcursor.c: libXcursor support for themed cursors.
  *
  */
-#ifndef _XCURSOR_CURSOR_H
-#define _XCURSOR_CURSOR_H
+#ifndef I3_XCURSOR_CURSOR_H
+#define I3_XCURSOR_CURSOR_H
 
 #include <X11/Xlib.h>
 
@@ -16,7 +16,12 @@ enum xcursor_cursor_t {
     XCURSOR_CURSOR_POINTER = 0,
     XCURSOR_CURSOR_RESIZE_HORIZONTAL,
     XCURSOR_CURSOR_RESIZE_VERTICAL,
+    XCURSOR_CURSOR_TOP_LEFT_CORNER,
+    XCURSOR_CURSOR_TOP_RIGHT_CORNER,
+    XCURSOR_CURSOR_BOTTOM_LEFT_CORNER,
+    XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER,
     XCURSOR_CURSOR_WATCH,
+    XCURSOR_CURSOR_MOVE,
     XCURSOR_CURSOR_MAX
 };
 
index 8c879c07fb7329c38b0c7fe8b2c5419a557d6cd7..ca7c2ab538a3938b42a6efdaaabc1bcb0833b047 100644 (file)
@@ -9,8 +9,8 @@
  * driver which does not support RandR in 2011 *sigh*.
  *
  */
-#ifndef _XINERAMA_H
-#define _XINERAMA_H
+#ifndef I3_XINERAMA_H
+#define I3_XINERAMA_H
 
 #include "data.h"
 
index 6b548d363bb01d5beea1c13835d35b76da429995..2f6c2aab63967e8ad48e0718f37098c6c4f18d2b 100644 (file)
@@ -24,7 +24,8 @@ workspaces.
 
 get_outputs::
 Gets the current outputs. The reply will be a JSON-encoded list of outputs (see
-the reply section).
+the reply section of docs/ipc, e.g. at
+http://i3wm.org/docs/ipc.html#_receiving_replies_from_i3).
 
 get_tree::
 Gets the layout tree. i3 uses a tree as data structure which includes every
index b4c9e005bd8ab16583bae3ef2dc8aeab8984f1f9..4224707c4c8829122d6c0368bcb583f7cc61f8f6 100644 (file)
@@ -61,10 +61,20 @@ state EXEC:
   command = string
       -> call cmd_exec($nosn, $command)
 
-# border normal|none|1pixel|toggle
+# border normal|none|1pixel|toggle|1pixel
 state BORDER:
-  border_style = 'normal', 'none', '1pixel', 'toggle'
-      -> call cmd_border($border_style)
+  border_style = 'normal', 'pixel'
+    -> BORDER_WIDTH
+  border_style = 'none', 'toggle'
+    -> call cmd_border($border_style, "0")
+  border_style = '1pixel'
+    -> call cmd_border($border_style, "1")
+
+state BORDER_WIDTH:
+  end
+    -> call cmd_border($border_style, "2")
+  border_width = word
+    -> call cmd_border($border_style, $border_width)
 
 # layout default|stacked|stacking|tabbed|splitv|splith
 # layout toggle [split|all]
@@ -243,6 +253,8 @@ state MOVE_WORKSPACE:
       -> MOVE_WORKSPACE_TO_OUTPUT
   workspace = 'next', 'prev', 'next_on_output', 'prev_on_output', 'current'
       -> call cmd_move_con_to_workspace($workspace)
+  'back_and_forth'
+      -> call cmd_move_con_to_workspace_back_and_forth()
   'number'
       -> MOVE_WORKSPACE_NUMBER
   workspace = string
diff --git a/parser-specs/config.spec b/parser-specs/config.spec
new file mode 100644 (file)
index 0000000..1c11bf9
--- /dev/null
@@ -0,0 +1,443 @@
+# vim:ts=2:sw=2:expandtab
+#
+# i3 - an improved dynamic tiling window manager
+# © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+#
+# parser-specs/config.spec: Specification file for generate-command-parser.pl
+# which will generate the appropriate header files for our C parser.
+#
+# Use :source highlighting.vim in vim to get syntax highlighting
+# for this file.
+
+# TODO: should we implement an include statement for the criteria part so we DRY?
+
+state INITIAL:
+  # We have an end token here for all the commands which just call some
+  # function without using an explicit 'end' token.
+  end ->
+  '#'                                      -> IGNORE_LINE
+  'set'                                    -> IGNORE_LINE
+  bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING
+  'bar'                                    -> BARBRACE
+  'font'                                   -> FONT
+  'mode'                                   -> MODENAME
+  'floating_minimum_size'                  -> FLOATING_MINIMUM_SIZE_WIDTH
+  'floating_maximum_size'                  -> FLOATING_MAXIMUM_SIZE_WIDTH
+  'floating_modifier'                      -> FLOATING_MODIFIER
+  'default_orientation'                    -> DEFAULT_ORIENTATION
+  'workspace_layout'                       -> WORKSPACE_LAYOUT
+  windowtype = 'new_window', 'new_float'   -> NEW_WINDOW
+  'hide_edge_borders'                      -> HIDE_EDGE_BORDERS
+  'for_window'                             -> FOR_WINDOW
+  'assign'                                 -> ASSIGN
+  'focus_follows_mouse'                    -> FOCUS_FOLLOWS_MOUSE
+  'force_focus_wrapping'                   -> FORCE_FOCUS_WRAPPING
+  'force_xinerama', 'force-xinerama'       -> FORCE_XINERAMA
+  'workspace_auto_back_and_forth'          -> WORKSPACE_BACK_AND_FORTH
+  'fake_outputs', 'fake-outputs'           -> FAKE_OUTPUTS
+  'force_display_urgency_hint'             -> FORCE_DISPLAY_URGENCY_HINT
+  'workspace'                              -> WORKSPACE
+  'ipc_socket', 'ipc-socket'               -> IPC_SOCKET
+  'restart_state'                          -> RESTART_STATE
+  'popup_during_fullscreen'                -> POPUP_DURING_FULLSCREEN
+  exectype = 'exec_always', 'exec'         -> EXEC
+  colorclass = 'client.background'
+      -> COLOR_SINGLE
+  colorclass = 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
+      -> COLOR_BORDER
+
+# We ignore comments and 'set' lines (variables).
+state IGNORE_LINE:
+  end, string
+      -> INITIAL
+
+# floating_minimum_size <width> x <height>
+state FLOATING_MINIMUM_SIZE_WIDTH:
+  width = number
+      -> FLOATING_MINIMUM_SIZE_X
+
+state FLOATING_MINIMUM_SIZE_X:
+  'x'
+      -> FLOATING_MINIMUM_SIZE_HEIGHT
+
+state FLOATING_MINIMUM_SIZE_HEIGHT:
+  height = number
+      -> call cfg_floating_minimum_size(&width, &height)
+
+# floating_maximum_size <width> x <height>
+state FLOATING_MAXIMUM_SIZE_WIDTH:
+  width = number
+      -> FLOATING_MAXIMUM_SIZE_X
+
+state FLOATING_MAXIMUM_SIZE_X:
+  'x'
+      -> FLOATING_MAXIMUM_SIZE_HEIGHT
+
+state FLOATING_MAXIMUM_SIZE_HEIGHT:
+  height = number
+      -> call cfg_floating_maximum_size(&width, &height)
+
+# floating_modifier <modifier>
+state FLOATING_MODIFIER:
+  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl'
+      ->
+  '+'
+      ->
+  end
+      -> call cfg_floating_modifier($modifiers)
+
+# default_orientation <horizontal|vertical|auto>
+state DEFAULT_ORIENTATION:
+  orientation = 'horizontal', 'vertical', 'auto'
+      -> call cfg_default_orientation($orientation)
+
+# workspace_layout <default|stacking|tabbed>
+state WORKSPACE_LAYOUT:
+  layout = 'default', 'stacking', 'stacked', 'tabbed'
+      -> call cfg_workspace_layout($layout)
+
+# new_window <normal|1pixel|none>
+# new_float <normal|1pixel|none>
+# TODO: new_float is not in the userguide yet
+# TODO: pixel is not in the userguide yet
+state NEW_WINDOW:
+  border = 'normal', 'pixel'
+      -> NEW_WINDOW_PIXELS
+  border = '1pixel', 'none'
+      -> call cfg_new_window($windowtype, $border, -1)
+
+state NEW_WINDOW_PIXELS:
+  end
+      -> call cfg_new_window($windowtype, $border, 2)
+  width = number
+      -> NEW_WINDOW_PIXELS_PX
+
+state NEW_WINDOW_PIXELS_PX:
+  'px'
+      ->
+  end
+      -> call cfg_new_window($windowtype, $border, &width)
+
+# hide_edge_borders <none|vertical|horizontal|both>
+# also hide_edge_borders <bool> for compatibility
+state HIDE_EDGE_BORDERS:
+  hide_borders = 'none', 'vertical', 'horizontal', 'both'
+      -> call cfg_hide_edge_borders($hide_borders)
+  hide_borders = '1', 'yes', 'true', 'on', 'enable', 'active'
+      -> call cfg_hide_edge_borders($hide_borders)
+
+# for_window <criteria> command
+state FOR_WINDOW:
+  '['
+      -> call cfg_criteria_init(FOR_WINDOW_COMMAND); CRITERIA
+
+state FOR_WINDOW_COMMAND:
+  command = string
+      -> call cfg_for_window($command)
+
+# assign <criteria> [→] workspace
+state ASSIGN:
+  '['
+      -> call cfg_criteria_init(ASSIGN_WORKSPACE); CRITERIA
+
+state ASSIGN_WORKSPACE:
+  '→'
+      ->
+  workspace = string
+      -> call cfg_assign($workspace)
+
+# Criteria: Used by for_window and assign.
+state CRITERIA:
+  ctype = 'class'       -> CRITERION
+  ctype = 'instance'    -> CRITERION
+  ctype = 'window_role' -> CRITERION
+  ctype = 'con_id'      -> CRITERION
+  ctype = 'id'          -> CRITERION
+  ctype = 'con_mark'    -> CRITERION
+  ctype = 'title'       -> CRITERION
+  ctype = 'urgent'      -> CRITERION
+  ']'
+      -> call cfg_criteria_pop_state()
+
+state CRITERION:
+  '=' -> CRITERION_STR
+
+state CRITERION_STR:
+  cvalue = word
+      -> call cfg_criteria_add($ctype, $cvalue); CRITERIA
+
+# focus_follows_mouse bool
+state FOCUS_FOLLOWS_MOUSE:
+  value = word
+      -> call cfg_focus_follows_mouse($value)
+
+# force_focus_wrapping
+state FORCE_FOCUS_WRAPPING:
+  value = word
+      -> call cfg_force_focus_wrapping($value)
+
+# force_xinerama
+state FORCE_XINERAMA:
+  value = word
+      -> call cfg_force_xinerama($value)
+
+# workspace_back_and_forth
+state WORKSPACE_BACK_AND_FORTH:
+  value = word
+      -> call cfg_workspace_back_and_forth($value)
+
+
+# fake_outputs (for testcases)
+state FAKE_OUTPUTS:
+  outputs = string
+      -> call cfg_fake_outputs($outputs)
+
+# force_display_urgency_hint <timeout> ms
+state FORCE_DISPLAY_URGENCY_HINT:
+  duration_ms = number
+      -> FORCE_DISPLAY_URGENCY_HINT_MS
+
+state FORCE_DISPLAY_URGENCY_HINT_MS:
+  'ms'
+      ->
+  end
+      -> call cfg_force_display_urgency_hint(&duration_ms)
+
+# workspace <workspace> output <output>
+state WORKSPACE:
+  workspace = word
+    -> WORKSPACE_OUTPUT
+
+state WORKSPACE_OUTPUT:
+  'output'
+      -> WORKSPACE_OUTPUT_STR
+
+state WORKSPACE_OUTPUT_STR:
+  output = string
+      -> call cfg_workspace($workspace, $output)
+
+# ipc-socket <path>
+state IPC_SOCKET:
+  path = string
+      -> call cfg_ipc_socket($path)
+
+# restart_state <path> (for testcases)
+state RESTART_STATE:
+  path = string
+      -> call cfg_restart_state($path)
+
+# popup_during_fullscreen
+state POPUP_DURING_FULLSCREEN:
+  value = 'ignore', 'leave_fullscreen'
+      -> call cfg_popup_during_fullscreen($value)
+
+# client.background <hexcolor>
+state COLOR_SINGLE:
+  color = word
+      -> call cfg_color_single($colorclass, $color)
+
+# colorclass border background text indicator
+state COLOR_BORDER:
+  border = word
+      -> COLOR_BACKGROUND
+
+state COLOR_BACKGROUND:
+  background = word
+      -> COLOR_TEXT
+
+state COLOR_TEXT:
+  text = word
+      -> COLOR_INDICATOR
+
+state COLOR_INDICATOR:
+  indicator = word
+      -> call cfg_color($colorclass, $border, $background, $text, $indicator)
+  end
+      -> call cfg_color($colorclass, $border, $background, $text, NULL)
+
+# <exec|exec_always> [--no-startup-id] command
+state EXEC:
+  no_startup_id = '--no-startup-id'
+      ->
+  command = string
+      -> call cfg_exec($exectype, $no_startup_id, $command)
+
+# font font
+state FONT:
+  font = string
+      -> call cfg_font($font)
+
+# bindsym/bindcode
+state BINDING:
+  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch'
+      ->
+  '+'
+      ->
+  key = word
+      -> BINDCOMMAND
+
+state BINDCOMMAND:
+  release = '--release'
+      ->
+  command = string
+      -> call cfg_binding($bindtype, $modifiers, $key, $release, $command)
+
+################################################################################
+# Mode configuration
+################################################################################
+
+state MODENAME:
+  modename = word
+      -> call cfg_enter_mode($modename); MODEBRACE
+
+state MODEBRACE:
+  end
+      ->
+  '{'
+      -> MODE
+
+state MODE:
+  end ->
+  '#' -> MODE_IGNORE_LINE
+  'set' -> MODE_IGNORE_LINE
+  bindtype = 'bindsym', 'bindcode', 'bind'
+      -> MODE_BINDING
+  '}'
+      -> INITIAL
+
+# We ignore comments and 'set' lines (variables).
+state MODE_IGNORE_LINE:
+  end, string
+      -> MODE
+
+state MODE_BINDING:
+  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch'
+      ->
+  '+'
+      ->
+  key = word
+      -> MODE_BINDCOMMAND
+
+state MODE_BINDCOMMAND:
+  release = '--release'
+      ->
+  command = string
+      -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $command); MODE
+
+################################################################################
+# Bar configuration (i3bar)
+################################################################################
+
+state BARBRACE:
+  end
+      ->
+  '{'
+      -> BAR
+
+state BAR:
+  end ->
+  '#' -> BAR_IGNORE_LINE
+  'set' -> BAR_IGNORE_LINE
+  'i3bar_command'     -> BAR_BAR_COMMAND
+  'status_command'    -> BAR_STATUS_COMMAND
+  'socket_path'       -> BAR_SOCKET_PATH
+  'mode'              -> BAR_MODE
+  'modifier'          -> BAR_MODIFIER
+  'position'          -> BAR_POSITION
+  'output'            -> BAR_OUTPUT
+  'tray_output'       -> BAR_TRAY_OUTPUT
+  'font'              -> BAR_FONT
+  'workspace_buttons' -> BAR_WORKSPACE_BUTTONS
+  'verbose'           -> BAR_VERBOSE
+  'colors'            -> BAR_COLORS_BRACE
+  '}'
+      -> call cfg_bar_finish(); INITIAL
+
+# We ignore comments and 'set' lines (variables).
+state BAR_IGNORE_LINE:
+  end, string
+      -> BAR
+
+state BAR_BAR_COMMAND:
+  command = string
+      -> call cfg_bar_i3bar_command($command); BAR
+
+state BAR_STATUS_COMMAND:
+  command = string
+      -> call cfg_bar_status_command($command); BAR
+
+state BAR_SOCKET_PATH:
+  path = string
+      -> call cfg_bar_socket_path($path); BAR
+
+state BAR_MODE:
+  mode = 'dock', 'hide'
+      -> call cfg_bar_mode($mode); BAR
+
+state BAR_MODIFIER:
+  modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift'
+      -> call cfg_bar_modifier($modifier); BAR
+
+state BAR_POSITION:
+  position = 'top', 'bottom'
+      -> call cfg_bar_position($position); BAR
+
+state BAR_OUTPUT:
+  output = string
+      -> call cfg_bar_output($output); BAR
+
+state BAR_TRAY_OUTPUT:
+  output = string
+      -> call cfg_bar_tray_output($output); BAR
+
+state BAR_FONT:
+  font = string
+      -> call cfg_bar_font($font); BAR
+
+state BAR_WORKSPACE_BUTTONS:
+  value = word
+      -> call cfg_bar_workspace_buttons($value); BAR
+
+state BAR_VERBOSE:
+  value = word
+      -> call cfg_bar_verbose($value); BAR
+
+state BAR_COLORS_BRACE:
+  end
+      ->
+  '{'
+      -> BAR_COLORS
+
+state BAR_COLORS:
+  end ->
+  '#' -> BAR_COLORS_IGNORE_LINE
+  'set' -> BAR_COLORS_IGNORE_LINE
+  colorclass = 'background', 'statusline'
+      -> BAR_COLORS_SINGLE
+  colorclass = 'focused_workspace', 'active_workspace', 'inactive_workspace', 'urgent_workspace'
+      -> BAR_COLORS_BORDER
+  '}'
+      -> BAR
+
+# We ignore comments and 'set' lines (variables).
+state BAR_COLORS_IGNORE_LINE:
+  end, string
+      -> BAR_COLORS
+
+state BAR_COLORS_SINGLE:
+  color = word
+      -> call cfg_bar_color_single($colorclass, $color); BAR_COLORS
+
+state BAR_COLORS_BORDER:
+  border = word
+      -> BAR_COLORS_BACKGROUND
+
+state BAR_COLORS_BACKGROUND:
+  background = word
+      -> BAR_COLORS_TEXT
+
+state BAR_COLORS_TEXT:
+  end
+      -> call cfg_bar_color($colorclass, $border, $background, NULL); BAR_COLORS
+  text = word
+      -> call cfg_bar_color($colorclass, $border, $background, $text); BAR_COLORS
index f3d1aaba7204de796e00bf3ce72ffd5afff359de..e0966d57e2fe4489a8c2417a0914d3b9d15736f0 100644 (file)
@@ -9,7 +9,7 @@ syntax match i3specComment /#.*/
 highlight link i3specComment Comment
 
 syntax region i3specLiteral start=/'/ end=/'/
-syntax keyword i3specToken string word end
+syntax keyword i3specToken string word number end
 highlight link i3specLiteral String
 highlight link i3specToken String
 
index 8ee2a1dad75fdcb5f414b59437c9f46c9662e815..6eef8a5ae6920d903a8ade3243ff5037b5fb8f9e 100644 (file)
@@ -56,6 +56,7 @@ EOL     (\r?\n)
 %s OUTPUT_COND
 %s FOR_WINDOW_COND
 %s EAT_WHITESPACE
+%s BORDER_WIDTH
 
 %x BUFFER_LINE
 %x BAR
@@ -171,6 +172,7 @@ EOL     (\r?\n)
                                 }
 <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; }
@@ -200,9 +202,10 @@ auto                            { return TOK_AUTO; }
 workspace_layout                { return TOK_WORKSPACE_LAYOUT; }
 new_window                      { return TOKNEWWINDOW; }
 new_float                       { return TOKNEWFLOAT; }
-normal                          { return TOK_NORMAL; }
+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; }
@@ -212,6 +215,8 @@ 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; }
index 29c519f0469f3aa6544993cb5701a9a8499e5759..2a22aae4ef6bef8197ceb65121bff3dd43bb569b 100644 (file)
@@ -13,6 +13,8 @@
 
 #include "all.h"
 
+bool force_old_config_parser = false;
+
 static pid_t configerror_pid = -1;
 
 static Match current_match;
@@ -108,6 +110,7 @@ static int detect_version(char *buf) {
                 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) {
@@ -624,15 +627,20 @@ void parse_file(const char *f) {
         }
     }
 
-    /* now lex/parse it */
-    yy_scan_string(new);
 
     context = scalloc(sizeof(struct context));
     context->filename = f;
 
-    if (yyparse() != 0) {
-        fprintf(stderr, "Could not parse configfile\n");
-        exit(1);
+    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);
@@ -668,7 +676,8 @@ void parse_file(const char *f) {
         start_configerror_nagbar(f);
     }
 
-    yylex_destroy();
+    if (force_old_config_parser)
+        yylex_destroy();
     FREE(context->line_copy);
     free(context);
     FREE(font_pattern);
@@ -728,6 +737,7 @@ void parse_file(const char *f) {
 %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"
@@ -738,6 +748,7 @@ void parse_file(const char *f) {
 %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"
@@ -746,6 +757,7 @@ void parse_file(const char *f) {
 %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"
@@ -816,9 +828,11 @@ void parse_file(const char *f) {
 %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
@@ -848,6 +862,7 @@ line:
     | force_focus_wrapping
     | force_xinerama
     | fake_outputs
+    | force_display_urgency_hint
     | workspace_back_and_forth
     | workspace_bar
     | workspace
@@ -1052,6 +1067,11 @@ word_or_number:
     }
     ;
 
+duration:
+    NUMBER { sasprintf(&$$, "%d", $1); }
+    | NUMBER TOK_TIME_MS { sasprintf(&$$, "%d", $1); }
+    ;
+
 mode:
     TOKMODE QUOTEDSTRING '{' modelines '}'
     {
@@ -1471,9 +1491,27 @@ new_float:
     ;
 
 border_style:
-    TOK_NORMAL      { $$ = BS_NORMAL; }
-    | TOK_NONE      { $$ = BS_NONE; }
-    | TOK_1PIXEL    { $$ = BS_1PIXEL; }
+    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:
@@ -1548,6 +1586,14 @@ workspace_back_and_forth:
     }
     ;
 
+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
     {
@@ -1736,6 +1782,11 @@ exec_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; }
index 23b6be4f15483d417101221f8f7a778c9cf0c2c5..5eedd00041f250b978798649bc8cf503dbbcb0f8 100644 (file)
@@ -179,6 +179,22 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
     DLOG("--> OUTCOME = %p\n", con);
     DLOG("type = %d, name = %s\n", con->type, con->name);
 
+    /* Any click in a workspace should focus that workspace. If the
+     * workspace is on another output we need to do a workspace_show in
+     * order for i3bar (and others) to notice the change in workspace. */
+    Con *ws = con_get_workspace(con);
+    Con *focused_workspace = con_get_workspace(focused);
+
+    if (!ws) {
+        ws = TAILQ_FIRST(&(output_get_content(con_get_output(con))->focus_head));
+        if (!ws)
+            goto done;
+    }
+
+    if (ws != focused_workspace)
+        workspace_show(ws);
+    focused_id = XCB_NONE;
+
     /* don’t handle dockarea cons, they must not be focused */
     if (con->parent->type == CT_DOCKAREA)
         goto done;
@@ -207,21 +223,13 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
         goto done;
     }
 
-    /* 2: focus this con. If the workspace is on another output we need to
-     * do a workspace_show in order for i3bar (and others) to notice the
-     * change in workspace. */
-    Con *ws = con_get_workspace(con);
-    Con *focused_workspace = con_get_workspace(focused);
-
-    if (ws != focused_workspace)
-        workspace_show(ws);
-    focused_id = XCB_NONE;
+    /* 2: focus this con. */
     con_focus(con);
 
     /* 3: For floating containers, we also want to raise them on click.
      * We will skip handling events on floating cons in fullscreen mode */
     Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
-    if (floatingcon != NULL && fs == NULL) {
+    if (floatingcon != NULL && fs != con) {
         floating_raise_con(floatingcon);
 
         /* 4: floating_modifier plus left mouse button drags */
@@ -309,6 +317,25 @@ int handle_button_press(xcb_button_press_event_t *event) {
         return route_click(con, event, mod_pressed, CLICK_INSIDE);
 
     if (!(con = con_by_frame_id(event->event))) {
+        /* If the root window is clicked, find the relevant output from the
+         * click coordinates and focus the output's active workspace. */
+        if (event->event == root) {
+            Con *output, *ws;
+            TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+                if (con_is_internal(output) ||
+                    !rect_contains(output->rect, event->event_x, event->event_y))
+                    continue;
+
+                ws = TAILQ_FIRST(&(output_get_content(output)->focus_head));
+                if (ws != con_get_workspace(focused)) {
+                    workspace_show(ws);
+                    tree_render();
+                }
+                return 1;
+            }
+            return 0;
+        }
+
         ELOG("Clicked into unknown window?!\n");
         xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
         xcb_flush(conn);
index 2d8fce3cc410cf7802eb2a72c280cdbc1b238553..4b2b2d2ab0af81a5d6141285487e1441f5f41db7 100644 (file)
@@ -38,7 +38,6 @@
     } \
 } while (0)
 
-static owindows_head owindows;
 
 /*
  * Returns true if a is definitely greater than b (using the given epsilon)
@@ -57,19 +56,19 @@ static Output *get_output_from_string(Output *current_output, const char *output
     Output *output;
 
     if (strcasecmp(output_str, "left") == 0) {
-        output = get_output_next(D_LEFT, current_output);
+        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);
+        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);
+        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);
+        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);
@@ -99,6 +98,29 @@ static bool maybe_back_and_forth(struct CommandResult *cmd_output, char *name) {
     return true;
 }
 
+/*
+ * Return the passed workspace unless it is the current one and auto back and
+ * forth is enabled, in which case the back_and_forth workspace is returned.
+ */
+static Con *maybe_auto_back_and_forth_workspace(Con *workspace) {
+    Con *current, *baf;
+
+    if (!config.workspace_auto_back_and_forth)
+        return workspace;
+
+    current = con_get_workspace(focused);
+
+    if (current == workspace) {
+        baf = workspace_back_and_forth_get();
+        if (baf != NULL) {
+            DLOG("Substituting workspace with back_and_forth, as it is focused.\n");
+            return baf;
+        }
+    }
+
+    return workspace;
+}
+
 // This code is commented out because we might recycle it for popping up error
 // messages on parser errors.
 #if 0
@@ -199,6 +221,20 @@ void cmd_MIGRATION_start_nagbar(void) {
  * Criteria functions.
  ******************************************************************************/
 
+/*
+ * Helper data structure for an operation window (window on which the operation
+ * will be performed). Used to build the TAILQ owindows.
+ *
+ */
+typedef struct owindow {
+    Con *con;
+    TAILQ_ENTRY(owindow) owindows;
+} owindow;
+
+typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
+
+static owindows_head owindows;
+
 /*
  * Initializes the specified 'Match' data structure and the initial state of
  * commands.c for matching target windows of a command.
@@ -365,7 +401,8 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
      *  when criteria was specified but didn't match any window or
      *  when criteria wasn't specified and we don't have any window focused. */
     if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
-        (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
+        (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
+        !con_has_children(focused))) {
         ysuccess(false);
         return;
     }
@@ -400,6 +437,38 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
     ysuccess(true);
 }
 
+/**
+ * Implementation of 'move [window|container] [to] workspace back_and_forth'.
+ *
+ */
+void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
+    owindow *current;
+    Con *ws;
+
+    ws = workspace_back_and_forth_get();
+
+    if (ws == NULL) {
+        y(map_open);
+        ystr("success");
+        y(bool, false);
+        ystr("error");
+        ystr("No workspace was previously active.");
+        y(map_close);
+        return;
+    }
+
+    HANDLE_EMPTY_MATCH;
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+        con_move_to_workspace(current->con, ws, true, false);
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    ysuccess(true);
+}
+
 /*
  * Implementation of 'move [window|container] [to] workspace <name>'.
  *
@@ -421,9 +490,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
         ysuccess(false);
         return;
     }
-
-    if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) {
-        ELOG("No window to move, you have focused a workspace.\n");
+    else if (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
+        !con_has_children(focused)) {
         ysuccess(false);
         return;
     }
@@ -432,6 +500,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
     /* get the workspace */
     Con *ws = workspace_get(name, NULL);
 
+    ws = maybe_auto_back_and_forth_workspace(ws);
+
     HANDLE_EMPTY_MATCH;
 
     TAILQ_FOREACH(current, &owindows, owindows) {
@@ -445,7 +515,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
 }
 
 /*
- * Implementation of 'move [window|container] [to] workspace number <number>'.
+ * Implementation of 'move [window|container] [to] workspace number <name>'.
  *
  */
 void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
@@ -455,7 +525,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
      *  when criteria was specified but didn't match any window or
      *  when criteria wasn't specified and we don't have any window focused. */
     if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
-        (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
+        (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
+        !con_has_children(focused))) {
         ysuccess(false);
         return;
     }
@@ -469,8 +540,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
     if (parsed_num == LONG_MIN ||
         parsed_num == LONG_MAX ||
         parsed_num < 0 ||
-        *endptr != '\0') {
-        LOG("Could not parse \"%s\" as a number.\n", which);
+        endptr == which) {
+        LOG("Could not parse initial part of \"%s\" as a number.\n", which);
         y(map_open);
         ystr("success");
         y(bool, false);
@@ -489,6 +560,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
         workspace = workspace_get(which, NULL);
     }
 
+    workspace = maybe_auto_back_and_forth_workspace(workspace);
+
     HANDLE_EMPTY_MATCH;
 
     TAILQ_FOREACH(current, &owindows, owindows) {
@@ -516,10 +589,9 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin
     }
 }
 
-static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int ppt) {
+static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) {
     LOG("tiling resize\n");
     /* get the appropriate current container (skip stacked/tabbed cons) */
-    Con *current = focused;
     Con *other = NULL;
     double percentage = 0;
     while (current->parent->layout == L_STACKED ||
@@ -599,10 +671,9 @@ static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int
     return true;
 }
 
-static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, int ppt) {
+static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char *direction, int ppt) {
     LOG("width/height resize\n");
     /* get the appropriate current container (skip stacked/tabbed cons) */
-    Con *current = focused;
     while (current->parent->layout == L_STACKED ||
            current->parent->layout == L_TABBED)
         current = current->parent;
@@ -697,17 +768,22 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz
         ppt *= -1;
     }
 
-    Con *floating_con;
-    if ((floating_con = con_inside_floating(focused))) {
-        cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px);
-    } else {
-        if (strcmp(direction, "width") == 0 ||
-            strcmp(direction, "height") == 0) {
-            if (!cmd_resize_tiling_width_height(current_match, cmd_output, way, direction, ppt))
-                return;
+    HANDLE_EMPTY_MATCH;
+
+    owindow *current;
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        Con *floating_con;
+        if ((floating_con = con_inside_floating(current->con))) {
+            cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px);
         } else {
-            if (!cmd_resize_tiling_direction(current_match, cmd_output, way, direction, ppt))
-                return;
+            if (strcmp(direction, "width") == 0 ||
+                strcmp(direction, "height") == 0) {
+                if (!cmd_resize_tiling_width_height(current_match, cmd_output, current->con, way, direction, ppt))
+                    return;
+            } else {
+                if (!cmd_resize_tiling_direction(current_match, cmd_output, current->con, way, direction, ppt))
+                    return;
+            }
         }
     }
 
@@ -717,11 +793,11 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz
 }
 
 /*
- * Implementation of 'border normal|none|1pixel|toggle'.
+ * Implementation of 'border normal|none|1pixel|toggle|pixel'.
  *
  */
-void cmd_border(I3_CMD, char *border_style_str) {
-    DLOG("border style should be changed to %s\n", border_style_str);
+void cmd_border(I3_CMD, char *border_style_str, char *border_width ) {
+    DLOG("border style should be changed to %s with border width %s\n", border_style_str, border_width);
     owindow *current;
 
     HANDLE_EMPTY_MATCH;
@@ -729,23 +805,39 @@ void cmd_border(I3_CMD, char *border_style_str) {
     TAILQ_FOREACH(current, &owindows, owindows) {
         DLOG("matching: %p / %s\n", current->con, current->con->name);
         int border_style = current->con->border_style;
+        char *end;
+        int tmp_border_width = -1;
+        tmp_border_width = strtol(border_width, &end, 10);
+        if (end == border_width) {
+            /* no valid digits found */
+            tmp_border_width = -1;
+        }
         if (strcmp(border_style_str, "toggle") == 0) {
             border_style++;
             border_style %= 3;
+            if (border_style == BS_NORMAL)
+                tmp_border_width = 2;
+            else if (border_style == BS_NONE)
+                tmp_border_width = 0;
+            else if (border_style == BS_PIXEL)
+                tmp_border_width = 1;
         } else {
             if (strcmp(border_style_str, "normal") == 0)
                 border_style = BS_NORMAL;
-            else if (strcmp(border_style_str, "none") == 0)
+            else if (strcmp(border_style_str, "pixel") == 0)
+                border_style = BS_PIXEL;
+            else if (strcmp(border_style_str, "1pixel") == 0){
+                border_style = BS_PIXEL;
+                tmp_border_width = 1;
+            } else if (strcmp(border_style_str, "none") == 0)
                 border_style = BS_NONE;
-            else if (strcmp(border_style_str, "1pixel") == 0)
-                border_style = BS_1PIXEL;
             else {
                 ELOG("BUG: called with border_style=%s\n", border_style_str);
                 ysuccess(false);
                 return;
             }
         }
-        con_set_border_style(current->con, border_style);
+        con_set_border_style(current->con, border_style, tmp_border_width);
     }
 
     cmd_output->needs_tree_render = true;
@@ -807,7 +899,7 @@ void cmd_workspace(I3_CMD, char *which) {
 }
 
 /*
- * Implementation of 'workspace number <number>'
+ * Implementation of 'workspace number <name>'
  *
  */
 void cmd_workspace_number(I3_CMD, char *which) {
@@ -818,8 +910,8 @@ void cmd_workspace_number(I3_CMD, char *which) {
     if (parsed_num == LONG_MIN ||
         parsed_num == LONG_MAX ||
         parsed_num < 0 ||
-        *endptr != '\0') {
-        LOG("Could not parse \"%s\" as a number.\n", which);
+        endptr == which) {
+        LOG("Could not parse initial part of \"%s\" as a number.\n", which);
         y(map_open);
         ystr("success");
         y(bool, false);
@@ -838,8 +930,6 @@ void cmd_workspace_number(I3_CMD, char *which) {
     if (!workspace) {
         LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num);
         ysuccess(true);
-        /* terminate the which string after the endposition of the number */
-        *endptr = '\0';
         workspace_show_by_name(which);
         cmd_output->needs_tree_render = true;
         return;
@@ -949,13 +1039,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);
+        output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
     else if (strcasecmp(name, "down") == 0)
-        output = get_output_next(D_DOWN, current_output);
+        output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
     else if (strcasecmp(name, "left") == 0)
-        output = get_output_next(D_LEFT, current_output);
+        output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
     else if (strcasecmp(name, "right") == 0)
-        output = get_output_next(D_RIGHT, current_output);
+        output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
     else
         output = get_output_by_name(name);
 
@@ -1042,8 +1132,12 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
         Con *content = output_get_content(output->con);
         LOG("got output %p with content %p\n", output, content);
 
+        Con *previously_visible_ws = TAILQ_FIRST(&(content->nodes_head));
+        LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name);
+
         Con *ws = con_get_workspace(current->con);
         LOG("should move workspace %p / %s\n", ws, ws->name);
+        bool workspace_was_visible = workspace_is_visible(ws);
 
         if (con_num_children(ws->parent) == 1) {
             LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name);
@@ -1078,9 +1172,9 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
             /* notify the IPC listeners */
             ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
         }
+        DLOG("Detaching\n");
 
         /* detach from the old output and attach to the new output */
-        bool workspace_was_visible = workspace_is_visible(ws);
         Con *old_content = ws->parent;
         con_detach(ws);
         if (workspace_was_visible) {
@@ -1102,6 +1196,22 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
             /* Focus the moved workspace on the destination output. */
             workspace_show(ws);
         }
+
+        /* NB: We cannot simply work with previously_visible_ws since it might
+         * have been cleaned up by workspace_show() already, depending on the
+         * focus order/number of other workspaces on the output.
+         * Instead, we loop through the available workspaces and only work with
+         * previously_visible_ws if we still find it. */
+        TAILQ_FOREACH(ws, &(content->nodes_head), nodes) {
+            if (ws != previously_visible_ws)
+                continue;
+
+            /* Call the on_remove_child callback of the workspace which previously
+             * was visible on the destination output. Since it is no longer
+             * visible, it might need to get cleaned up. */
+            CALL(previously_visible_ws, on_remove_child);
+            break;
+        }
     }
 
     cmd_output->needs_tree_render = true;
@@ -1132,7 +1242,7 @@ void cmd_split(I3_CMD, char *direction) {
 }
 
 /*
- * Implementaiton of 'kill [window|client]'.
+ * Implementation of 'kill [window|client]'.
  *
  */
 void cmd_kill(I3_CMD, char *kill_mode_str) {
@@ -1187,14 +1297,6 @@ void cmd_exec(I3_CMD, char *nosn, char *command) {
  *
  */
 void cmd_focus_direction(I3_CMD, char *direction) {
-    if (focused &&
-        focused->type != CT_WORKSPACE &&
-        focused->fullscreen_mode != CF_NONE) {
-        LOG("Cannot change focus while in fullscreen mode.\n");
-        ysuccess(false);
-        return;
-    }
-
     DLOG("direction = *%s*\n", direction);
 
     if (strcmp(direction, "left") == 0)
@@ -1221,14 +1323,6 @@ void cmd_focus_direction(I3_CMD, char *direction) {
  *
  */
 void cmd_focus_window_mode(I3_CMD, char *window_mode) {
-    if (focused &&
-        focused->type != CT_WORKSPACE &&
-        focused->fullscreen_mode != CF_NONE) {
-        LOG("Cannot change focus while in fullscreen mode.\n");
-        ysuccess(false);
-        return;
-    }
-
     DLOG("window_mode = %s\n", window_mode);
 
     Con *ws = con_get_workspace(focused);
@@ -1478,7 +1572,7 @@ void cmd_layout_toggle(I3_CMD, char *toggle_mode) {
 }
 
 /*
- * Implementaiton of 'exit'.
+ * Implementation of 'exit'.
  *
  */
 void cmd_exit(I3_CMD) {
@@ -1490,7 +1584,7 @@ void cmd_exit(I3_CMD) {
 }
 
 /*
- * Implementaiton of 'reload'.
+ * Implementation of 'reload'.
  *
  */
 void cmd_reload(I3_CMD) {
@@ -1507,7 +1601,7 @@ void cmd_reload(I3_CMD) {
 }
 
 /*
- * Implementaiton of 'restart'.
+ * Implementation of 'restart'.
  *
  */
 void cmd_restart(I3_CMD) {
@@ -1519,7 +1613,7 @@ void cmd_restart(I3_CMD) {
 }
 
 /*
- * Implementaiton of 'open'.
+ * Implementation of 'open'.
  *
  */
 void cmd_open(I3_CMD) {
index d739f4e146ad7ae11449829c464457a019d4df96..1720fd6f0418d84fced2026d992f8049bca8fc89 100644 (file)
@@ -46,7 +46,7 @@
  * input parser-specs/commands.spec.
  ******************************************************************************/
 
-#include "GENERATED_enums.h"
+#include "GENERATED_command_enums.h"
 
 typedef struct token {
     char *name;
@@ -63,7 +63,7 @@ typedef struct tokenptr {
     int n;
 } cmdp_token_ptr;
 
-#include "GENERATED_tokens.h"
+#include "GENERATED_command_tokens.h"
 
 /*******************************************************************************
  * The (small) stack where identified literals are stored during the parsing
@@ -182,7 +182,7 @@ static Match current_match;
 static struct CommandResult subcommand_output;
 static struct CommandResult command_output;
 
-#include "GENERATED_call.h"
+#include "GENERATED_command_call.h"
 
 
 static void next_state(const cmdp_token *token) {
index 0f7a5cbb4937d30fb4f85ec4a84c2897b2d761b3..ad5025a92057aa10aa16d4916065f42fcf884183 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -28,6 +28,20 @@ char *colors[] = {
 
 static void con_on_remove_child(Con *con);
 
+/*
+ * force parent split containers to be redrawn
+ *
+ */
+static void con_force_split_parents_redraw(Con *con) {
+    Con *parent = con;
+
+    while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
+        if (!con_is_leaf(parent))
+            FREE(parent->deco_render_params);
+        parent = parent->parent;
+    }
+}
+
 /*
  * Create a new container (and attach it to the given parent, if not NULL).
  * This function initializes the data structures and creates the appropriate
@@ -41,6 +55,7 @@ Con *con_new(Con *parent, i3Window *window) {
     new->type = CT_CON;
     new->window = window;
     new->border_style = config.default_border;
+    new->current_border_width = -1;
     static int cnt = 0;
     DLOG("opening window %d\n", cnt);
 
@@ -162,6 +177,7 @@ add_to_focus_head:
      * This way, we have the option to insert Cons without having
      * to focus them. */
     TAILQ_INSERT_TAIL(focus_head, con, focused);
+    con_force_split_parents_redraw(con);
 }
 
 /*
@@ -169,6 +185,7 @@ add_to_focus_head:
  *
  */
 void con_detach(Con *con) {
+    con_force_split_parents_redraw(con);
     if (con->type == CT_FLOATING_CON) {
         TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows);
         TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
@@ -195,8 +212,14 @@ void con_focus(Con *con) {
         con_focus(con->parent);
 
     focused = con;
-    if (con->urgent) {
+    /* We can't blindly reset non-leaf containers since they might have
+     * other urgent children. Therefore we only reset leafs and propagate
+     * the changes upwards via con_update_parents_urgency() which does proper
+     * checks before resetting the urgency.
+     */
+    if (con->urgent && con_is_leaf(con)) {
         con->urgent = false;
+        con_update_parents_urgency(con);
         workspace_update_urgent_flag(con_get_workspace(con));
     }
 }
@@ -209,6 +232,32 @@ bool con_is_leaf(Con *con) {
     return TAILQ_EMPTY(&(con->nodes_head));
 }
 
+/**
+ * Returns true if this node has regular or floating children.
+ *
+ */
+bool con_has_children(Con *con) {
+    return (!con_is_leaf(con) || !TAILQ_EMPTY(&(con->floating_head)));
+}
+
+/*
+ * Returns true if a container should be considered split.
+ *
+ */
+bool con_is_split(Con *con) {
+    if (con_is_leaf(con))
+        return false;
+
+    switch (con->layout) {
+        case L_DOCKAREA:
+        case L_OUTPUT:
+            return false;
+
+        default:
+            return true;
+    }
+}
+
 /*
  * Returns true if this node accepts a window (if the node swallows windows,
  * it might already have swallowed enough and cannot hold any more).
@@ -219,7 +268,7 @@ bool con_accepts_window(Con *con) {
     if (con->type == CT_WORKSPACE)
         return false;
 
-    if (con->split) {
+    if (con_is_split(con)) {
         DLOG("container %p does not accept windows, it is a split container.\n", con);
         return false;
     }
@@ -337,6 +386,14 @@ Con *con_get_fullscreen_con(Con *con, int fullscreen_mode) {
     return NULL;
 }
 
+/**
+ * Returns true if the container is internal, such as __i3_scratch
+ *
+ */
+bool con_is_internal(Con *con) {
+    return (con->name[0] == '_' && con->name[1] == '_');
+}
+
 /*
  * Returns true if the node is floating.
  *
@@ -576,11 +633,6 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) {
  *
  */
 void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp) {
-    if (con->type == CT_WORKSPACE) {
-        DLOG("Moving workspaces is not yet implemented.\n");
-        return;
-    }
-
     /* Prevent moving if this would violate the fullscreen focus restrictions. */
     if (!con_fullscreen_permits_focusing(workspace)) {
         LOG("Cannot move out of a fullscreen container");
@@ -598,6 +650,25 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
         return;
     }
 
+    if (con->type == CT_WORKSPACE) {
+        /* Re-parent all of the old workspace's floating windows. */
+        Con *child;
+        while (!TAILQ_EMPTY(&(source_ws->floating_head))) {
+            child = TAILQ_FIRST(&(source_ws->floating_head));
+            con_move_to_workspace(child, workspace, true, true);
+        }
+
+        /* If there are no non-floating children, ignore the workspace. */
+        if (con_is_leaf(con))
+            return;
+
+        con = workspace_encapsulate(con);
+        if (con == NULL) {
+            ELOG("Workspace failed to move its contents into a container!\n");
+            return;
+        }
+    }
+
     /* Save the current workspace. So we can call workspace_show() by the end
      * of this function. */
     Con *current_ws = con_get_workspace(focused);
@@ -655,6 +726,14 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
         }
     }
 
+    /* If moving a fullscreen container and the destination already has a
+     * fullscreen window on it, un-fullscreen the target's fullscreen con. */
+    Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
+    if (con->fullscreen_mode != CF_NONE && fullscreen != NULL) {
+        con_toggle_fullscreen(fullscreen, CF_OUTPUT);
+        fullscreen = NULL;
+    }
+
     DLOG("Re-attaching container to %p / %s\n", next, next->name);
     /* 5: re-attach the con to the parent of this focused container */
     Con *parent = con->parent;
@@ -671,14 +750,16 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
      * invisible.
      * We don’t focus the con for i3 pseudo workspaces like __i3_scratch and
      * we don’t focus when there is a fullscreen con on that workspace. */
-    if ((workspace->name[0] != '_' || workspace->name[1] != '_') &&
-        con_get_fullscreen_con(workspace, CF_OUTPUT) == NULL) {
-        /* We need to save focus on workspace level and restore it afterwards.
-         * Otherwise, we might focus a different workspace without actually
-         * switching workspaces. */
+    if (!con_is_internal(workspace) && !fullscreen) {
+        /* We need to save the focused workspace on the output in case the
+         * new workspace is hidden and it's necessary to immediately switch
+         * back to the originally-focused workspace. */
         Con *old_focus = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head));
         con_focus(con_descend_focused(con));
-        con_focus(old_focus);
+
+        /* Restore focus if the output's focused workspace has changed. */
+        if (con_get_workspace(focused) != old_focus)
+            con_focus(old_focus);
     }
 
     /* 8: when moving to a visible workspace on a different output, we keep the
@@ -686,8 +767,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
      * don’t want to focus invisible workspaces */
     if (source_output != dest_output &&
         workspace_is_visible(workspace) &&
-        workspace->name[0] != '_' &&
-        workspace->name[1] != '_') {
+        !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
@@ -701,6 +781,38 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
             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. */
+    struct Startup_Sequence *sequence;
+    xcb_get_property_cookie_t cookie;
+    xcb_get_property_reply_t *startup_id_reply;
+
+    if (!con_is_leaf(con)) {
+        Con *child;
+        TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+            if (!child->window)
+                continue;
+
+            cookie = xcb_get_property(conn, false, child->window->id,
+                A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
+            startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
+
+            sequence = startup_sequence_get(child->window, startup_id_reply, true);
+            if (sequence != NULL)
+                startup_sequence_delete(sequence);
+        }
+    }
+
+    if (con->window) {
+        cookie = xcb_get_property(conn, false, con->window->id,
+            A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
+        startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
+
+        sequence = startup_sequence_get(con->window, startup_id_reply, true);
+        if (sequence != NULL)
+            startup_sequence_delete(sequence);
+    }
+
     CALL(parent, on_remove_child);
 }
 
@@ -893,6 +1005,7 @@ Con *con_descend_tiling_focused(Con *con) {
  */
 Con *con_descend_direction(Con *con, direction_t direction) {
     Con *most = NULL;
+    Con *current;
     int orientation = con_orientation(con);
     DLOG("con_descend_direction(%p, orientation %d, direction %d)\n", con, orientation, direction);
     if (direction == D_LEFT || direction == D_RIGHT) {
@@ -906,7 +1019,12 @@ Con *con_descend_direction(Con *con, direction_t direction) {
             /* Wrong orientation. We use the last focused con. Within that con,
              * we recurse to chose the left/right con or at least the last
              * focused one. */
-            most = TAILQ_FIRST(&(con->focus_head));
+            TAILQ_FOREACH(current, &(con->focus_head), focused) {
+                if (current->type != CT_FLOATING_CON) {
+                    most = current;
+                    break;
+                }
+            }
         } else {
             /* If the con has no orientation set, it’s not a split container
              * but a container with a client window, so stop recursing */
@@ -925,7 +1043,12 @@ Con *con_descend_direction(Con *con, direction_t direction) {
             /* Wrong orientation. We use the last focused con. Within that con,
              * we recurse to chose the top/bottom con or at least the last
              * focused one. */
-            most = TAILQ_FIRST(&(con->focus_head));
+            TAILQ_FOREACH(current, &(con->focus_head), focused) {
+                if (current->type != CT_FLOATING_CON) {
+                    most = current;
+                    break;
+                }
+            }
         } else {
             /* If the con has no orientation set, it’s not a split container
              * but a container with a client window, so stop recursing */
@@ -946,52 +1069,38 @@ Con *con_descend_direction(Con *con, direction_t direction) {
  */
 Rect con_border_style_rect(Con *con) {
     adjacent_t borders_to_hide = ADJ_NONE;
+    int border_width = con->current_border_width;
+    DLOG("The border width for con is set to: %d\n", con->current_border_width);
     Rect result;
+    if (con->current_border_width < 0)
+        border_width = config.default_border_width;
+    DLOG("Effective border width is set to: %d\n", border_width);
     /* Shortcut to avoid calling con_adjacent_borders() on dock containers. */
     int border_style = con_border_style(con);
     if (border_style == BS_NONE)
         return (Rect){ 0, 0, 0, 0 };
     borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
-    switch (border_style) {
-    case BS_NORMAL:
-        result = (Rect){2, 0, -(2 * 2), -2};
-        if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
-            result.x -= 2;
-            result.width += 2;
-        }
-        if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
-            result.width += 2;
-        }
-        /* With normal borders we never hide the upper border */
-        if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
-            result.height += 2;
-        }
-        return result;
-
-    case BS_1PIXEL:
-        result = (Rect){1, 1, -2, -2};
-        if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
-            result.x -= 1;
-            result.width += 1;
-        }
-        if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
-            result.width += 1;
-        }
-        if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE) {
-            result.y -= 1;
-            result.height += 1;
-        }
-        if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
-            result.height += 1;
-        }
-        return result;
-
-    case BS_NONE:
-        return (Rect){0, 0, 0, 0};
-
-    default:
-        assert(false);
+    if (border_style == BS_NORMAL) {
+        result = (Rect){border_width, 0 , -(2 * border_width), -(border_width)};
+    } else {
+        result = (Rect){border_width, border_width, -(2 * border_width), -(2 * border_width)};
+    }
+    if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
+        result.x -= border_width;
+        result.width += border_width;
+    }
+    if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
+        result.width += border_width;
+    }
+    if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE && (border_style != BS_NORMAL)) {
+        result.y -= border_width;
+        result.height += border_width;
     }
+    if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
+        result.height += border_width;
+    }
+    return result;
+
 }
 
 /*
@@ -1046,10 +1155,11 @@ int con_border_style(Con *con) {
  * floating window.
  *
  */
-void con_set_border_style(Con *con, int border_style) {
+void con_set_border_style(Con *con, int border_style, int border_width) {
     /* Handle the simple case: non-floating containerns */
     if (!con_is_floating(con)) {
         con->border_style = border_style;
+        con->current_border_width = border_width;
         return;
     }
 
@@ -1068,6 +1178,7 @@ void con_set_border_style(Con *con, int border_style) {
 
     /* Change the border style, get new border/decoration values. */
     con->border_style = border_style;
+    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);
@@ -1125,7 +1236,6 @@ void con_set_layout(Con *con, int layout) {
              * split. */
             new->layout = layout;
             new->last_split_layout = con->last_split_layout;
-            new->split = true;
 
             Con *old_focused = TAILQ_FIRST(&(con->focus_head));
             if (old_focused == TAILQ_END(&(con->focus_head)))
@@ -1149,6 +1259,7 @@ void con_set_layout(Con *con, int layout) {
 
             tree_flatten(croot);
         }
+        con_force_split_parents_redraw(con);
         return;
     }
 
@@ -1169,6 +1280,7 @@ void con_set_layout(Con *con, int layout) {
     } else {
         con->layout = layout;
     }
+    con_force_split_parents_redraw(con);
 }
 
 /*
@@ -1248,6 +1360,8 @@ static void con_on_remove_child(Con *con) {
         return;
     }
 
+    con_force_split_parents_redraw(con);
+
     /* TODO: check if this container would swallow any other client and
      * don’t close it automatically. */
     int children = con_num_children(con);
@@ -1294,7 +1408,7 @@ Rect con_minimum_size(Con *con) {
     /* For horizontal/vertical split containers we sum up the width (h-split)
      * or height (v-split) and use the maximum of the height (h-split) or width
      * (v-split) as minimum size. */
-    if (con->split) {
+    if (con_is_split(con)) {
         uint32_t width = 0, height = 0;
         Con *child;
         TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
@@ -1312,7 +1426,7 @@ Rect con_minimum_size(Con *con) {
     }
 
     ELOG("Unhandled case, type = %d, layout = %d, split = %d\n",
-         con->type, con->layout, con->split);
+         con->type, con->layout, con_is_split(con));
     assert(false);
 }
 
@@ -1378,3 +1492,104 @@ bool con_fullscreen_permits_focusing(Con *con) {
     /* Focusing con would hide it behind a fullscreen window, disallow it. */
     return false;
 }
+
+/*
+ *
+ * Checks if the given container has an urgent child.
+ *
+ */
+bool con_has_urgent_child(Con *con) {
+    Con *child;
+
+    if (con_is_leaf(con))
+        return con->urgent;
+
+    /* We are not interested in floating windows since they can only be
+     * attached to a workspace → nodes_head instead of focus_head */
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        if (con_has_urgent_child(child))
+            return true;
+    }
+
+    return false;
+}
+
+/*
+ * Make all parent containers urgent if con is urgent or clear the urgent flag
+ * of all parent containers if there are no more urgent children left.
+ *
+ */
+void con_update_parents_urgency(Con *con) {
+    Con *parent = con->parent;
+
+    bool new_urgency_value = con->urgent;
+    while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
+        if (new_urgency_value) {
+            parent->urgent = true;
+        } else {
+            /* We can only reset the urgency when the parent
+             * has no other urgent children */
+            if (!con_has_urgent_child(parent))
+                parent->urgent = false;
+        }
+        parent = parent->parent;
+    }
+}
+
+/*
+ * Create a string representing the subtree under con.
+ *
+ */
+char *con_get_tree_representation(Con *con) {
+    /* this code works as follows:
+     *  1) create a string with the layout type (D/V/H/T/S) and an opening bracket
+     *  2) append the tree representation of the children to the string
+     *  3) add closing bracket
+     *
+     * The recursion ends when we hit a leaf, in which case we return the
+     * class_instance of the contained window.
+     */
+
+    /* end of recursion */
+    if (con_is_leaf(con)) {
+        if (!con->window)
+            return sstrdup("nowin");
+
+        if (!con->window->class_instance)
+            return sstrdup("noinstance");
+
+        return sstrdup(con->window->class_instance);
+    }
+
+    char *buf;
+    /* 1) add the Layout type to buf */
+    if (con->layout == L_DEFAULT)
+        buf = sstrdup("D[");
+    else if (con->layout == L_SPLITV)
+        buf = sstrdup("V[");
+    else if (con->layout == L_SPLITH)
+        buf = sstrdup("H[");
+    else if (con->layout == L_TABBED)
+        buf = sstrdup("T[");
+    else if (con->layout == L_STACKED)
+        buf = sstrdup("S[");
+
+    /* 2) append representation of children */
+    Con *child;
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        char *child_txt = con_get_tree_representation(child);
+
+        char *tmp_buf;
+        sasprintf(&tmp_buf, "%s%s%s", buf,
+                (TAILQ_FIRST(&(con->nodes_head)) == child ? "" : " "), child_txt);
+        free(buf);
+        buf = tmp_buf;
+    }
+
+    /* 3) close the brackets */
+    char *complete_buf;
+    sasprintf(&complete_buf, "%s]", buf);
+    free(buf);
+
+    return complete_buf;
+}
index fcf3841ed3be21387a334deef437e974d4c82171..ce9adca500c2b423190d86f5f974b5dec0cc5c81 100644 (file)
@@ -46,6 +46,7 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint
     }
     GRAB_KEY(mods);
     GRAB_KEY(mods | xcb_numlock_mask);
+    GRAB_KEY(mods | XCB_MOD_MASK_LOCK);
     GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
 }
 
@@ -194,6 +195,13 @@ void switch_mode(const char *new_mode) {
         bindings = mode->bindings;
         translate_keysyms();
         grab_all_keys(conn, false);
+
+        char *event_msg;
+        sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name);
+
+        ipc_send_event("mode", I3_IPC_EVENT_MODE, event_msg);
+        FREE(event_msg);
+
         return;
     }
 
@@ -410,9 +418,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
 
     config.default_border = BS_NORMAL;
     config.default_floating_border = BS_NORMAL;
+    config.default_border_width = 2;
     /* Set default_orientation to NO_ORIENTATION for auto orientation. */
     config.default_orientation = NO_ORIENTATION;
 
+    /* Set default urgency reset delay to 500ms */
+    if (config.workspace_urgency_timer == 0)
+        config.workspace_urgency_timer = 0.5;
+
     parse_configuration(override_configpath);
 
     if (reload) {
diff --git a/src/config_directives.c b/src/config_directives.c
new file mode 100644 (file)
index 0000000..8b636c0
--- /dev/null
@@ -0,0 +1,564 @@
+#undef I3__FILE__
+#define I3__FILE__ "config_directives.c"
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * config_directives.c: all config storing functions (see config_parser.c)
+ *
+ */
+#include <float.h>
+#include <stdarg.h>
+
+#include "all.h"
+
+// Macros to make the YAJL API a bit easier to use.
+#define y(x, ...) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__)
+#define ystr(str) yajl_gen_string(cmd_output->json_gen, (unsigned char*)str, strlen(str))
+#define ysuccess(success) do { \
+    y(map_open); \
+    ystr("success"); \
+    y(bool, success); \
+    y(map_close); \
+} while (0)
+
+/*******************************************************************************
+ * Criteria functions.
+ ******************************************************************************/
+
+static int criteria_next_state;
+
+/*
+ * Initializes the specified 'Match' data structure and the initial state of
+ * commands.c for matching target windows of a command.
+ *
+ */
+CFGFUN(criteria_init, int _state) {
+    criteria_next_state = _state;
+
+    DLOG("Initializing criteria, current_match = %p, state = %d\n", current_match, _state);
+    match_init(current_match);
+}
+
+CFGFUN(criteria_pop_state) {
+    result->next_state = criteria_next_state;
+}
+
+/*
+ * Interprets a ctype=cvalue pair and adds it to the current match
+ * specification.
+ *
+ */
+CFGFUN(criteria_add, const char *ctype, const char *cvalue) {
+    DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue);
+
+    if (strcmp(ctype, "class") == 0) {
+        current_match->class = regex_new(cvalue);
+        return;
+    }
+
+    if (strcmp(ctype, "instance") == 0) {
+        current_match->instance = regex_new(cvalue);
+        return;
+    }
+
+    if (strcmp(ctype, "window_role") == 0) {
+        current_match->role = regex_new(cvalue);
+        return;
+    }
+
+    if (strcmp(ctype, "con_id") == 0) {
+        char *end;
+        long parsed = strtol(cvalue, &end, 10);
+        if (parsed == LONG_MIN ||
+            parsed == LONG_MAX ||
+            parsed < 0 ||
+            (end && *end != '\0')) {
+            ELOG("Could not parse con id \"%s\"\n", cvalue);
+        } else {
+            current_match->con_id = (Con*)parsed;
+            printf("id as int = %p\n", current_match->con_id);
+        }
+        return;
+    }
+
+    if (strcmp(ctype, "id") == 0) {
+        char *end;
+        long parsed = strtol(cvalue, &end, 10);
+        if (parsed == LONG_MIN ||
+            parsed == LONG_MAX ||
+            parsed < 0 ||
+            (end && *end != '\0')) {
+            ELOG("Could not parse window id \"%s\"\n", cvalue);
+        } else {
+            current_match->id = parsed;
+            printf("window id as int = %d\n", current_match->id);
+        }
+        return;
+    }
+
+    if (strcmp(ctype, "con_mark") == 0) {
+        current_match->mark = regex_new(cvalue);
+        return;
+    }
+
+    if (strcmp(ctype, "title") == 0) {
+        current_match->title = regex_new(cvalue);
+        return;
+    }
+
+    if (strcmp(ctype, "urgent") == 0) {
+        if (strcasecmp(cvalue, "latest") == 0 ||
+            strcasecmp(cvalue, "newest") == 0 ||
+            strcasecmp(cvalue, "recent") == 0 ||
+            strcasecmp(cvalue, "last") == 0) {
+            current_match->urgent = U_LATEST;
+        } else if (strcasecmp(cvalue, "oldest") == 0 ||
+                   strcasecmp(cvalue, "first") == 0) {
+            current_match->urgent = U_OLDEST;
+        }
+        return;
+    }
+
+    ELOG("Unknown criterion: %s\n", ctype);
+}
+
+/* TODO: refactor the above criteria code into a single file (with src/commands.c). */
+
+/*******************************************************************************
+ * Utility functions
+ ******************************************************************************/
+
+static bool eval_boolstr(const char *str) {
+    return (strcasecmp(str, "1") == 0 ||
+            strcasecmp(str, "yes") == 0 ||
+            strcasecmp(str, "true") == 0 ||
+            strcasecmp(str, "on") == 0 ||
+            strcasecmp(str, "enable") == 0 ||
+            strcasecmp(str, "active") == 0);
+}
+
+static uint32_t modifiers_from_str(const char *str) {
+    /* It might be better to use strtok() here, but the simpler strstr() should
+     * do for now. */
+    uint32_t result = 0;
+    if (str == NULL)
+        return result;
+    if (strstr(str, "Mod1") != NULL)
+        result |= BIND_MOD1;
+    if (strstr(str, "Mod2") != NULL)
+        result |= BIND_MOD2;
+    if (strstr(str, "Mod3") != NULL)
+        result |= BIND_MOD3;
+    if (strstr(str, "Mod4") != NULL)
+        result |= BIND_MOD4;
+    if (strstr(str, "Mod5") != NULL)
+        result |= BIND_MOD5;
+    if (strstr(str, "Control") != NULL ||
+        strstr(str, "Ctrl") != NULL)
+        result |= BIND_CONTROL;
+    if (strstr(str, "Shift") != NULL)
+        result |= BIND_SHIFT;
+    if (strstr(str, "Mode_switch") != NULL)
+        result |= BIND_MODE_SWITCH;
+    return result;
+}
+
+static char *font_pattern;
+
+CFGFUN(font, const char *font) {
+       config.font = load_font(font, true);
+       set_font(&config.font);
+
+       /* Save the font pattern for using it as bar font later on */
+       FREE(font_pattern);
+       font_pattern = sstrdup(font);
+}
+
+// TODO: refactor with mode_binding
+CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command) {
+    Binding *new_binding = scalloc(sizeof(Binding));
+    DLOG("bindtype %s, modifiers %s, key %s, release %s\n", bindtype, modifiers, key, release);
+    new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
+    if (strcmp(bindtype, "bindsym") == 0) {
+        new_binding->symbol = sstrdup(key);
+    } else {
+        // TODO: strtol with proper error handling
+        new_binding->keycode = atoi(key);
+        if (new_binding->keycode == 0) {
+            ELOG("Could not parse \"%s\" as a keycode, ignoring this binding.\n", key);
+            return;
+        }
+    }
+    new_binding->mods = modifiers_from_str(modifiers);
+    new_binding->command = sstrdup(command);
+    TAILQ_INSERT_TAIL(bindings, new_binding, bindings);
+}
+
+
+/*******************************************************************************
+ * Mode handling
+ ******************************************************************************/
+
+static struct bindings_head *current_bindings;
+
+CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command) {
+    Binding *new_binding = scalloc(sizeof(Binding));
+    DLOG("bindtype %s, modifiers %s, key %s, release %s\n", bindtype, modifiers, key, release);
+    new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
+    if (strcmp(bindtype, "bindsym") == 0) {
+        new_binding->symbol = sstrdup(key);
+    } else {
+        // TODO: strtol with proper error handling
+        new_binding->keycode = atoi(key);
+        if (new_binding->keycode == 0) {
+            ELOG("Could not parse \"%s\" as a keycode, ignoring this binding.\n", key);
+            return;
+        }
+    }
+    new_binding->mods = modifiers_from_str(modifiers);
+    new_binding->command = sstrdup(command);
+    TAILQ_INSERT_TAIL(current_bindings, new_binding, bindings);
+}
+
+CFGFUN(enter_mode, const char *modename) {
+    if (strcasecmp(modename, "default") == 0) {
+        ELOG("You cannot use the name \"default\" for your mode\n");
+        exit(1);
+    }
+    DLOG("\t now in mode %s\n", modename);
+    struct Mode *mode = scalloc(sizeof(struct Mode));
+    mode->name = sstrdup(modename);
+    mode->bindings = scalloc(sizeof(struct bindings_head));
+    TAILQ_INIT(mode->bindings);
+    current_bindings = mode->bindings;
+    SLIST_INSERT_HEAD(&modes, mode, modes);
+}
+
+CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command) {
+       struct Autostart *new = smalloc(sizeof(struct Autostart));
+       new->command = sstrdup(command);
+       new->no_startup_id = (no_startup_id != NULL);
+       if (strcmp(exectype, "exec") == 0) {
+               TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
+       } else {
+               TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always);
+       }
+}
+
+CFGFUN(for_window, const char *command) {
+    if (match_is_empty(current_match)) {
+        ELOG("Match is empty, ignoring this for_window statement\n");
+        return;
+    }
+    DLOG("\t should execute command %s for the criteria mentioned above\n", command);
+    Assignment *assignment = scalloc(sizeof(Assignment));
+    assignment->type = A_COMMAND;
+    match_copy(&(assignment->match), current_match);
+    assignment->dest.command = sstrdup(command);
+    TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+}
+
+CFGFUN(floating_minimum_size, const long width, const long height) {
+    config.floating_minimum_width = width;
+    config.floating_minimum_height = height;
+}
+
+CFGFUN(floating_maximum_size, const long width, const long height) {
+    config.floating_maximum_width = width;
+    config.floating_maximum_height = height;
+}
+
+CFGFUN(floating_modifier, const char *modifiers) {
+    config.floating_modifier = modifiers_from_str(modifiers);
+}
+
+CFGFUN(default_orientation, const char *orientation) {
+    if (strcmp(orientation, "horizontal") == 0)
+        config.default_orientation = HORIZ;
+    else if (strcmp(orientation, "vertical") == 0)
+        config.default_orientation = VERT;
+    else config.default_orientation = NO_ORIENTATION;
+}
+
+CFGFUN(workspace_layout, const char *layout) {
+    if (strcmp(layout, "default") == 0)
+        config.default_layout = L_DEFAULT;
+    else if (strcmp(layout, "stacking") == 0 ||
+             strcmp(layout, "stacked") == 0)
+        config.default_layout = L_STACKED;
+    else config.default_layout = L_TABBED;
+}
+
+CFGFUN(new_window, const char *windowtype, const char *border, const long width) {
+    // FIXME: when using new_float *and* new_window with different border
+    // types, this breaks because default_border_width gets overwritten.
+
+    int border_style;
+    int border_width;
+
+    if (strcmp(border, "1pixel") == 0) {
+        border_style = BS_PIXEL;
+        border_width = 1;
+    } else if (strcmp(border, "none") == 0) {
+        border_style = BS_NONE;
+        border_width = 0;
+    } else if (strcmp(border, "pixel") == 0) {
+        border_style = BS_PIXEL;
+        border_width = width;
+    } else {
+        border_style = BS_NORMAL;
+        border_width = width;
+    }
+
+    if (strcmp(windowtype, "new_window") == 0) {
+        config.default_border = border_style;
+    } else {
+        config.default_floating_border = border_style;
+    }
+
+    config.default_border_width = border_width;
+}
+
+CFGFUN(hide_edge_borders, const char *borders) {
+    if (strcmp(borders, "vertical") == 0)
+        config.hide_edge_borders = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE;
+    else if (strcmp(borders, "horizontal") == 0)
+        config.hide_edge_borders = ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE;
+    else if (strcmp(borders, "both") == 0)
+        config.hide_edge_borders = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE | ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE;
+    else if (strcmp(borders, "none") == 0)
+        config.hide_edge_borders = ADJ_NONE;
+    else if (eval_boolstr(borders))
+        config.hide_edge_borders = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE;
+    else config.hide_edge_borders = ADJ_NONE;
+}
+
+CFGFUN(focus_follows_mouse, const char *value) {
+    config.disable_focus_follows_mouse = !eval_boolstr(value);
+}
+
+CFGFUN(force_xinerama, const char *value) {
+    config.force_xinerama = eval_boolstr(value);
+}
+
+CFGFUN(force_focus_wrapping, const char *value) {
+    config.force_focus_wrapping = eval_boolstr(value);
+}
+
+CFGFUN(workspace_back_and_forth, const char *value) {
+    config.workspace_auto_back_and_forth = eval_boolstr(value);
+}
+
+CFGFUN(fake_outputs, const char *outputs) {
+    config.fake_outputs = sstrdup(outputs);
+}
+
+CFGFUN(force_display_urgency_hint, const long duration_ms) {
+    config.workspace_urgency_timer = duration_ms / 1000.0;
+}
+
+CFGFUN(workspace, const char *workspace, const char *output) {
+    DLOG("Assigning workspace \"%s\" to output \"%s\"\n", workspace, output);
+    /* 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, workspace) == 0) {
+            ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
+                 workspace);
+            assignment->output = sstrdup(output);
+            duplicate = true;
+        }
+    }
+    if (!duplicate) {
+        assignment = scalloc(sizeof(struct Workspace_Assignment));
+        assignment->name = sstrdup(workspace);
+        assignment->output = sstrdup(output);
+        TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
+    }
+}
+
+CFGFUN(ipc_socket, const char *path) {
+    config.ipc_socket_path = sstrdup(path);
+}
+
+CFGFUN(restart_state, const char *path) {
+    config.restart_state_path = sstrdup(path);
+}
+
+CFGFUN(popup_during_fullscreen, const char *value) {
+    config.popup_during_fullscreen =
+        (strcmp(value, "ignore") == 0 ? PDF_IGNORE : PDF_LEAVE_FULLSCREEN);
+}
+
+CFGFUN(color_single, const char *colorclass, const char *color) {
+    /* used for client.background only currently */
+    config.client.background = get_colorpixel(color);
+}
+
+CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator) {
+#define APPLY_COLORS(classname) \
+    do { \
+        if (strcmp(colorclass, "client." #classname) == 0) { \
+            config.client.classname.border = get_colorpixel(border); \
+            config.client.classname.background = get_colorpixel(background); \
+            config.client.classname.text = get_colorpixel(text); \
+            if (indicator != NULL) { \
+                config.client. classname .indicator = get_colorpixel(indicator); \
+            } \
+        } \
+    } while (0)
+
+    APPLY_COLORS(focused_inactive);
+    APPLY_COLORS(focused);
+    APPLY_COLORS(unfocused);
+    APPLY_COLORS(urgent);
+
+#undef APPLY_COLORS
+}
+
+CFGFUN(assign, const char *workspace) {
+    if (match_is_empty(current_match)) {
+        ELOG("Match is empty, ignoring this assignment\n");
+        return;
+    }
+    DLOG("new assignment, using above criteria, to workspace %s\n", workspace);
+    Assignment *assignment = scalloc(sizeof(Assignment));
+    match_copy(&(assignment->match), current_match);
+    assignment->type = A_TO_WORKSPACE;
+    assignment->dest.workspace = sstrdup(workspace);
+    TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+}
+
+/*******************************************************************************
+ * Bar configuration (i3bar)
+ ******************************************************************************/
+
+static Barconfig current_bar;
+
+CFGFUN(bar_font, const char *font) {
+    FREE(current_bar.font);
+    current_bar.font = sstrdup(font);
+}
+
+CFGFUN(bar_mode, const char *mode) {
+    current_bar.mode = (strcmp(mode, "hide") == 0 ? M_HIDE : M_DOCK);
+}
+
+CFGFUN(bar_output, const char *output) {
+    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] = sstrdup(output);
+    current_bar.num_outputs = new_outputs;
+}
+
+CFGFUN(bar_verbose, const char *verbose) {
+    current_bar.verbose = eval_boolstr(verbose);
+}
+
+CFGFUN(bar_modifier, const char *modifier) {
+    if (strcmp(modifier, "Mod1") == 0)
+        current_bar.modifier = M_MOD1;
+    else if (strcmp(modifier, "Mod2") == 0)
+        current_bar.modifier = M_MOD2;
+    else if (strcmp(modifier, "Mod3") == 0)
+        current_bar.modifier = M_MOD3;
+    else if (strcmp(modifier, "Mod4") == 0)
+        current_bar.modifier = M_MOD4;
+    else if (strcmp(modifier, "Mod5") == 0)
+        current_bar.modifier = M_MOD5;
+    else if (strcmp(modifier, "Control") == 0 ||
+             strcmp(modifier, "Ctrl") == 0)
+        current_bar.modifier = M_CONTROL;
+    else if (strcmp(modifier, "Shift") == 0)
+        current_bar.modifier = M_SHIFT;
+}
+
+CFGFUN(bar_position, const char *position) {
+    current_bar.position = (strcmp(position, "top") == 0 ? P_TOP : P_BOTTOM);
+}
+
+CFGFUN(bar_i3bar_command, const char *i3bar_command) {
+    FREE(current_bar.i3bar_command);
+    current_bar.i3bar_command = sstrdup(i3bar_command);
+}
+
+CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text) {
+#define APPLY_COLORS(classname) \
+    do { \
+        if (strcmp(colorclass, #classname) == 0) { \
+            if (text != NULL) { \
+                /* New syntax: border, background, text */ \
+                current_bar.colors. classname ## _border = sstrdup(border); \
+                current_bar.colors. classname ## _bg = sstrdup(background); \
+                current_bar.colors. classname ## _text = sstrdup(text); \
+            } else { \
+                /* Old syntax: text, background */ \
+                current_bar.colors. classname ## _bg = sstrdup(background); \
+                current_bar.colors. classname ## _text = sstrdup(border); \
+            } \
+        } \
+    } while (0)
+
+    APPLY_COLORS(focused_workspace);
+    APPLY_COLORS(active_workspace);
+    APPLY_COLORS(inactive_workspace);
+    APPLY_COLORS(urgent_workspace);
+
+#undef APPLY_COLORS
+}
+
+CFGFUN(bar_socket_path, const char *socket_path) {
+    FREE(current_bar.socket_path);
+    current_bar.socket_path = sstrdup(socket_path);
+}
+
+CFGFUN(bar_tray_output, const char *output) {
+    FREE(current_bar.tray_output);
+    current_bar.tray_output = sstrdup(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);
+}
+
+CFGFUN(bar_status_command, const char *command) {
+    FREE(current_bar.status_command);
+    current_bar.status_command = sstrdup(command);
+}
+
+CFGFUN(bar_workspace_buttons, const char *value) {
+    current_bar.hide_workspace_buttons = !eval_boolstr(value);
+}
+
+CFGFUN(bar_finish) {
+    DLOG("\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));
+}
diff --git a/src/config_parser.c b/src/config_parser.c
new file mode 100644 (file)
index 0000000..889179a
--- /dev/null
@@ -0,0 +1,598 @@
+#undef I3__FILE__
+#define I3__FILE__ "config_parser.c"
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * config_parser.c: hand-written parser to parse configuration directives.
+ *
+ * See also src/commands_parser.c for rationale on why we use a custom parser.
+ *
+ * This parser works VERY MUCH like src/commands_parser.c, so read that first.
+ * The differences are:
+ *
+ * 1. config_parser supports the 'number' token type (in addition to 'word' and
+ *    'string'). Numbers are referred to using &num (like $str).
+ *
+ * 2. Criteria are not executed immediately, they are just stored.
+ *
+ * 3. config_parser recognizes \n and \r as 'end' token, while commands_parser
+ *    ignores them.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "all.h"
+
+// Macros to make the YAJL API a bit easier to use.
+#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))
+
+/*******************************************************************************
+ * The data structures used for parsing. Essentially the current state and a
+ * list of tokens for that state.
+ *
+ * The GENERATED_* files are generated by generate-commands-parser.pl with the
+ * input parser-specs/configs.spec.
+ ******************************************************************************/
+
+#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"
+
+/*******************************************************************************
+ * The (small) stack where identified literals are stored during the parsing
+ * of a single command (like $workspace).
+ ******************************************************************************/
+
+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, 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 = str;
+            stack[c].type = STACK_STR;
+        } else {
+            /* Append the value. */
+            sasprintf(&(stack[c].val.str), "%s,%s", stack[c].val.str, str);
+        }
+        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 const long get_long(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.num;
+    }
+    return 0;
+}
+
+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;
+    }
+}
+
+// TODO: remove this if it turns out we don’t need it for testing.
+#if 0
+/*******************************************************************************
+ * A dynamically growing linked list which holds the criteria for the current
+ * command.
+ ******************************************************************************/
+
+typedef struct criterion {
+    char *type;
+    char *value;
+
+    TAILQ_ENTRY(criterion) criteria;
+} criterion;
+
+static TAILQ_HEAD(criteria_head, criterion) criteria =
+  TAILQ_HEAD_INITIALIZER(criteria);
+
+/*
+ * Stores the given type/value in the list of criteria.
+ * Accepts a pointer as first argument, since it is 'call'ed by the parser.
+ *
+ */
+static void push_criterion(void *unused_criteria, const char *type,
+                           const char *value) {
+    struct criterion *criterion = malloc(sizeof(struct criterion));
+    criterion->type = strdup(type);
+    criterion->value = strdup(value);
+    TAILQ_INSERT_TAIL(&criteria, criterion, criteria);
+}
+
+/*
+ * Clears the criteria linked list.
+ * Accepts a pointer as first argument, since it is 'call'ed by the parser.
+ *
+ */
+static void clear_criteria(void *unused_criteria) {
+    struct criterion *criterion;
+    while (!TAILQ_EMPTY(&criteria)) {
+        criterion = TAILQ_FIRST(&criteria);
+        free(criterion->type);
+        free(criterion->value);
+        TAILQ_REMOVE(&criteria, criterion, criteria);
+        free(criterion);
+    }
+}
+#endif
+
+/*******************************************************************************
+ * The parser itself.
+ ******************************************************************************/
+
+static cmdp_state state;
+static Match current_match;
+static struct ConfigResult subcommand_output;
+static struct ConfigResult command_output;
+
+#include "GENERATED_config_call.h"
+
+
+static void next_state(const cmdp_token *token) {
+       //printf("token = name %s identifier %s\n", token->name, token->identifier);
+       //printf("next_state = %d\n", token->next_state);
+    if (token->next_state == __CALL) {
+        subcommand_output.json_gen = command_output.json_gen;
+        GENERATED_call(token->extra.call_identifier, &subcommand_output);
+        clear_stack();
+        return;
+    }
+
+    state = token->next_state;
+    if (state == INITIAL) {
+        clear_stack();
+    }
+}
+
+/*
+ * Returns a pointer to the start of the line (one byte after the previous \r,
+ * \n) or the start of the input, if this is the first line.
+ *
+ */
+static const char *start_of_line(const char *walk, const char *beginning) {
+    while (*walk != '\n' && *walk != '\r' && walk >= beginning) {
+        walk--;
+    }
+
+    return walk + 1;
+}
+
+/*
+ * Copies the line and terminates it at the next \n, if any.
+ *
+ * The caller has to free() the result.
+ *
+ */
+static char *single_line(const char *start) {
+    char *result = sstrdup(start);
+    char *end = strchr(result, '\n');
+    if (end != NULL)
+        *end = '\0';
+    return result;
+}
+
+struct ConfigResult *parse_config(const char *input, struct context *context) {
+    /* Dump the entire config file into the debug log. We cannot just use
+     * DLOG("%s", input); because one log message must not exceed 4 KiB. */
+    const char *dumpwalk = input;
+    int linecnt = 1;
+    while (*dumpwalk != '\0') {
+        char *next_nl = strchr(dumpwalk, '\n');
+        if (next_nl != NULL) {
+            DLOG("CONFIG(line %3d): %.*s\n", linecnt, (int)(next_nl - dumpwalk), dumpwalk);
+            dumpwalk = next_nl + 1;
+        } else {
+            DLOG("CONFIG(line %3d): %s\n", linecnt, dumpwalk);
+            break;
+        }
+        linecnt++;
+    }
+    state = INITIAL;
+
+/* A YAJL JSON generator used for formatting replies. */
+#if YAJL_MAJOR >= 2
+    command_output.json_gen = yajl_gen_alloc(NULL);
+#else
+    command_output.json_gen = yajl_gen_alloc(NULL, NULL);
+#endif
+
+    y(array_open);
+
+    const char *walk = input;
+    const size_t len = strlen(input);
+    int c;
+    const cmdp_token *token;
+    bool token_handled;
+    linecnt = 1;
+
+    // TODO: make this testable
+#ifndef TEST_PARSER
+    cfg_criteria_init(&current_match, &subcommand_output, INITIAL);
+#endif
+
+    /* 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]);
+        token_handled = false;
+        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, sstrdup(token->name + 1));
+                    walk += strlen(token->name) - 1;
+                    next_state(token);
+                    token_handled = true;
+                    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;
+                next_state(token);
+                token_handled = true;
+                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);
+                    /* If we are at the end of a quoted string, skip the ending
+                     * double quote. */
+                    if (*walk == '"')
+                        walk++;
+                    next_state(token);
+                    token_handled = true;
+                    break;
+                }
+            }
+
+            if (strcmp(token->name, "end") == 0) {
+                //printf("checking for end: *%s*\n", walk);
+                if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
+                    next_state(token);
+                    token_handled = true;
+                    /* 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
+#ifndef TEST_PARSER
+                    cfg_criteria_init(&current_match, &subcommand_output, INITIAL);
+#endif
+                    linecnt++;
+                    walk++;
+                    break;
+               }
+           }
+        }
+
+        if (!token_handled) {
+            /* Figure out how much memory we will need to fill in the names of
+             * all tokens afterwards. */
+            int tokenlen = 0;
+            for (c = 0; c < ptr->n; c++)
+                tokenlen += strlen(ptr->array[c].name) + strlen("'', ");
+
+            /* Build up a decent error message. We include the problem, the
+             * full input, and underline the position where the parser
+             * currently is. */
+            char *errormessage;
+            char *possible_tokens = smalloc(tokenlen + 1);
+            char *tokenwalk = possible_tokens;
+            for (c = 0; c < ptr->n; c++) {
+                token = &(ptr->array[c]);
+                if (token->name[0] == '\'') {
+                    /* A literal is copied to the error message enclosed with
+                     * single quotes. */
+                    *tokenwalk++ = '\'';
+                    strcpy(tokenwalk, token->name + 1);
+                    tokenwalk += strlen(token->name + 1);
+                    *tokenwalk++ = '\'';
+                } else {
+                    /* Any other token is copied to the error message enclosed
+                     * with angle brackets. */
+                    *tokenwalk++ = '<';
+                    strcpy(tokenwalk, token->name);
+                    tokenwalk += strlen(token->name);
+                    *tokenwalk++ = '>';
+                }
+                if (c < (ptr->n - 1)) {
+                    *tokenwalk++ = ',';
+                    *tokenwalk++ = ' ';
+                }
+            }
+            *tokenwalk = '\0';
+            sasprintf(&errormessage, "Expected one of these tokens: %s",
+                      possible_tokens);
+            free(possible_tokens);
+
+
+            /* Go back to the beginning of the line */
+            const char *error_line = start_of_line(walk, input);
+
+            /* Contains the same amount of characters as 'input' has, but with
+             * the unparseable part highlighted using ^ characters. */
+            char *position = scalloc(strlen(error_line) + 1);
+            const char *copywalk;
+            for (copywalk = error_line;
+                 *copywalk != '\n' && *copywalk != '\r' && *copywalk != '\0';
+                 copywalk++)
+                position[(copywalk - error_line)] = (copywalk >= walk ? '^' : (*copywalk == '\t' ? '\t' : ' '));
+            position[(copywalk - error_line)] = '\0';
+
+            ELOG("CONFIG: %s\n", errormessage);
+            ELOG("CONFIG: (in file %s)\n", context->filename);
+            char *error_copy = single_line(error_line);
+
+            /* Print context lines *before* the error, if any. */
+            if (linecnt > 1) {
+                const char *context_p1_start = start_of_line(error_line-2, input);
+                char *context_p1_line = single_line(context_p1_start);
+                if (linecnt > 2) {
+                    const char *context_p2_start = start_of_line(context_p1_start-2, input);
+                    char *context_p2_line = single_line(context_p2_start);
+                    ELOG("CONFIG: Line %3d: %s\n", linecnt - 2, context_p2_line);
+                    free(context_p2_line);
+                }
+                ELOG("CONFIG: Line %3d: %s\n", linecnt - 1, context_p1_line);
+                free(context_p1_line);
+            }
+            ELOG("CONFIG: Line %3d: %s\n", linecnt, error_copy);
+            ELOG("CONFIG:           %s\n", position);
+            free(error_copy);
+            /* Print context lines *after* the error, if any. */
+            for (int i = 0; i < 2; i++) {
+                char *error_line_end = strchr(error_line, '\n');
+                if (error_line_end != NULL && *(error_line_end + 1) != '\0') {
+                    error_line = error_line_end + 1;
+                    error_copy = single_line(error_line);
+                    ELOG("CONFIG: Line %3d: %s\n", linecnt + i + 1, error_copy);
+                    free(error_copy);
+                }
+            }
+
+            context->has_errors = true;
+
+            /* Format this error message as a JSON reply. */
+            y(map_open);
+            ystr("success");
+            y(bool, false);
+            /* We set parse_error to true to distinguish this from other
+             * errors. i3-nagbar is spawned upon keypresses only for parser
+             * errors. */
+            ystr("parse_error");
+            y(bool, true);
+            ystr("error");
+            ystr(errormessage);
+            ystr("input");
+            ystr(input);
+            ystr("errorposition");
+            ystr(position);
+            y(map_close);
+
+            free(position);
+            free(errormessage);
+            clear_stack();
+            break;
+        }
+    }
+
+    y(array_close);
+
+    return &command_output;
+}
+
+/*******************************************************************************
+ * Code for building the stand-alone binary test.commands_parser which is used
+ * by t/187-commands-parser.t.
+ ******************************************************************************/
+
+#ifdef TEST_PARSER
+
+/*
+ * Logs the given message to stdout while prefixing the current time to it,
+ * but only if debug logging was activated.
+ * This is to be called by DLOG() which includes filename/linenumber
+ *
+ */
+void debuglog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    fprintf(stdout, "# ");
+    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 int criteria_next_state;
+
+void cfg_criteria_init(I3_CFG, int _state) {
+    criteria_next_state = _state;
+}
+
+void cfg_criteria_add(I3_CFG, const char *ctype, const char *cvalue) {
+}
+
+void cfg_criteria_pop_state(I3_CFG) {
+    result->next_state = criteria_next_state;
+}
+
+int main(int argc, char *argv[]) {
+    if (argc < 2) {
+        fprintf(stderr, "Syntax: %s <command>\n", argv[0]);
+        return 1;
+    }
+    struct context context;
+    context.filename = "<stdin>";
+    parse_config(argv[1], &context);
+}
+#endif
index 5df39bf10342a189f5990db96d03c2fadb263b9e..b884a1820bbeb834853f2ea301b8e362c89adf88 100644 (file)
@@ -99,7 +99,6 @@ void floating_enable(Con *con, bool automatic) {
      * otherwise. */
     Con *ws = con_get_workspace(con);
     nc->parent = ws;
-    nc->split = true;
     nc->type = CT_FLOATING_CON;
     nc->layout = L_SPLITH;
     /* We insert nc already, even though its rect is not yet calculated. This
@@ -220,17 +219,22 @@ void floating_enable(Con *con, bool automatic) {
 
     /* Sanity check: Are the coordinates on the appropriate output? If not, we
      * need to change them */
-    Output *current_output = get_output_containing(nc->rect.x, nc->rect.y);
+    Output *current_output = get_output_containing(nc->rect.x +
+        (nc->rect.width / 2), nc->rect.y + (nc->rect.height / 2));
+
     Con *correct_output = con_get_output(ws);
     if (!current_output || current_output->con != correct_output) {
         DLOG("This floating window is on the wrong output, fixing coordinates (currently (%d, %d))\n",
              nc->rect.x, nc->rect.y);
-        /* Take the relative coordinates of the current output, then add them
-         * to the coordinate space of the correct output */
-        uint32_t rel_x = (nc->rect.x - (current_output ? current_output->con->rect.x : 0));
-        uint32_t rel_y = (nc->rect.y - (current_output ? current_output->con->rect.y : 0));
-        nc->rect.x = correct_output->rect.x + rel_x;
-        nc->rect.y = correct_output->rect.y + rel_y;
+
+        /* If moving from one output to another, keep the relative position
+         * consistent (e.g. a centered dialog will remain centered). */
+        if (current_output)
+            floating_fix_coordinates(nc, &current_output->con->rect, &correct_output->rect);
+        else {
+            nc->rect.x = correct_output->rect.x;
+            nc->rect.y = correct_output->rect.y;
+        }
     }
 
     DLOG("Floating rect: (%d, %d) with %d x %d\n", nc->rect.x, nc->rect.y, nc->rect.width, nc->rect.height);
@@ -395,7 +399,7 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
     tree_render();
 
     /* Drag the window */
-    drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event);
+    drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
     tree_render();
 }
 
@@ -479,13 +483,21 @@ void floating_resize_window(Con *con, const bool proportional,
         corner |= BORDER_LEFT;
     else corner |= BORDER_RIGHT;
 
-    if (event->event_y <= (con->rect.height / 2))
+    int cursor = 0;
+    if (event->event_y <= (con->rect.height / 2)) {
         corner |= BORDER_TOP;
-    else corner |= BORDER_BOTTOM;
+        cursor = (corner & BORDER_LEFT) ?
+            XCURSOR_CURSOR_TOP_LEFT_CORNER : XCURSOR_CURSOR_TOP_RIGHT_CORNER;
+    }
+    else {
+        corner |= BORDER_BOTTOM;
+        cursor = (corner & BORDER_LEFT) ?
+            XCURSOR_CURSOR_BOTTOM_LEFT_CORNER : XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER;
+    }
 
     struct resize_window_callback_params params = { corner, proportional, event };
 
-    drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, &params);
+    drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, &params);
 }
 
 /*
@@ -497,13 +509,16 @@ void floating_resize_window(Con *con, const bool proportional,
  *
  */
 void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
-                confine_to, border_t border, callback_t callback, const void *extra)
+                confine_to, border_t border, int cursor, callback_t callback, const void *extra)
 {
     uint32_t new_x, new_y;
     Rect old_rect = { 0, 0, 0, 0 };
     if (con != NULL)
         memcpy(&old_rect, &(con->rect), sizeof(Rect));
 
+    Cursor xcursor = (cursor && xcursor_supported) ?
+        xcursor_get_cursor(cursor) : XCB_NONE;
+
     /* Grab the pointer */
     xcb_grab_pointer_cookie_t cookie;
     xcb_grab_pointer_reply_t *reply;
@@ -514,7 +529,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
         XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
         XCB_GRAB_MODE_ASYNC, /* keyboard mode */
         confine_to,          /* confine_to = in which window should the cursor stay */
-        XCB_NONE,            /* don’t display a special cursor */
+        xcursor,             /* possibly display a special cursor */
         XCB_CURRENT_TIME);
 
     if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) {
@@ -623,16 +638,18 @@ void floating_fix_coordinates(Con *con, Rect *old_rect, Rect *new_rect) {
          new_rect->x, new_rect->y, new_rect->width, new_rect->height);
     /* First we get the x/y coordinates relative to the x/y coordinates
      * of the output on which the window is on */
-    int32_t rel_x = (con->rect.x - old_rect->x);
-    int32_t rel_y = (con->rect.y - old_rect->y);
+    int32_t rel_x = con->rect.x - old_rect->x + (int32_t)(con->rect.width  / 2);
+    int32_t rel_y = con->rect.y - old_rect->y + (int32_t)(con->rect.height / 2);
     /* Then we calculate a fraction, for example 0.63 for a window
      * which is at y = 1212 of a 1920 px high output */
     DLOG("rel_x = %d, rel_y = %d, fraction_x = %f, fraction_y = %f, output->w = %d, output->h = %d\n",
           rel_x, rel_y, (double)rel_x / old_rect->width, (double)rel_y / old_rect->height,
           old_rect->width, old_rect->height);
     /* Here we have to multiply at first. Or we will lose precision when not compiled with -msse2 */
-    con->rect.x = (int32_t)new_rect->x + (double)(rel_x * (int32_t)new_rect->width)  / (int32_t)old_rect->width;
-    con->rect.y = (int32_t)new_rect->y + (double)(rel_y * (int32_t)new_rect->height) / (int32_t)old_rect->height;
+    con->rect.x = (int32_t)new_rect->x + (double)(rel_x * (int32_t)new_rect->width)
+        / (int32_t)old_rect->width - (int32_t)(con->rect.width / 2);
+    con->rect.y = (int32_t)new_rect->y + (double)(rel_y * (int32_t)new_rect->height)
+        / (int32_t)old_rect->height - (int32_t)(con->rect.height / 2);
     DLOG("Resulting coordinates: x = %d, y = %d\n", con->rect.x, con->rect.y);
 }
 
index 21a873420d989430394f7d1b25043ea76ca6e100..7fa29e126f0944cfdf0d821c7975bc05c0a4c2fe 100644 (file)
@@ -836,7 +836,13 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
     }
 
     /* Update the flag on the client directly */
-    con->urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
+    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) {
@@ -846,6 +852,9 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
             con->window->urgent.tv_usec = 0;
         }
     }
+
+    con_update_parents_urgency(con);
+
     LOG("Urgency flag changed to %d\n", con->urgent);
 
     Con *ws;
index 78e19890e52882e77485747ba813b3648ed1dc4b..81916394578325ae7d10bb9434fd978fda0d4424 100644 (file)
--- a/src/i3.mk
+++ b/src/i3.mk
@@ -50,12 +50,25 @@ src/cfgparse.tab.c: src/cfgparse.y $(i3_HEADERS_DEP)
 # and once as an object file for i3.
 src/commands_parser.o: src/commands_parser.c $(i3_HEADERS_DEP) i3-command-parser.stamp
        echo "[i3] CC $<"
-       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -o test.commands_parser $< $(LIBS) $(i3_LIBS)
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -g -o test.commands_parser $< $(LIBS) $(i3_LIBS)
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ ${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.
+src/config_parser.o: src/config_parser.c $(i3_HEADERS_DEP) i3-config-parser.stamp
+       echo "[i3] CC $<"
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -g -o test.config_parser $< $(LIBS) $(i3_LIBS)
        $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ ${canonical_path}/$<
 
 i3-command-parser.stamp: generate-command-parser.pl parser-specs/commands.spec
        echo "[i3] Generating command parser"
-       (cd include; ../generate-command-parser.pl)
+       (cd include; ../generate-command-parser.pl --input=../parser-specs/commands.spec --prefix=command)
+       touch $@
+
+i3-config-parser.stamp: generate-command-parser.pl parser-specs/config.spec
+       echo "[i3] Generating config parser"
+       (cd include; ../generate-command-parser.pl --input=../parser-specs/config.spec --prefix=config)
        touch $@
 
 i3: libi3.a $(i3_OBJECTS)
@@ -82,4 +95,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 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 src/*.gcno src/cfgparse.{output,dot,tab.h,y.o} src/cmdparse.*
index 7dfbc8713c19c9e44295441f880bf21350f7f504..5232acf2779cdb8a406f9a6a42ca8577433fb3d3 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -52,7 +52,7 @@ static bool mkdirp(const char *path) {
         ELOG("mkdir(%s) failed: %s\n", path, strerror(errno));
         return false;
     }
-    char *copy = strdup(path);
+    char *copy = sstrdup(path);
     /* strip trailing slashes, if any */
     while (copy[strlen(copy)-1] == '/')
         copy[strlen(copy)-1] = '\0';
@@ -165,7 +165,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
 
     /* provided for backwards compatibility only. */
     ystr("orientation");
-    if (!con->split)
+    if (!con_is_split(con))
         ystr("none");
     else {
         if (con_orientation(con) == HORIZ)
@@ -202,9 +202,6 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     ystr("focused");
     y(bool, (con == focused));
 
-    ystr("split");
-    y(bool, con->split);
-
     ystr("layout");
     switch (con->layout) {
         case L_DEFAULT:
@@ -266,11 +263,14 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
         case BS_NONE:
             ystr("none");
             break;
-        case BS_1PIXEL:
-            ystr("1pixel");
+        case BS_PIXEL:
+            ystr("pixel");
             break;
     }
 
+    ystr("current_border_width");
+    y(integer, con->current_border_width);
+
     dump_rect(gen, "rect", con->rect);
     dump_rect(gen, "window_rect", con->window_rect);
     dump_rect(gen, "geometry", con->geometry);
@@ -405,7 +405,7 @@ IPC_HANDLER(get_workspaces) {
 
     Con *output;
     TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
-        if (output->name[0] == '_' && output->name[1] == '_')
+        if (con_is_internal(output))
             continue;
         Con *ws;
         TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
index cce1a7127da9f660d4282e52cb8d521e743d6fa3..ca4c87ef827b1537735a1c46c407e488ef7362a9 100644 (file)
@@ -172,19 +172,17 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
             else if (strcasecmp(buf, "vertical") == 0)
                 json_node->last_split_layout = L_SPLITV;
             else LOG("Unhandled orientation: %s\n", buf);
-
-            /* What used to be an implicit check whether orientation !=
-             * NO_ORIENTATION is now a proper separate flag. */
-            if (strcasecmp(buf, "none") != 0)
-                json_node->split = true;
             free(buf);
         } else if (strcasecmp(last_key, "border") == 0) {
             char *buf = NULL;
             sasprintf(&buf, "%.*s", (int)len, val);
             if (strcasecmp(buf, "none") == 0)
                 json_node->border_style = BS_NONE;
-            else if (strcasecmp(buf, "1pixel") == 0)
-                json_node->border_style = BS_1PIXEL;
+            else if (strcasecmp(buf, "1pixel") == 0) {
+                json_node->border_style = BS_PIXEL;
+                json_node->current_border_width = 1;
+            } else if (strcasecmp(buf, "pixel") == 0)
+                json_node->border_style = BS_PIXEL;
             else if (strcasecmp(buf, "normal") == 0)
                 json_node->border_style = BS_NORMAL;
             else LOG("Unhandled \"border\": %s\n", buf);
@@ -199,11 +197,9 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
                 json_node->layout = L_STACKED;
             else if (strcasecmp(buf, "tabbed") == 0)
                 json_node->layout = L_TABBED;
-            else if (strcasecmp(buf, "dockarea") == 0) {
+            else if (strcasecmp(buf, "dockarea") == 0)
                 json_node->layout = L_DOCKAREA;
-                /* Necessary for migrating from older versions of i3. */
-                json_node->split = false;
-            } else if (strcasecmp(buf, "output") == 0)
+            else if (strcasecmp(buf, "output") == 0)
                 json_node->layout = L_OUTPUT;
             else if (strcasecmp(buf, "splith") == 0)
                 json_node->layout = L_SPLITH;
@@ -278,6 +274,9 @@ static int json_int(void *ctx, long val) {
     if (strcasecmp(last_key, "num") == 0)
         json_node->num = val;
 
+    if (strcasecmp(last_key, "current_border_width") == 0)
+        json_node->current_border_width = val;
+
     if (!parsing_swallows && strcasecmp(last_key, "id") == 0)
         json_node->old_id = val;
 
@@ -327,9 +326,6 @@ static int json_bool(void *ctx, int val) {
         to_focus = json_node;
     }
 
-    if (strcasecmp(last_key, "split") == 0)
-        json_node->split = val;
-
     if (parsing_swallows) {
         if (strcasecmp(last_key, "restart_mode") == 0)
             current_swallow->restart_mode = val;
@@ -350,11 +346,22 @@ void tree_append_json(const char *filename) {
     /* TODO: percent of other windows are not correctly fixed at the moment */
     FILE *f;
     if ((f = fopen(filename, "r")) == NULL) {
-        LOG("Cannot open file\n");
+        LOG("Cannot open file \"%s\"\n", filename);
+        return;
+    }
+    struct stat stbuf;
+    if (fstat(fileno(f), &stbuf) != 0) {
+        LOG("Cannot fstat() the file\n");
+        fclose(f);
+        return;
+    }
+    char *buf = smalloc(stbuf.st_size);
+    int n = fread(buf, 1, stbuf.st_size, f);
+    if (n != stbuf.st_size) {
+        LOG("File \"%s\" could not be read entirely, not loading.\n", filename);
+        fclose(f);
         return;
     }
-    char *buf = malloc(65535); /* TODO */
-    int n = fread(buf, 1, 65535, f);
     LOG("read %d bytes\n", n);
     yajl_gen g;
     yajl_handle hand;
index 16fa0beded1acd51cce8bc9faf3fe78370bc103d..42e714ef0bcee0f65445a557c15f7019d5453707 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -108,7 +108,7 @@ void init_logging(void) {
 #endif
         logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size);
         sasprintf(&shmlogname, "/i3-log-%d", getpid());
-        logbuffer_shm = shm_open(shmlogname, O_RDWR | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE);
+        logbuffer_shm = shm_open(shmlogname, O_RDWR | O_CREAT, S_IREAD | S_IWRITE);
         if (logbuffer_shm == -1) {
             ELOG("Could not shm_open SHM segment for the i3 log: %s\n", strerror(errno));
             return;
index 8bae9957db894e3fac02363b37e857ef49f9ad4f..6d0f80cb124c44cbd1956d89fb87da56db036eb6 100644 (file)
@@ -233,12 +233,9 @@ static void i3_exit(void) {
  *
  */
 static void handle_signal(int sig, siginfo_t *info, void *data) {
-    fprintf(stderr, "Received signal %d, terminating\n", sig);
     if (*shmlogname != '\0') {
-        fprintf(stderr, "Closing SHM log \"%s\"\n", shmlogname);
         shm_unlink(shmlogname);
     }
-    fflush(stderr);
     raise(sig);
 }
 
@@ -272,6 +269,7 @@ int main(int argc, char *argv[]) {
         {"get_socketpath", no_argument, 0, 0},
         {"fake_outputs", required_argument, 0, 0},
         {"fake-outputs", required_argument, 0, 0},
+        {"force-old-config-parser-v4.4-only", no_argument, 0, 0},
         {0, 0, 0, 0}
     };
     int option_index = 0, opt;
@@ -375,6 +373,10 @@ int main(int argc, char *argv[]) {
                     LOG("Initializing fake outputs: %s\n", optarg);
                     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;
+                    break;
                 }
                 /* fall-through */
             default:
@@ -542,6 +544,7 @@ int main(int argc, char *argv[]) {
 
     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 */
index d3f5ca8dfd98bdd8506c89759fb5ef2fda8af723..9835aa2a9637a49ae2d1055caddf2cba2e442d00 100644 (file)
@@ -172,9 +172,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
     xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS,
                     XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
-                    3 /* right mouse button */,
+                    2 /* middle mouse button */,
                     XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
 
+    xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS,
+                    XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
+                    3 /* right mouse button */,
+                    XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
 
     /* update as much information as possible so far (some replies may be NULL) */
     window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true);
@@ -343,6 +347,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
             fs != NULL) {
             LOG("There is a fullscreen window, leaving fullscreen mode\n");
             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);
         }
     }
 
index 8b6ba1d9ac52a13a1e87423330fc50de038a0710..267d6e41bf307628c058653325d94100888c7ad4 100644 (file)
@@ -69,7 +69,7 @@ Output *get_first_output(void) {
         if (output->active)
             return output;
 
-    return NULL;
+    die("No usable outputs available.\n");
 }
 
 /*
@@ -101,89 +101,76 @@ Output *get_output_containing(int x, int y) {
  *
  */
 Output *get_output_most(direction_t direction, Output *current) {
-    Output *output, *candidate = NULL;
-    int position = 0;
-    TAILQ_FOREACH(output, &outputs, outputs) {
-        if (!output->active)
-            continue;
-
-        /* Repeated calls of WIN determine the winner of the comparison */
-        #define WIN(variable, condition) \
-            if (variable condition) { \
-                candidate = output; \
-                position = variable; \
-            } \
-            break;
-
-        if (((direction == D_UP) || (direction == D_DOWN)) &&
-            (current->rect.x != output->rect.x))
-            continue;
-
-        if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
-            (current->rect.y != output->rect.y))
-            continue;
-
-        switch (direction) {
-            case D_UP:
-                WIN(output->rect.y, <= position);
-            case D_DOWN:
-                WIN(output->rect.y, >= position);
-            case D_LEFT:
-                WIN(output->rect.x, <= position);
-            case D_RIGHT:
-                WIN(output->rect.x, >= position);
-        }
-    }
-
-    assert(candidate != NULL);
-
-    return candidate;
+    Output *best = get_output_next(direction, current, FARTHEST_OUTPUT);
+    if (!best)
+        best = current;
+    DLOG("current = %s, best = %s\n", current->name, best->name);
+    return best;
 }
 
 /*
  * Gets the output which is the next one in the given direction.
  *
  */
-Output *get_output_next(direction_t direction, Output *current) {
-    Output *output, *candidate = NULL;
-
+Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far) {
+    Rect *cur = &(current->rect),
+         *other;
+    Output *output,
+           *best = NULL;
     TAILQ_FOREACH(output, &outputs, outputs) {
         if (!output->active)
             continue;
 
-        if (((direction == D_UP) || (direction == D_DOWN)) &&
-            (current->rect.x != output->rect.x))
+        other = &(output->rect);
+
+        if ((direction == D_RIGHT && other->x > cur->x) ||
+            (direction == D_LEFT  && other->x < cur->x)) {
+            /* Skip the output when it doesn’t overlap the other one’s y
+             * coordinate at all. */
+            if ((other->y + other->height) <= cur->y ||
+                (cur->y   + cur->height)   <= other->y)
+                continue;
+        } else if ((direction == D_DOWN && other->y > cur->y) ||
+                   (direction == D_UP   && other->y < cur->y)) {
+            /* Skip the output when it doesn’t overlap the other one’s x
+             * coordinate at all. */
+            if ((other->x + other->width) <= cur->x ||
+                (cur->x   + cur->width)   <= other->x)
+                continue;
+        } else
             continue;
 
-        if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
-            (current->rect.y != output->rect.y))
+        /* No candidate yet? Start with this one. */
+        if (!best) {
+            best = output;
             continue;
+        }
 
-        switch (direction) {
-            case D_UP:
-                if (output->rect.y < current->rect.y &&
-                    (!candidate || output->rect.y < candidate->rect.y))
-                    candidate = output;
-                break;
-            case D_DOWN:
-                if (output->rect.y > current->rect.y &&
-                    (!candidate || output->rect.y > candidate->rect.y))
-                    candidate = output;
-                break;
-            case D_LEFT:
-                if (output->rect.x < current->rect.x &&
-                    (!candidate || output->rect.x > candidate->rect.x))
-                    candidate = output;
-                break;
-            case D_RIGHT:
-                if (output->rect.x > current->rect.x &&
-                    (!candidate || output->rect.x < candidate->rect.x))
-                    candidate = output;
-                break;
+        if (close_far == CLOSEST_OUTPUT) {
+            /* Is this output better (closer to the current output) than our
+             * current best bet? */
+            if ((direction == D_RIGHT && other->x < best->rect.x) ||
+                (direction == D_LEFT  && other->x > best->rect.x) ||
+                (direction == D_DOWN  && other->y < best->rect.y) ||
+                (direction == D_UP    && other->y > best->rect.y)) {
+                best = output;
+                continue;
+            }
+        } else {
+            /* Is this output better (farther to the current output) than our
+             * current best bet? */
+            if ((direction == D_RIGHT && other->x > best->rect.x) ||
+                (direction == D_LEFT  && other->x < best->rect.x) ||
+                (direction == D_DOWN  && other->y > best->rect.y) ||
+                (direction == D_UP    && other->y < best->rect.y)) {
+                best = output;
+                continue;
+            }
         }
     }
 
-    return candidate;
+    DLOG("current = %s, best = %s\n", current->name, (best ? best->name : "NULL"));
+    return best;
 }
 
 /*
@@ -660,8 +647,7 @@ void randr_query_outputs(void) {
             output->active = false;
             DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
 
-            if ((first = get_first_output()) == NULL)
-                die("No usable outputs available\n");
+            first = get_first_output();
 
             /* TODO: refactor the following code into a nice function. maybe
              * use an on_destroy callback which is implement differently for
@@ -749,6 +735,9 @@ void randr_query_outputs(void) {
         disable_randr(conn);
     }
 
+    /* Verifies that there is at least one active output as a side-effect. */
+    get_first_output();
+
     /* Just go through each active output and assign one workspace */
     TAILQ_FOREACH(output, &outputs, outputs) {
         if (!output->active)
index da993a57206ee13fa130616093dbc1e88fd91f38..1216241b49af184a8e2ef45cdf997768b6f54af0 100644 (file)
@@ -225,7 +225,7 @@ void render_con(Con *con, bool render_fullscreen) {
 
     if (con->layout == L_OUTPUT) {
         /* Skip i3-internal outputs */
-        if (con->name[0] == '_' && con->name[1] == '_')
+        if (con_is_internal(con))
             return;
         render_l_output(con);
     } else if (con->type == CT_ROOT) {
@@ -240,36 +240,34 @@ void render_con(Con *con, bool render_fullscreen) {
          * windows/containers so that they overlap on another output. */
         DLOG("Rendering floating windows:\n");
         TAILQ_FOREACH(output, &(con->nodes_head), nodes) {
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             /* Get the active workspace of that output */
             Con *content = output_get_content(output);
             Con *workspace = TAILQ_FIRST(&(content->focus_head));
-
-            /* Check for (floating!) fullscreen nodes */
-            /* XXX: This code duplication is unfortunate. Keep in mind to fix
-             * this when we clean up the whole render.c */
-            Con *fullscreen = NULL;
-            fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
-            if (fullscreen) {
-                /* Either the fullscreen window is inside the floating
-                 * container, then we need to render and raise it now… */
-                if (con_inside_floating(fullscreen)) {
-                    fullscreen->rect = output->rect;
-                    x_raise_con(fullscreen);
-                    render_con(fullscreen, true);
-                    continue;
-                } else {
-                    /* …or it’s a tiling window, in which case the floating
-                     * windows should not overlap it, so we skip rendering this
-                     * output. */
-                    continue;
-                }
-            }
-
+            Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
             Con *child;
             TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) {
-                DLOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height);
+                /* 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) {
+                    Con *floating_child = con_descend_focused(child);
+                    /* 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)
+                        continue;
+                    else {
+                        DLOG("Rendering floating child even though in fullscreen mode: "
+                             "floating->transient_for (0x%08x) == fullscreen->id (0x%08x)\n",
+                             floating_child->window->transient_for, fullscreen->window->id);
+                    }
+                }
+                DLOG("floating child at (%d,%d) with %d x %d\n",
+                     child->rect.x, child->rect.y, child->rect.width, child->rect.height);
                 x_raise_con(child);
                 render_con(child, false);
             }
@@ -324,7 +322,7 @@ void render_con(Con *con, bool render_fullscreen) {
             child->deco_rect.width = child->rect.width;
             child->deco_rect.height = deco_height;
 
-            if (children > 1 || (child->border_style != BS_1PIXEL && child->border_style != BS_NONE)) {
+            if (children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) {
                 child->rect.y += (deco_height * children);
                 child->rect.height -= (deco_height * children);
             }
@@ -341,12 +339,12 @@ void render_con(Con *con, bool render_fullscreen) {
             child->deco_rect.x = x - con->rect.x + i * child->deco_rect.width;
             child->deco_rect.y = y - con->rect.y;
 
-            if (children > 1 || (child->border_style != BS_1PIXEL && child->border_style != BS_NONE)) {
+            if (children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) {
                 child->rect.y += deco_height;
                 child->rect.height -= deco_height;
                 child->deco_rect.height = deco_height;
             } else {
-                child->deco_rect.height = (child->border_style == BS_1PIXEL ? 1 : 0);
+                child->deco_rect.height = (child->border_style == BS_PIXEL ? 1 : 0);
             }
         }
 
index b65344a276c5edbefdee04f260e0856d45ca9441..268dc3fbd119b69780bcbee89f7d47cc195829d2 100644 (file)
@@ -106,7 +106,7 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
 
     const struct callback_params params = { orientation, output, helpwin, &new_position };
 
-    drag_pointer(NULL, event, grabwin, BORDER_TOP, resize_callback, &params);
+    drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, &params);
 
     xcb_destroy_window(conn, helpwin);
     xcb_destroy_window(conn, grabwin);
index 16e26ceebe32b0964380b039e8a710fabe9274c8..7b30909554040360233317dd7f70bdeedb9ff28b 100644 (file)
@@ -39,11 +39,17 @@ void scratchpad_move(Con *con) {
         return;
     }
 
-    /* 1: Ensure the window is floating. From now on, we deal with the
-     * CT_FLOATING_CON. We use automatic == false because the user made the
-     * choice that this window should be a scratchpad (and floating). */
-    floating_enable(con, false);
-    con = con->parent;
+    /* 1: Ensure the window or any parent is floating. From now on, we deal
+     * with the CT_FLOATING_CON. We use automatic == false because the user
+     * made the choice that this window should be a scratchpad (and floating).
+     */
+    Con *maybe_floating_con = con_inside_floating(con);
+    if (maybe_floating_con == NULL) {
+        floating_enable(con, false);
+        con = con->parent;
+    } else {
+        con = maybe_floating_con;
+    }
 
     /* 2: Send the window to the __i3_scratch workspace, mainting its
      * coordinates and not warping the pointer. */
index 89324dbd250f81a84f0727f9eee95cc3b2621696..ee51664fa3dd7ab360ed5674d87667df772fb018 100644 (file)
@@ -58,7 +58,7 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) {
 }
 
 /*
- * Some applications (such as Firefox) mark a startup sequence as completede
+ * Some applications (such as Firefox) mark a startup sequence as completed
  * *before* they even map a window. Therefore, we cannot entirely delete the
  * startup sequence once it’s marked as complete. Instead, we’ll mark it for
  * deletion in 30 seconds and use that chance to delete old sequences.
@@ -68,15 +68,10 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) {
  * the root window cursor.
  *
  */
-static int _delete_startup_sequence(struct Startup_Sequence *sequence) {
+static int _prune_startup_sequences(void) {
     time_t current_time = time(NULL);
     int active_sequences = 0;
 
-    /* Mark the given sequence for deletion in 30 seconds. */
-    sequence->delete_at = current_time + 30;
-    DLOG("Will delete startup sequence %s at timestamp %ld\n",
-         sequence->id, sequence->delete_at);
-
     /* Traverse the list and delete everything which was marked for deletion 30
      * seconds ago or earlier. */
     struct Startup_Sequence *current, *next;
@@ -94,20 +89,35 @@ static int _delete_startup_sequence(struct Startup_Sequence *sequence) {
         if (current_time <= current->delete_at)
             continue;
 
-        DLOG("Deleting startup sequence %s, delete_at = %ld, current_time = %ld\n",
-             current->id, current->delete_at, current_time);
-
-        /* Unref the context, will be free()d */
-        sn_launcher_context_unref(current->context);
-
-        /* Delete our internal sequence */
-        TAILQ_REMOVE(&startup_sequences, current, sequences);
+        startup_sequence_delete(current);
     }
 
     return active_sequences;
 
 }
 
+/**
+ * Deletes a startup sequence, ignoring whether its timeout has elapsed.
+ * Useful when e.g. a window is moved between workspaces and its children
+ * shouldn't spawn on the original workspace.
+ *
+ */
+void startup_sequence_delete(struct Startup_Sequence *sequence) {
+    assert(sequence != NULL);
+    DLOG("Deleting startup sequence %s, delete_at = %ld, current_time = %ld\n",
+         sequence->id, sequence->delete_at, time(NULL));
+
+    /* Unref the context, will be free()d */
+    sn_launcher_context_unref(sequence->context);
+
+    /* Delete our internal sequence */
+    TAILQ_REMOVE(&startup_sequences, sequence, sequences);
+
+    free(sequence->id);
+    free(sequence->workspace);
+    FREE(sequence);
+}
+
 /*
  * Starts the given application by passing it through a shell. We use double fork
  * to avoid zombie processes. As the started application’s parent exits (immediately),
@@ -233,7 +243,13 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) {
         case SN_MONITOR_EVENT_COMPLETED:
             DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(snsequence));
 
-            if (_delete_startup_sequence(sequence) == 0) {
+            /* Mark the given sequence for deletion in 30 seconds. */
+            time_t current_time = time(NULL);
+            sequence->delete_at = current_time + 30;
+            DLOG("Will delete startup sequence %s at timestamp %ld\n",
+                 sequence->id, sequence->delete_at);
+
+            if (_prune_startup_sequences() == 0) {
                 DLOG("No more startup sequences running, changing root window cursor to default pointer.\n");
                 /* Change the pointer of the root window to indicate progress */
                 if (xcursor_supported)
@@ -247,32 +263,44 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) {
     }
 }
 
-/*
- * Checks if the given window belongs to a startup notification by checking if
- * the _NET_STARTUP_ID property is set on the window (or on its leader, if it’s
- * unset).
- *
- * If so, returns the workspace on which the startup was initiated.
- * Returns NULL otherwise.
+/**
+ * Gets the stored startup sequence for the _NET_STARTUP_ID of a given window.
  *
  */
-char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply) {
+struct Startup_Sequence *startup_sequence_get(i3Window *cwindow,
+    xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader) {
     /* The _NET_STARTUP_ID is only needed during this function, so we get it
      * here and don’t save it in the 'cwindow'. */
     if (startup_id_reply == NULL || xcb_get_property_value_length(startup_id_reply) == 0) {
         FREE(startup_id_reply);
-        DLOG("No _NET_STARTUP_ID set on this window\n");
+        DLOG("No _NET_STARTUP_ID set on window 0x%08x\n", cwindow->id);
         if (cwindow->leader == XCB_NONE)
             return NULL;
 
-        xcb_get_property_cookie_t cookie;
-        cookie = xcb_get_property(conn, false, cwindow->leader, A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
+        /* This is a special case that causes the leader's startup sequence
+         * to only be returned if it has never been mapped, useful primarily
+         * when trying to delete a sequence.
+         *
+         * It's generally inappropriate to delete a leader's sequence when
+         * moving a child window, but if the leader has no container, it's
+         * likely permanently unmapped and the child is the "real" window. */
+        if (ignore_mapped_leader && con_by_window_id(cwindow->leader) != NULL) {
+            DLOG("Ignoring leader window 0x%08x\n", cwindow->leader);
+            return NULL;
+        }
+
         DLOG("Checking leader window 0x%08x\n", cwindow->leader);
+
+        xcb_get_property_cookie_t cookie;
+
+        cookie = xcb_get_property(conn, false, cwindow->leader,
+            A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
         startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
 
-        if (startup_id_reply == NULL || xcb_get_property_value_length(startup_id_reply) == 0) {
-            DLOG("No _NET_STARTUP_ID set on the leader either\n");
+        if (startup_id_reply == NULL ||
+            xcb_get_property_value_length(startup_id_reply) == 0) {
             FREE(startup_id_reply);
+            DLOG("No _NET_STARTUP_ID set on the leader either\n");
             return NULL;
         }
     }
@@ -304,5 +332,31 @@ char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *
 
     free(startup_id);
     free(startup_id_reply);
+
+    return sequence;
+}
+
+/*
+ * Checks if the given window belongs to a startup notification by checking if
+ * the _NET_STARTUP_ID property is set on the window (or on its leader, if it’s
+ * unset).
+ *
+ * If so, returns the workspace on which the startup was initiated.
+ * Returns NULL otherwise.
+ *
+ */
+char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply) {
+    struct Startup_Sequence *sequence = startup_sequence_get(cwindow, startup_id_reply, false);
+    if (sequence == NULL)
+        return NULL;
+
+    /* If the startup sequence's time span has elapsed, delete it. */
+    time_t current_time = time(NULL);
+    if (sequence->delete_at > 0 && current_time > sequence->delete_at) {
+        DLOG("Deleting expired startup sequence %s\n", sequence->id);
+        startup_sequence_delete(sequence);
+        return NULL;
+    }
+
     return sequence->workspace;
 }
index 321bc78a5951b2b9fa13c78819301f53049b5d49..7a5fb9f010081c7a24f28fbac1673b0471a4583b 100644 (file)
@@ -255,6 +255,15 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
     x_con_kill(con);
 
     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));
+        ev_timer_stop(main_loop, con->urgency_timer);
+        FREE(con->urgency_timer);
+    }
+
     if (con->type != CT_FLOATING_CON) {
         /* If the container is *not* floating, we might need to re-distribute
          * percentage values for the resized containers. */
@@ -347,6 +356,10 @@ void tree_split(Con *con, orientation_t orientation) {
         con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
         return;
     }
+    else if (con->type == CT_FLOATING_CON) {
+        DLOG("Floating containers can't be split.\n");
+        return;
+    }
 
     Con *parent = con->parent;
 
@@ -373,7 +386,6 @@ void tree_split(Con *con, orientation_t orientation) {
     TAILQ_REPLACE(&(parent->focus_head), con, new, focused);
     new->parent = parent;
     new->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
-    new->split = true;
 
     /* 3: swap 'percent' (resize factor) */
     new->percent = con->percent;
@@ -388,9 +400,16 @@ void tree_split(Con *con, orientation_t orientation) {
  *
  */
 bool level_up(void) {
+    /* Skip over floating containers and go directly to the grandparent
+     * (which should always be a workspace) */
+    if (focused->parent->type == CT_FLOATING_CON) {
+        con_focus(focused->parent->parent);
+        return true;
+    }
+
     /* We can focus up to the workspace, but not any higher in the tree */
     if ((focused->parent->type != CT_CON &&
-        focused->parent->type != CT_WORKSPACE) ||
+         focused->parent->type != CT_WORKSPACE) ||
         focused->type == CT_WORKSPACE) {
         ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n");
         return false;
@@ -407,9 +426,21 @@ bool level_down(void) {
     /* Go down the focus stack of the current node */
     Con *next = TAILQ_FIRST(&(focused->focus_head));
     if (next == TAILQ_END(&(focused->focus_head))) {
-        printf("cannot go down\n");
+        DLOG("cannot go down\n");
         return false;
     }
+    else if (next->type == CT_FLOATING_CON) {
+        /* Floating cons shouldn't be directly focused; try immediately
+         * going to the grandchild of the focused con. */
+        Con *child = TAILQ_FIRST(&(next->focus_head));
+        if (child == TAILQ_END(&(next->focus_head))) {
+            DLOG("cannot go down\n");
+            return false;
+        }
+        else
+            next = TAILQ_FIRST(&(next->focus_head));
+    }
+
     con_focus(next);
     return true;
 }
@@ -454,9 +485,20 @@ void tree_render(void) {
  *
  */
 static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) {
+    /* When dealing with fullscreen containers, it's necessary to go up to the
+     * workspace level, because 'focus $dir' will start at the con's real
+     * position in the tree, and it may not be possible to get to the edge
+     * normally due to fullscreen focusing restrictions. */
+    if (con->fullscreen_mode == CF_OUTPUT && con->type != CT_WORKSPACE)
+        con = con_get_workspace(con);
+
     /* Stop recursing at workspaces after attempting to switch to next
      * workspace if possible. */
     if (con->type == CT_WORKSPACE) {
+        if (con_get_fullscreen_con(con, CF_GLOBAL)) {
+            DLOG("Cannot change workspace while in global fullscreen mode.\n");
+            return false;
+        }
         Output *current_output = get_output_containing(con->rect.x, con->rect.y);
         Output *next_output;
 
@@ -477,7 +519,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
         else
             return false;
 
-        next_output = get_output_next(direction, current_output);
+        next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
         if (!next_output)
             return false;
         DLOG("Next output is %s\n", next_output->name);
@@ -491,6 +533,13 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
             return false;
 
         workspace_show(workspace);
+
+        /* If a workspace has an active fullscreen container, one of its
+         * children should always be focused. The above workspace_show()
+         * should be adequate for that, so return. */
+        if (con_get_fullscreen_con(workspace, CF_OUTPUT))
+            return true;
+
         Con *focus = con_descend_direction(workspace, direction);
         if (focus) {
             con_focus(focus);
@@ -617,8 +666,8 @@ void tree_flatten(Con *con) {
 
     /* The child must have a different orientation than the con but the same as
      * the con’s parent to be redundant */
-    if (!con->split ||
-        !child->split ||
+    if (!con_is_split(con) ||
+        !con_is_split(child) ||
         con_orientation(con) == con_orientation(child) ||
         con_orientation(child) != con_orientation(parent))
         goto recurse;
index 2b7b19e5e66bb8ecfbbc607525f7170935687b05..872ec768d99ad64038d35df85031db72bf463145 100644 (file)
@@ -312,12 +312,30 @@ static void workspace_reassign_sticky(Con *con) {
         workspace_reassign_sticky(current);
 }
 
+/*
+ * Callback to reset the urgent flag of the given con to false. May be started by
+ * _workspace_show to avoid urgency hints being lost by switching to a workspace
+ * focusing the con.
+ *
+ */
+static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) {
+    Con *con = w->data;
+
+    DLOG("Resetting urgency flag of con %p by timer\n", con);
+    con->urgent = false;
+    con_update_parents_urgency(con);
+    workspace_update_urgent_flag(con_get_workspace(con));
+    tree_render();
+
+    ev_timer_stop(main_loop, con->urgency_timer);
+    FREE(con->urgency_timer);
+}
 
 static void _workspace_show(Con *workspace) {
     Con *current, *old = NULL;
 
     /* safe-guard against showing i3-internal workspaces like __i3_scratch */
-    if (workspace->name[0] == '_' && workspace->name[1] == '_')
+    if (con_is_internal(workspace))
         return;
 
     /* disable fullscreen for the other workspaces and get the workspace we are
@@ -348,9 +366,47 @@ static void _workspace_show(Con *workspace) {
 
     workspace_reassign_sticky(workspace);
 
-    LOG("switching to %p\n", workspace);
+    DLOG("switching to %p / %s\n", workspace, workspace->name);
     Con *next = con_descend_focused(workspace);
 
+    /* Memorize current output */
+    Con *old_output = con_get_output(focused);
+
+    /* Display urgency hint for a while if the newly visible workspace would
+     * focus and thereby immediately destroy it */
+    if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) {
+        /* focus for now… */
+        con_focus(next);
+
+        /* … but immediately reset urgency flags; they will be set to false by
+         * the timer callback in case the container is focused at the time of
+         * its expiration */
+        focused->urgent = true;
+        workspace->urgent = true;
+
+        if (focused->urgency_timer == NULL) {
+            DLOG("Deferring reset of urgency flag of con %p on newly shown workspace %p\n",
+                    focused, workspace);
+            focused->urgency_timer = scalloc(sizeof(struct ev_timer));
+            /* use a repeating timer to allow for easy resets */
+            ev_timer_init(focused->urgency_timer, workspace_defer_update_urgent_hint_cb,
+                    config.workspace_urgency_timer, config.workspace_urgency_timer);
+            focused->urgency_timer->data = focused;
+            ev_timer_start(main_loop, focused->urgency_timer);
+        } else {
+            DLOG("Resetting urgency timer of con %p on workspace %p\n",
+                    focused, workspace);
+            ev_timer_again(main_loop, focused->urgency_timer);
+        }
+    } else
+        con_focus(next);
+
+    DLOG("old = %p / %s\n", old, (old ? old->name : "(null)"));
+    /* Close old workspace if necessary. This must be done *after* doing
+     * urgency handling, because tree_close() will do a con_focus() on the next
+     * client, which will clear the urgency flag too early. Also, there is no
+     * way for con_focus() to know about when to clear urgency immediately and
+     * when to defer it. */
     if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
         /* check if this workspace is currently visible */
         if (!workspace_is_visible(old)) {
@@ -360,10 +416,6 @@ static void _workspace_show(Con *workspace) {
         }
     }
 
-    /* Memorize current output */
-    Con *old_output = con_get_output(focused);
-
-    con_focus(next);
     workspace->fullscreen_mode = CF_OUTPUT;
     LOG("focused now = %p / %s\n", focused, focused->name);
 
@@ -393,8 +445,7 @@ void workspace_show(Con *workspace) {
  */
 void workspace_show_by_name(const char *num) {
     Con *workspace;
-    bool changed_num_workspaces;
-    workspace = workspace_get(num, &changed_num_workspaces);
+    workspace = workspace_get(num, NULL);
     _workspace_show(workspace);
 }
 
@@ -414,7 +465,7 @@ Con* workspace_next(void) {
         /* If currently a numbered workspace, find next numbered workspace. */
         TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
             /* Skip outputs starting with __, they are internal. */
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             NODES_FOREACH(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
@@ -435,7 +486,7 @@ Con* workspace_next(void) {
         bool found_current = false;
         TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
             /* Skip outputs starting with __, they are internal. */
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             NODES_FOREACH(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
@@ -454,7 +505,7 @@ Con* workspace_next(void) {
     if (!next) {
         TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
             /* Skip outputs starting with __, they are internal. */
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             NODES_FOREACH(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
@@ -486,7 +537,7 @@ Con* workspace_prev(void) {
         /* If numbered workspace, find previous numbered workspace. */
         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
             /* Skip outputs starting with __, they are internal. */
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             NODES_FOREACH_REVERSE(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE || child->num == -1)
@@ -505,7 +556,7 @@ Con* workspace_prev(void) {
         bool found_current = false;
         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
             /* Skip outputs starting with __, they are internal. */
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             NODES_FOREACH_REVERSE(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
@@ -524,7 +575,7 @@ Con* workspace_prev(void) {
     if (!prev) {
         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
             /* Skip outputs starting with __, they are internal. */
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             NODES_FOREACH_REVERSE(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
@@ -665,6 +716,22 @@ void workspace_back_and_forth(void) {
     workspace_show_by_name(previous_workspace_name);
 }
 
+/*
+ * Returns the previously focused workspace con, or NULL if unavailable.
+ *
+ */
+Con *workspace_back_and_forth_get(void) {
+    if (!previous_workspace_name) {
+        DLOG("no previous workspace name set.");
+        return NULL;
+    }
+
+    Con *workspace;
+    workspace = workspace_get(previous_workspace_name, NULL);
+
+    return workspace;
+}
+
 static bool get_urgency_flag(Con *con) {
     Con *child;
     TAILQ_FOREACH(child, &(con->nodes_head), nodes)
@@ -701,7 +768,6 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
     /* 1: create a new split container */
     Con *split = con_new(NULL, NULL);
     split->parent = ws;
-    split->split = true;
 
     /* 2: copy layout from workspace */
     split->layout = ws->layout;
@@ -753,7 +819,6 @@ Con *workspace_attach_to(Con *ws) {
     /* 1: create a new split container */
     Con *new = con_new(NULL, NULL);
     new->parent = ws;
-    new->split = true;
 
     /* 2: set the requested layout on the split con */
     new->layout = ws->workspace_layout;
@@ -764,3 +829,34 @@ Con *workspace_attach_to(Con *ws) {
 
     return new;
 }
+
+/**
+ * Creates a new container and re-parents all of children from the given
+ * workspace into it.
+ *
+ * The container inherits the layout from the workspace.
+ */
+Con *workspace_encapsulate(Con *ws) {
+    if (TAILQ_EMPTY(&(ws->nodes_head))) {
+        ELOG("Workspace %p / %s has no children to encapsulate\n", ws, ws->name);
+        return NULL;
+    }
+
+    Con *new = con_new(NULL, NULL);
+    new->parent = ws;
+    new->layout = ws->layout;
+
+    DLOG("Moving children of workspace %p / %s into container %p\n",
+        ws, ws->name, new);
+
+    Con *child;
+    while (!TAILQ_EMPTY(&(ws->nodes_head))) {
+        child = TAILQ_FIRST(&(ws->nodes_head));
+        con_detach(child);
+        con_attach(child, new, true);
+    }
+
+    con_attach(new, ws, true);
+
+    return new;
+}
diff --git a/src/x.c b/src/x.c
index ba157e2b56a494c367633bd73a023d775a3744f5..78c6de33b243d8735af613b19719719364f70a7a 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -432,7 +432,7 @@ void x_draw_decoration(Con *con) {
             xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &bottomline);
         }
         /* 1pixel border needs an additional line at the top */
-        if (p->border_style == BS_1PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
+        if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
             xcb_rectangle_t topline = { br.x, 0, con->rect.width + br.width + br.x, br.y };
             xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline);
         }
@@ -468,12 +468,20 @@ void x_draw_decoration(Con *con) {
     /* 5: draw two unconnected 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;
+    int deco_diff_r = 2;
+    if (parent->layout == L_TABBED) {
+        if (TAILQ_PREV(con, nodes_head, nodes) != NULL)
+            deco_diff_l = 0;
+        if (TAILQ_NEXT(con, nodes) != NULL)
+            deco_diff_r = 0;
+    }
     xcb_segment_t segments[] = {
         { dr->x,                 dr->y,
           dr->x + dr->width - 1, dr->y },
 
-        { dr->x + 2,             dr->y + dr->height - 1,
-          dr->x + dr->width - 3, dr->y + dr->height - 1 }
+        { dr->x + deco_diff_l,                 dr->y + dr->height - 1,
+          dr->x - deco_diff_r + dr->width - 1, dr->y + dr->height - 1 }
     };
     xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments);
 
@@ -482,16 +490,27 @@ void x_draw_decoration(Con *con) {
     int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
 
     struct Window *win = con->window;
-    if (win == NULL || win->name == NULL) {
-        /* this is a non-leaf container, we need to make up a good description */
-        // TODO: use a good description instead of just "another container"
-        draw_text_ascii("another container",
+    if (win == NULL) {
+        /* we have a split container which gets a representation
+         * of its children as title
+         */
+        char *title;
+        char *tree = con_get_tree_representation(con);
+        sasprintf(&title, "i3: %s", tree);
+        free(tree);
+
+        draw_text_ascii(title,
                 parent->pixmap, parent->pm_gc,
                 con->deco_rect.x + 2, con->deco_rect.y + text_offset_y,
                 con->deco_rect.width - 2);
+        free(title);
+
         goto copy_pixmaps;
     }
 
+    if (win->name == NULL)
+        goto copy_pixmaps;
+
     int indent_level = 0,
         indent_mult = 0;
     Con *il_parent = parent;
index 7683b0d37ed8fd76c0330200b4650440ea8b53ab..90fd69dd237a67cc7470f238af33a693e6175aad 100644 (file)
@@ -34,10 +34,15 @@ static Cursor load_cursor(const char *name) {
 }
 
 void xcursor_load_cursors(void) {
-    cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr");
-    cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow");
-    cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow");
-    cursors[XCURSOR_CURSOR_WATCH] = load_cursor("watch");
+    cursors[XCURSOR_CURSOR_POINTER]             = load_cursor("left_ptr");
+    cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL]   = load_cursor("sb_h_double_arrow");
+    cursors[XCURSOR_CURSOR_RESIZE_VERTICAL]     = load_cursor("sb_v_double_arrow");
+    cursors[XCURSOR_CURSOR_WATCH]               = load_cursor("watch");
+    cursors[XCURSOR_CURSOR_MOVE]                = load_cursor("fleur");
+    cursors[XCURSOR_CURSOR_TOP_LEFT_CORNER]     = load_cursor("top_left_corner");
+    cursors[XCURSOR_CURSOR_TOP_RIGHT_CORNER]    = load_cursor("top_right_corner");
+    cursors[XCURSOR_CURSOR_BOTTOM_LEFT_CORNER]  = load_cursor("bottom_left_corner");
+    cursors[XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER] = load_cursor("bottom_right_corner");
 }
 
 /*
index b1e698ae9182395efcd68f7f4f4edb1d9ee9fd31..28036d1fd390dae998e3de8548e240edcace0514 100755 (executable)
@@ -8,11 +8,12 @@ WriteMakefile(
     MIN_PERL_VERSION => '5.010000', # 5.10.0
     PREREQ_PM => {
         'AnyEvent'     => 0,
-        'AnyEvent::I3' => '0.09',
+        'AnyEvent::I3' => '0.14',
         'X11::XCB'     => '0.03',
         'Inline'       => 0,
         'ExtUtils::PkgConfig' => 0,
         'Test::More'   => '0.94',
+        'IPC::Run' => 0,
     },
     PM => {}, # do not install any files from this directory
     clean => {
index 5ea9d0783777d8f02f265eb7a83375f30a4426b3..d73bb3327d242344f641930d8e34534d5ec725f5 100755 (executable)
@@ -66,6 +66,22 @@ my $result = GetOptions(
 
 pod2usage(-verbose => 2, -exitcode => 0) if $help;
 
+# Check for missing executables
+my @binaries = qw(
+                   ../i3
+                   ../i3bar/i3bar
+                   ../i3-config-wizard/i3-config-wizard
+                   ../i3-dump-log/i3-dump-log
+                   ../i3-input/i3-input
+                   ../i3-msg/i3-msg
+                   ../i3-nagbar/i3-nagbar
+               );
+
+foreach my $binary (@binaries) {
+    die "$binary executable not found" unless -e $binary;
+    die "$binary is not an executable" unless -x $binary;
+}
+
 @displays = split(/,/, join(',', @displays));
 @displays = map { s/ //g; $_ } @displays;
 
diff --git a/testcases/new-test b/testcases/new-test
new file mode 100755 (executable)
index 0000000..2849301
--- /dev/null
@@ -0,0 +1,100 @@
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+# © 2012 Michael Stapelberg and contributors
+# Script to create a new testcase from a template.
+#
+#     # Create (and edit) a new test for moving floating windows
+#     ./new-test floating move
+#
+#     # Create (and edit) a multi-monitor test for moving workspaces
+#     ./new-test -m move workspaces
+
+use strict;
+use warnings;
+use File::Basename qw(basename);
+use Getopt::Long;
+use v5.10;
+
+my $multi_monitor;
+
+my $result = GetOptions(
+    'multi-monitor|mm' => \$multi_monitor
+);
+
+my $testname = join(' ', @ARGV);
+$testname =~ s/ /-/g;
+
+my $header = <<'EOF';
+#!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)
+#
+# TODO: Description of this file.
+# Ticket: #999
+# Bug still in: #GITREV#
+EOF
+
+# Figure out the next test filename.
+my @files;
+if ($multi_monitor) {
+    @files = grep { basename($_) =~ /^5/ } <t/*.t>;
+} else {
+    @files = grep { basename($_) !~ /^5/ } <t/*.t>;
+}
+my ($latest) = sort { $b cmp $a } @files;
+my ($num) = (basename($latest) =~ /^([0-9]+)/);
+my $filename = "t/" . ($num+1) . "-" . lc($testname) . ".t";
+
+# Get the current git revision.
+chomp(my $gitrev = qx(git describe --tags));
+$header =~ s/#GITREV#/$gitrev/g;
+
+# Create the file based on our template.
+open(my $fh, '>', $filename);
+print $fh $header;
+if ($multi_monitor) {
+    print $fh <<'EOF';
+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
+
+
+
+exit_gracefully($pid);
+
+done_testing;
+EOF
+} else {
+    print $fh <<'EOF';
+use i3test;
+
+
+done_testing;
+EOF
+}
+close($fh);
+
+my $editor = $ENV{EDITOR};
+$editor ||= 'vi';
+exec $editor, $filename;
index 81a97d062156f603b539a1733816e89fff2b6381..cec7000aa9146ea0ef8de534554c88b24c96fae3 100644 (file)
@@ -22,7 +22,10 @@ my $i3 = i3(get_socket_path());
 my $tmp = fresh_workspace;
 
 sub fullscreen_windows {
-    scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($tmp)}
+    my $ws = $tmp;
+    $ws = shift if @_;
+
+    scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)}
 }
 
 # get the output of this workspace
@@ -188,4 +191,27 @@ is($x->input_focus, $window->id, 'fullscreen window focused');
 cmd 'focus left';
 is($x->input_focus, $window->id, 'fullscreen window still focused');
 
+################################################################################
+# Verify that fullscreening a window on a second workspace and moving it onto
+# the first workspace unfullscreens the first window.
+################################################################################
+
+my $tmp2 = fresh_workspace;
+$swindow = open_window;
+
+cmd 'fullscreen';
+
+is(fullscreen_windows($tmp2), 1, 'one fullscreen window on second ws');
+
+cmd "move workspace $tmp";
+
+is(fullscreen_windows($tmp2), 0, 'no fullscreen windows on second ws');
+is(fullscreen_windows($tmp), 1, 'one fullscreen window on first ws');
+
+$swindow->fullscreen(0);
+sync_with_i3;
+
+# Verify that $swindow was the one that initially remained fullscreen.
+is(fullscreen_windows($tmp), 0, 'no fullscreen windows on first ws');
+
 done_testing;
index 10368532eeae60c3eb8431efcc354e83f195cf45..ff44e0ea0bc263ed52d29b0a789212c97ae1809f 100644 (file)
 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
 #   (unless you are already familiar with Perl)
 
-use i3test;
+use i3test i3_autostart => 0;
 use List::Util qw(first);
 
+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;
 
 #####################################################################
@@ -145,4 +153,84 @@ is($x->input_focus, $bottom->id, 'oldest urgent window focused');
 $bottom->delete_hint('urgency');
 sync_with_i3;
 
+################################################################################
+# Check if urgent flag gets propagated to parent containers
+################################################################################
+
+cmd 'split v';
+
+
+
+sub count_urgent {
+    my ($con) = @_;
+
+    my @children = (@{$con->{nodes}}, @{$con->{floating_nodes}});
+    my $urgent = grep { $_->{urgent} } @children;
+    $urgent += count_urgent($_) for @children;
+    return $urgent;
+}
+
+$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;
+
+sync_with_i3;
+
+
+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;
+
+# 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');
+
+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');
+
+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');
+
+################################################################################
+# Regression test: Check that urgent floating containers work properly (ticket
+# #821)
+################################################################################
+
+$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);
+
 done_testing;
index 70008801261c078d7433cd1784e729670e7146f2..84e86879753b1821262e785ae1ae045c6112878e 100644 (file)
@@ -52,7 +52,6 @@ my $expected = {
     name => 'root',
     orientation => $ignore,
     type => 0,
-    split => JSON::XS::false,
     id => $ignore,
     rect => $ignore,
     window_rect => $ignore,
@@ -69,6 +68,7 @@ my $expected = {
     border => 'normal',
     'floating_nodes' => $ignore,
     workspace_layout => 'default',
+    current_border_width => -1,
 };
 
 # a shallow copy is sufficient, since we only ignore values at the root
index 7991abe54458d9ef2a1d6375270414b956ce07c5..2283ddc1370591c767ba496a2b9dfbd8660b136b 100644 (file)
@@ -156,6 +156,34 @@ ok(!workspace_exists('5'), 'workspace 5 does not exist');
 cmd 'workspace number 5';
 ok(workspace_exists('5'), 'workspace 5 was created');
 
+################################################################################
+# Check that we can go to workspace "7: foo" with the command
+# "workspace number 7: bar", i.e. the additional workspace name is ignored.
+################################################################################
+
+ok(!workspace_exists('7'), 'workspace 7 does not exist');
+ok(!workspace_exists('7: bar'), 'workspace 7: bar does not exist');
+ok(!workspace_exists('7: foo'), 'workspace 7: foo does not exist yet');
+cmd 'workspace 7: foo';
+ok(workspace_exists('7: foo'), 'workspace 7: foo was created');
+cmd 'open';
+
+cmd 'workspace 6';
+ok(workspace_exists('7: foo'), 'workspace 7: foo still open');
+cmd 'workspace number 7: bar';
+is(focused_ws(), '7: foo', 'now on workspace 7: foo');
+ok(!workspace_exists('7'), 'workspace 7 still does not exist');
+ok(!workspace_exists('7: bar'), 'workspace 7: bar still does not exist');
+
+################################################################################
+# Check that "workspace number 8: foo" will create workspace "8: foo" if it
+# does not yet exist (just like "workspace 8: foo" would).
+################################################################################
+
+ok(!workspace_exists('8: foo'), 'workspace 8: foo does not exist');
+cmd 'workspace number 8: foo';
+ok(workspace_exists('8: foo'), 'workspace 8: foo was created');
+
 ################################################################################
 # Verify that renaming workspaces works.
 ################################################################################
index ba26c85ff122955de3d27067efba8ac1663d9241..d09910397a81de46e0cbd50938177967bdbebd51 100644 (file)
@@ -79,6 +79,28 @@ is_num_children('12', 0, 'no container on 12 anymore');
 
 ok(!workspace_exists('13'), 'workspace 13 does still not exist');
 
+################################################################################
+# Check that 'move to workspace number <number><name>' works to move a window to
+# named workspaces which start with <number>.
+################################################################################
+
+cmd 'workspace 15: meh';
+cmd 'open';
+is_num_children('15: meh', 1, 'one container on 15: meh');
+
+ok(!workspace_exists('15'), 'workspace 15 does not exist yet');
+ok(!workspace_exists('15: duh'), 'workspace 15: duh does not exist yet');
+
+cmd 'workspace 14';
+cmd 'open';
+
+cmd 'move to workspace number 15: duh';
+is_num_children('15: meh', 2, 'two containers on 15: meh');
+is_num_children('14', 0, 'no container on 14 anymore');
+
+ok(!workspace_exists('15'), 'workspace 15 does still not exist');
+ok(!workspace_exists('15: duh'), 'workspace 15 does still not exist');
+
 ###################################################################
 # check if 'move workspace next' and 'move workspace prev' work
 ###################################################################
@@ -176,4 +198,143 @@ cmd 'move workspace number 17';
 ok(workspace_exists('17'), 'workspace 17 created by moving');
 is(@{get_ws('17')->{nodes}}, 1, 'one node on ws 16');
 
+################################################################################
+# The following four tests verify the various 'move workspace' commands when
+# the selection is itself a workspace.
+################################################################################
+
+# borrowed from 122-split.t
+# recursively sums up all nodes and their children
+sub sum_nodes {
+    my ($nodes) = @_;
+
+    return 0 if !@{$nodes};
+
+    my @children = (map { @{$_->{nodes}} } @{$nodes},
+                    map { @{$_->{'floating_nodes'}} } @{$nodes});
+
+    return @{$nodes} + sum_nodes(\@children);
+}
+
+############################################################
+# move workspace 'next|prev'
+############################################################
+$tmp = get_unused_workspace();
+$tmp2 = get_unused_workspace();
+
+cmd "workspace $tmp";
+cmd 'open';
+is_num_children($tmp, 1, 'one container on first ws');
+
+cmd "workspace $tmp2";
+cmd 'open';
+is_num_children($tmp2, 1, 'one container on second ws');
+cmd 'open';
+is_num_children($tmp2, 2, 'two containers on second ws');
+
+cmd 'focus parent';
+cmd 'move workspace prev';
+
+is_num_children($tmp, 2, 'two child containers on first ws');
+is(sum_nodes(get_ws_content($tmp)), 4, 'four total containers on first ws');
+is_num_children($tmp2, 0, 'no containers on second ws');
+
+############################################################
+# move workspace current
+# This is a special case that should be a no-op.
+############################################################
+$tmp = fresh_workspace();
+
+cmd 'open';
+is_num_children($tmp, 1, 'one container on first ws');
+my $tmpcount = sum_nodes(get_ws_content($tmp));
+
+cmd 'focus parent';
+cmd "move workspace $tmp";
+
+is(sum_nodes(get_ws_content($tmp)), $tmpcount, 'number of containers in first ws unchanged');
+
+############################################################
+# move workspace '<name>'
+############################################################
+$tmp2 = get_unused_workspace();
+$tmp = fresh_workspace();
+
+cmd 'open';
+is_num_children($tmp, 1, 'one container on first ws');
+
+cmd "workspace $tmp2";
+cmd 'open';
+is_num_children($tmp2, 1, 'one container on second ws');
+cmd 'open';
+is_num_children($tmp2, 2, 'two containers on second ws');
+
+cmd 'focus parent';
+cmd "move workspace $tmp";
+
+is_num_children($tmp, 2, 'two child containers on first ws');
+is(sum_nodes(get_ws_content($tmp)), 4, 'four total containers on first ws');
+is_num_children($tmp2, 0, 'no containers on second ws');
+
+############################################################
+# move workspace number '<number>'
+############################################################
+cmd 'workspace 18';
+cmd 'open';
+is_num_children('18', 1, 'one container on ws 18');
+
+cmd 'workspace 19';
+cmd 'open';
+is_num_children('19', 1, 'one container on ws 19');
+cmd 'open';
+is_num_children('19', 2, 'two containers on ws 19');
+
+cmd 'focus parent';
+cmd 'move workspace number 18';
+
+is_num_children('18', 2, 'two child containers on ws 18');
+is(sum_nodes(get_ws_content('18')), 4, 'four total containers on ws 18');
+is_num_children('19', 0, 'no containers on ws 19');
+
+###################################################################
+# move workspace '<name>' with a floating child
+###################################################################
+$tmp2 = get_unused_workspace();
+$tmp = fresh_workspace();
+cmd 'open';
+cmd 'floating toggle';
+cmd 'open';
+cmd 'floating toggle';
+cmd 'open';
+
+$ws = get_ws($tmp);
+is_num_children($tmp, 1, 'one container on first workspace');
+is(@{$ws->{floating_nodes}}, 2, 'two floating nodes on first workspace');
+
+cmd 'focus parent';
+cmd "move workspace $tmp2";
+
+$ws = get_ws($tmp2);
+is_num_children($tmp2, 1, 'one container on second workspace');
+is(@{$ws->{floating_nodes}}, 2, 'two floating nodes on second workspace');
+
+###################################################################
+# same as the above, but with only floating children
+###################################################################
+$tmp2 = get_unused_workspace();
+$tmp = fresh_workspace();
+cmd 'open';
+cmd 'floating toggle';
+
+$ws = get_ws($tmp);
+is_num_children($tmp, 0, 'no regular nodes on first workspace');
+is(@{$ws->{floating_nodes}}, 1, 'one floating node on first workspace');
+
+cmd 'focus parent';
+cmd "move workspace $tmp2";
+
+$ws = get_ws($tmp2);
+is_num_children($tmp2, 0, 'no regular nodes on second workspace');
+is(@{$ws->{floating_nodes}}, 1, 'one floating node on second workspace');
+
 done_testing;
index f38a1472f5dc71bf374d63e06b698f5368d75029..f23dabae444661ccd2a6b8398c8803b6ffa1cbe8 100644 (file)
@@ -197,4 +197,23 @@ cmd 'focus right';
 
 is($x->input_focus, $second->id, 'focus on second container');
 
+#############################################################################
+# 7: verify that focusing the parent of a window inside a floating con goes
+# up to the grandparent (workspace) and that focusing child from the ws
+# goes back down to the child of the floating con
+#############################################################################
+
+$tmp = fresh_workspace;
+
+my $tiled = open_window;
+my $floating = open_floating_window;
+is($x->input_focus, $floating->id, 'floating window focused');
+
+cmd 'focus parent';
+
+is(get_ws($tmp)->{focused}, 1, 'workspace is focused');
+cmd 'focus child';
+
+is($x->input_focus, $floating->id, 'floating window focused');
+
 done_testing;
index e038a87b1647fe41915e6672e34e714a18841f4e..97315c3deb2f5876f404f41058bf42af54db9db9 100644 (file)
@@ -255,4 +255,38 @@ cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y the same as before');
 cmp_ok($content[0]->{rect}->{height}, '<', $oldrect->{height}, 'height smaller than before');
 cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'width the same as before');
 
+################################################################################
+# Check that resizing with criteria works
+################################################################################
+
+$tmp = fresh_workspace;
+
+my $left = open_floating_window;
+my $right = open_floating_window;
+
+sub get_floating_rect {
+    my ($window_id) = @_;
+
+    my $floating_nodes = get_ws($tmp)->{floating_nodes};
+    for my $floating_node (@$floating_nodes) {
+        # Get all the windows within that floating container
+        my @window_ids = map { $_->{window} } @{$floating_node->{nodes}};
+        if ($window_id ~~ @window_ids) {
+            return $floating_node->{rect};
+        }
+    }
+
+    return undef;
+}
+
+# focus is on the right window, so we resize the left one using criteria
+my $leftold = get_floating_rect($left->id);
+my $rightold = get_floating_rect($right->id);
+cmd '[id="' . $left->id . '"] resize shrink height 10px or 10ppt';
+
+my $leftnew = get_floating_rect($left->id);
+my $rightnew = get_floating_rect($right->id);
+is($rightnew->{height}, $rightold->{height}, 'height of right container unchanged');
+is($leftnew->{height}, $leftold->{height} - 10, 'height of left container changed');
+
 done_testing;
index 29a410d2295c6aa61fe3115141431cb92a4b6048..170c641d2e0b918c146738ce4f8965e52903c62d 100644 (file)
 # the time of launching the new one. Also make sure that focusing containers
 # in other workspaces work even when there is a fullscreen container.
 #
-use i3test;
+use i3test i3_autostart => 0;
+
+# Screen setup looks like this:
+# +----+----+
+# | S1 | S2 |
+# +----+----+
+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 $i3 = i3(get_socket_path());
 
@@ -112,10 +125,18 @@ is($nodes->[0]->{focused}, 1, 'fullscreen window focused');
 
 cmd 'fullscreen';
 
+# Focus screen 1
+$x->root->warp_pointer(1025, 0);
+sync_with_i3;
+
 $tmp = fresh_workspace;
 cmd "workspace $tmp";
 my $diff_ws = open_window;
 
+# Focus screen 0
+$x->root->warp_pointer(0, 0);
+sync_with_i3;
+
 $tmp2 = fresh_workspace;
 cmd "workspace $tmp2";
 cmd 'split h';
@@ -194,21 +215,24 @@ is($x->input_focus, $right1->id, 'allowed focus up');
 cmd 'focus down';
 is($x->input_focus, $right2->id, 'allowed focus down');
 
-cmd 'focus left';
-is($x->input_focus, $right2->id, 'prevented focus left');
-
-cmd 'focus right';
-is($x->input_focus, $right2->id, 'prevented focus right');
-
 cmd 'focus down';
 is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
 
 cmd 'focus up';
 is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
 
-cmd '[id="' . $diff_ws->id . '"] focus';
+cmd 'focus left';
+is($x->input_focus, $right2->id, 'focus left wrapped (no-op)');
+
+cmd 'focus right';
 is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws');
 
+cmd 'focus left';
+is($x->input_focus, $right2->id, 'focused back into fullscreen container');
+
+cmd '[id="' . $diff_ws->id . '"] focus';
+is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws by id');
+
 ################################################################################
 # More testing of the interaction between wrapping and the fullscreen focus
 # restrictions.
@@ -305,4 +329,6 @@ verify_move(2, 'prevented move to workspace by position');
 
 # TODO: Tests for "move to output" and "move workspace to output".
 
+exit_gracefully($pid);
+
 done_testing;
index 1db64575800258e798e826ed3ebd557ef30b02f0..de1b7c81f35feb1c107015acee8acc89f86dbc85 100644 (file)
@@ -36,13 +36,13 @@ is(get_border_style(), 'normal', 'border style normal');
 
 cmd 'border 1pixel';
 
-is(get_border_style(), '1pixel', 'border style 1pixel after changing');
+is(get_border_style(), 'pixel', 'border style 1pixel after changing');
 
 # perform an inplace-restart
 cmd 'restart';
 
 does_i3_live;
 
-is(get_border_style(), '1pixel', 'border style still 1pixel after restart');
+is(get_border_style(), 'pixel', 'border style still 1pixel after restart');
 
 done_testing;
index a06bb59d75067248a7ba406b7d1a6748135fd097..6af13fa566eccb4ae7e658b41f99f07845f22a56 100644 (file)
@@ -86,7 +86,7 @@ $window->destroy;
 $config = <<EOT;
 # i3 config file (v4)
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → targetws
+assign [class="special"] → targetws
 EOT
 
 $pid = launch_with_config($config);
@@ -145,7 +145,7 @@ exit_gracefully($pid);
 $config = <<EOT;
 # i3 config file (v4)
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → ~
+for_window [class="special"] floating enable
 EOT
 
 $pid = launch_with_config($config);
@@ -166,35 +166,6 @@ $window->destroy;
 
 exit_gracefully($pid);
 
-#####################################################################
-# make sure that assignments are case-insensitive in the old syntax.
-#####################################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → ~
-EOT
-
-$pid = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-$workspaces = get_workspace_names;
-ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
-
-$window = open_special(wm_class => 'SPEcial');
-wait_for_map $window;
-
-$content = get_ws($tmp);
-ok(@{$content->{nodes}} == 0, 'no tiling cons');
-ok(@{$content->{floating_nodes}} == 1, 'one floating con');
-
-$window->destroy;
-
-exit_gracefully($pid);
-
 #####################################################################
 # regression test: dock clients with floating assignments should not crash
 # (instead, nothing should happen - dock clients can’t float)
index 7377194daacb891015bee32acdc9d237c0c1fe7d..c89dcc764b9313877e4c087dcd722f36d514904e 100644 (file)
@@ -28,26 +28,32 @@ is($nodes[0]->{border}, 'normal', 'border style normal');
 
 cmd 'border 1pixel';
 @nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
+is($nodes[0]->{border}, 'pixel', 'border style 1pixel');
+is($nodes[0]->{current_border_width}, 1, 'border width = 1px');
 
 cmd 'border none';
 @nodes = @{get_ws_content($tmp)};
 is($nodes[0]->{border}, 'none', 'border style none');
+is($nodes[0]->{current_border_width}, 0, 'border width = 0px');
 
 cmd 'border normal';
 @nodes = @{get_ws_content($tmp)};
 is($nodes[0]->{border}, 'normal', 'border style back to normal');
+is($nodes[0]->{current_border_width}, 2, 'border width = 2px');
 
 cmd 'border toggle';
 @nodes = @{get_ws_content($tmp)};
 is($nodes[0]->{border}, 'none', 'border style none');
+is($nodes[0]->{current_border_width}, 0, 'border width = 0px');
 
 cmd 'border toggle';
 @nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
+is($nodes[0]->{border}, 'pixel', 'border style 1pixel');
+is($nodes[0]->{current_border_width}, 1, 'border width = 1px');
 
 cmd 'border toggle';
 @nodes = @{get_ws_content($tmp)};
 is($nodes[0]->{border}, 'normal', 'border style back to normal');
+is($nodes[0]->{current_border_width}, 2, 'border width = 2px');
 
 done_testing;
index 91d367d1f000fb9b0db99c4d74117300a39e1b85..f27ec0e4b625225796663d521597fdda550933be 100644 (file)
@@ -62,7 +62,7 @@ sub open_special {
 my $config = <<EOT;
 # i3 config file (v4)
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → targetws
+assign [class="special"] targetws
 EOT
 
 my $pid = launch_with_config($config);
index 6e837cf09528e07a0f1773dc8e7598bb8cfaafd4..56ad865a6657ea1c6d21c2c38aff2a3e3f92859a 100644 (file)
@@ -65,7 +65,8 @@ $first = open_window;
 
 @content = @{get_ws_content($tmp)};
 ok(@content == 1, 'one container opened');
-is($content[0]->{border}, '1pixel', 'border normal by default');
+is($content[0]->{border}, 'pixel', 'border pixel by default');
+is($content[0]->{current_border_width}, -1, 'border width pixels -1 (default)');
 
 exit_gracefully($pid);
 
@@ -119,7 +120,7 @@ $wscontent = get_ws($tmp);
 @floating = @{$wscontent->{floating_nodes}};
 ok(@floating == 1, 'one floating container opened');
 $floatingcon = $floating[0];
-is($floatingcon->{nodes}->[0]->{border}, '1pixel', 'border normal by default');
+is($floatingcon->{nodes}->[0]->{border}, 'pixel', 'border pixel by default');
 
 exit_gracefully($pid);
 
index b27a9a70a0296420a191531f190ee18d8a5e58ed..7160399d3387fddc66bfd62caa6746a2de343b0f 100644 (file)
@@ -136,16 +136,49 @@ is_num_children($second_ws, 0, 'still no containers on the second workspace');
 is_num_children($first_ws, 2, 'two containers on the first workspace');
 
 ######################################################################
-# 2) open another window after the startup process is completed
-# (should be placed on the current workspace)
+# verifies that finishing startup doesn't immediately stop windows
+# from being placed on the sequence's workspace, but that moving
+# the leader actually deletes the startup sequence mapping
 ######################################################################
 
 complete_startup();
 sync_with_i3;
 
-my $otherwin = open_window;
+# Startup has completed but the 30-second deletion time hasn't elapsed,
+# so this window should still go on the leader's initial workspace.
+$win = open_window({ dont_map => 1, client_leader => $leader });
+$win->map;
+sync_with_i3;
+
+is_num_children($first_ws, 3, 'three containers on the first workspace');
+
+# Switch to the first workspace and move the focused window to the
+# second workspace.
+cmd "workspace $first_ws";
+cmd "move workspace $second_ws";
+
 is_num_children($second_ws, 1, 'one container on the second workspace');
 
+# Create and switch to a new workspace, just to be safe.
+my $third_ws = fresh_workspace;
+
+# Moving the window between workspaces should have immediately
+# removed the startup workspace mapping. New windows with that
+# leader should be created on the current workspace.
+$win = open_window({ dont_map => 1, client_leader => $leader });
+$win->map;
+sync_with_i3;
+
+is_num_children($third_ws, 1, 'one container on the third workspace');
+
+######################################################################
+# 2) open another window after the startup process is completed
+# (should be placed on the current workspace)
+######################################################################
+
+my $otherwin = open_window;
+is_num_children($third_ws, 2, 'two containers on the third workspace');
+
 ######################################################################
 # 3) test that the --no-startup-id flag for exec leads to no DESKTOP_STARTUP_ID
 # environment variable.
index 07c3c84a1e8fe8c016ad0e68bb7bf33c2a229921..8ed33030818343e595db4cfcf9df558ad79b60b8 100644 (file)
@@ -47,6 +47,33 @@ ok(get_ws($second_ws)->{focused}, 'second workspace focused');
 cmd qq|workspace "$second_ws"|;
 ok(get_ws($second_ws)->{focused}, 'second workspace still focused');
 
+################################################################################
+# verify that 'move workspace back_and_forth' works as expected
+################################################################################
+
+cmd qq|workspace "$first_ws"|;
+my $first_win = open_window;
+
+cmd qq|workspace "$second_ws"|;
+my $second_win = open_window;
+
+is(@{get_ws_content($first_ws)}, 1, 'one container on ws 1 before moving');
+cmd 'move workspace back_and_forth';
+is(@{get_ws_content($first_ws)}, 2, 'two containers on ws 1 before moving');
+
+my $third_win = open_window;
+
+################################################################################
+# verify that moving to the current ws is a no-op without
+# workspace_auto_back_and_forth.
+################################################################################
+
+cmd qq|workspace "$first_ws"|;
+
+is(@{get_ws_content($second_ws)}, 1, 'one container on ws 2 before moving');
+cmd qq|move workspace "$first_ws"|;
+is(@{get_ws_content($second_ws)}, 1, 'still one container');
+
 exit_gracefully($pid);
 
 #####################################################################
@@ -72,6 +99,19 @@ ok(get_ws($third_ws)->{focused}, 'third workspace focused');
 
 cmd qq|workspace "$third_ws"|;
 ok(get_ws($second_ws)->{focused}, 'second workspace focused');
+$first_win = open_window;
+
+################################################################################
+# verify that moving to the current ws moves to the previous one with
+# workspace_auto_back_and_forth.
+################################################################################
+
+cmd qq|workspace "$first_ws"|;
+$second_win = open_window;
+
+is(@{get_ws_content($second_ws)}, 1, 'one container on ws 2 before moving');
+cmd qq|move workspace "$first_ws"|;
+is(@{get_ws_content($second_ws)}, 2, 'two containers on ws 2');
 
 ################################################################################
 # Now see if "workspace number <number>" also works as expected with
index 87bda5295242c077de5941d5065237b8a09488a7..dafe51e07efca0827f8b7831e9b253a702baa997 100644 (file)
@@ -323,39 +323,51 @@ does_i3_live;
 # 11: focus a workspace and move all of its children to the scratchpad area
 ################################################################################
 
-$tmp = fresh_workspace;
+sub verify_scratchpad_move_multiple_win {
+    my $floating = shift;
 
-my $first = open_window;
-my $second = open_window;
+    my $first = open_window;
+    my $second = open_window;
 
-cmd 'focus parent';
-cmd 'move scratchpad';
+    if ($floating) {
+        cmd 'floating toggle';
+        cmd 'focus tiling';
+    }
 
-does_i3_live;
+    cmd 'focus parent';
+    cmd 'move scratchpad';
 
-$ws = get_ws($tmp);
-is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
-is(scalar @{$ws->{floating_nodes}}, 0, 'no floating windows on ws');
+    does_i3_live;
 
-# show the first window.
-cmd 'scratchpad show';
+    $ws = get_ws($tmp);
+    is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
+    is(scalar @{$ws->{floating_nodes}}, 0, 'no floating windows on ws');
 
-$ws = get_ws($tmp);
-is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
-is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
+    # show the first window.
+    cmd 'scratchpad show';
 
-$old_focus = get_focused($tmp);
+    $ws = get_ws($tmp);
+    is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
+    is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
 
-cmd 'scratchpad show';
+    $old_focus = get_focused($tmp);
 
-# show the second window.
-cmd 'scratchpad show';
+    cmd 'scratchpad show';
 
-$ws = get_ws($tmp);
-is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
-is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
+    # show the second window.
+    cmd 'scratchpad show';
 
-isnt(get_focused($tmp), $old_focus, 'focus changed');
+    $ws = get_ws($tmp);
+    is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
+    is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
+
+    isnt(get_focused($tmp), $old_focus, 'focus changed');
+}
+
+$tmp = fresh_workspace;
+verify_scratchpad_move_multiple_win(0);
+$tmp = fresh_workspace;
+verify_scratchpad_move_multiple_win(1);
 
 # TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint
 
index 8f732bacdf20a5754476e2cfa6dc67828dd89083..2448452ea290ff9e0c4036ddd3d260452d61e63e 100644 (file)
@@ -1,5 +1,19 @@
 #!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)
+#
 # When using a command which moves a window to scratchpad from an invisible
 # (e.g. unfocused) workspace and immediately shows that window again, i3
 # crashed.
diff --git a/testcases/t/199-ipc-mode-event.t b/testcases/t/199-ipc-mode-event.t
new file mode 100644 (file)
index 0000000..43f7b17
--- /dev/null
@@ -0,0 +1,57 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verifies that the IPC 'mode' event is sent when modes are changed.
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+mode "m1" {
+    bindsym Mod1+x nop foo
+}
+
+mode "with spaces" {
+    bindsym Mod1+y nop bar
+}
+EOT
+
+my $pid = launch_with_config($config);
+
+my $i3 = i3(get_socket_path(0));
+$i3->connect->recv;
+
+my $cv = AnyEvent->condvar;
+
+$i3->subscribe({
+    mode => sub {
+        my ($event) = @_;
+        $cv->send($event->{change} eq 'm1');
+    }
+})->recv;
+
+cmd 'mode "m1"';
+
+# Timeout after 0.5s
+my $t;
+$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); });
+
+ok($cv->recv, 'Mode event received');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/200-urgency-timer.t b/testcases/t/200-urgency-timer.t
new file mode 100644 (file)
index 0000000..730a950
--- /dev/null
@@ -0,0 +1,149 @@
+#!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 the urgency timer works as expected and does not break
+# urgency handling.
+#
+
+use List::Util qw(first);
+use i3test i3_autostart => 0;
+use Time::HiRes qw(sleep);
+
+# 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
+
+force_display_urgency_hint 150ms
+EOT
+my $pid = launch_with_config($config);
+
+#####################################################################
+# Initial setup: one window on ws1, empty ws2
+#####################################################################
+
+my $tmp1 = fresh_workspace;
+my $w = open_window;
+
+my $tmp2 = fresh_workspace;
+cmd "workspace $tmp2";
+
+$w->add_hint('urgency');
+sync_with_i3;
+
+#######################################################################
+# Create a window on ws1, then switch to ws2, set urgency, switch back
+#######################################################################
+
+isnt($x->input_focus, $w->id, 'window not focused');
+
+my @content = @{get_ws_content($tmp1)};
+my @urgent = grep { $_->{urgent} } @content;
+is(@urgent, 1, "window marked as urgent");
+
+# switch to ws1
+cmd "workspace $tmp1";
+
+# this will start the timer
+sleep(0.1);
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 1, 'window still marked as urgent');
+
+# now check if the timer was triggered
+cmd "workspace $tmp2";
+
+sleep(0.1);
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 0, 'window not marked as urgent anymore');
+
+#######################################################################
+# Create another window on ws1, focus it, switch to ws2, make the other
+# window urgent, and switch back. This should not trigger the timer.
+#######################################################################
+
+cmd "workspace $tmp1";
+my $w2 = open_window;
+is($x->input_focus, $w2->id, 'window 2 focused');
+
+cmd "workspace $tmp2";
+$w->add_hint('urgency');
+sync_with_i3;
+
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 1, 'window 1 marked as urgent');
+
+# Switch back to ws1. This should focus w2.
+cmd "workspace $tmp1";
+is($x->input_focus, $w2->id, 'window 2 focused');
+
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 1, 'window 1 still marked as urgent');
+
+# explicitly focusing the window should result in immediate urgency reset
+cmd '[id="' . $w->id . '"] focus';
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 0, 'window 1 not marked as urgent anymore');
+
+################################################################################
+# open a stack, mark one window as urgent, switch to that workspace and verify
+# it’s cleared correctly.
+################################################################################
+
+sub count_total_urgent {
+    my ($con) = @_;
+
+    my $urgent = ($con->{urgent} ? 1 : 0);
+    $urgent += count_total_urgent($_) for (@{$con->{nodes}}, @{$con->{floating_nodes}});
+    return $urgent;
+}
+
+my $tmp3 = fresh_workspace;
+open_window;
+open_window;
+cmd 'split v';
+my $split_left = open_window;
+cmd 'layout stacked';
+
+cmd "workspace $tmp2";
+
+is(count_total_urgent(get_ws($tmp3)), 0, "no urgent windows on workspace $tmp3");
+
+$split_left->add_hint('urgency');
+sync_with_i3;
+
+cmp_ok(count_total_urgent(get_ws($tmp3)), '>=', 0, "more than one urgent window on workspace $tmp3");
+
+cmd "workspace $tmp3";
+
+# Remove the urgency hint.
+$split_left->delete_hint('urgency');
+sync_with_i3;
+
+sleep(0.2);
+is(count_total_urgent(get_ws($tmp3)), 0, "no more urgent windows on workspace $tmp3");
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t
new file mode 100644 (file)
index 0000000..a850b7e
--- /dev/null
@@ -0,0 +1,533 @@
+#!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 the standalone parser binary to see if it calls the right code when
+# confronted with various commands, if it prints proper error messages for
+# wrong commands and if it terminates in every case.
+#
+use i3test i3_autostart => 0;
+use IPC::Run qw(run);
+
+sub parser_calls {
+    my ($command) = @_;
+
+    my $stdout;
+    run [ '../test.config_parser', $command ],
+        '>&-',
+        '2>', \$stdout;
+    # TODO: use a timeout, so that we can error out if it doesn’t terminate
+
+    # Filter out all debugging output.
+    my @lines = split("\n", $stdout);
+    @lines = grep { not /^# / } @lines;
+
+    # The criteria management calls are irrelevant and not what we want to test
+    # in the first place.
+    @lines = grep { !(/cfg_criteria_init/ || /cfg_criteria_pop_state/) } @lines;
+    return join("\n", @lines) . "\n";
+}
+
+my $config = <<'EOT';
+mode "meh" {
+    bindsym Mod1 + Shift +   x resize grow
+    bindcode Mod1+44 resize shrink
+}
+EOT
+
+my $expected = <<'EOT';
+cfg_enter_mode(meh)
+cfg_mode_binding(bindsym, Mod1,Shift, x, (null), resize grow)
+cfg_mode_binding(bindcode, Mod1, 44, (null), resize shrink)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'single number (move workspace 3) ok');
+
+################################################################################
+# exec and exec_always
+################################################################################
+
+$config = <<'EOT';
+exec geeqie
+exec --no-startup-id /tmp/foo.sh
+exec_always firefox
+exec_always --no-startup-id /tmp/bar.sh
+EOT
+
+$expected = <<'EOT';
+cfg_exec(exec, (null), geeqie)
+cfg_exec(exec, --no-startup-id, /tmp/foo.sh)
+cfg_exec(exec_always, (null), firefox)
+cfg_exec(exec_always, --no-startup-id, /tmp/bar.sh)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'exec okay');
+
+################################################################################
+# for_window
+################################################################################
+
+$config = <<'EOT';
+for_window [class="^Chrome"] floating enable
+EOT
+
+$expected = <<'EOT';
+cfg_criteria_add(class, ^Chrome)
+cfg_for_window(floating enable)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'for_window okay');
+
+################################################################################
+# assign
+################################################################################
+
+$config = <<'EOT';
+assign [class="^Chrome"] 4
+assign [class="^Chrome"] named workspace
+assign [class="^Chrome"] "quoted named workspace"
+assign [class="^Chrome"] → "quoted named workspace"
+EOT
+
+$expected = <<'EOT';
+cfg_criteria_add(class, ^Chrome)
+cfg_assign(4)
+cfg_criteria_add(class, ^Chrome)
+cfg_assign(named workspace)
+cfg_criteria_add(class, ^Chrome)
+cfg_assign(quoted named workspace)
+cfg_criteria_add(class, ^Chrome)
+cfg_assign(quoted named workspace)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'for_window okay');
+
+################################################################################
+# floating_minimum_size / floating_maximum_size
+################################################################################
+
+$config = <<'EOT';
+floating_minimum_size 80x55
+floating_minimum_size 80    x  55  
+floating_maximum_size 73 x 10
+EOT
+
+$expected = <<'EOT';
+cfg_floating_minimum_size(80, 55)
+cfg_floating_minimum_size(80, 55)
+cfg_floating_maximum_size(73, 10)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'floating_minimum_size ok');
+
+################################################################################
+# floating_modifier
+################################################################################
+
+$config = <<'EOT';
+floating_modifier Mod1
+floating_modifier mOd1
+EOT
+
+$expected = <<'EOT';
+cfg_floating_modifier(Mod1)
+cfg_floating_modifier(Mod1)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'floating_modifier ok');
+
+################################################################################
+# default_orientation
+################################################################################
+
+$config = <<'EOT';
+default_orientation horizontal
+default_orientation vertical
+default_orientation auto
+EOT
+
+$expected = <<'EOT';
+cfg_default_orientation(horizontal)
+cfg_default_orientation(vertical)
+cfg_default_orientation(auto)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'default_orientation ok');
+
+################################################################################
+# workspace_layout
+################################################################################
+
+$config = <<'EOT';
+workspace_layout default
+workspace_layout stacked
+workspace_layout stacking
+workspace_layout tabbed
+EOT
+
+$expected = <<'EOT';
+cfg_workspace_layout(default)
+cfg_workspace_layout(stacked)
+cfg_workspace_layout(stacking)
+cfg_workspace_layout(tabbed)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'workspace_layout ok');
+
+################################################################################
+# new_window
+################################################################################
+
+$config = <<'EOT';
+new_window 1pixel
+new_window normal
+new_window none
+new_float 1pixel
+new_float normal
+new_float none
+EOT
+
+$expected = <<'EOT';
+cfg_new_window(new_window, 1pixel, -1)
+cfg_new_window(new_window, normal, 2)
+cfg_new_window(new_window, none, -1)
+cfg_new_window(new_float, 1pixel, -1)
+cfg_new_window(new_float, normal, 2)
+cfg_new_window(new_float, none, -1)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'new_window ok');
+
+################################################################################
+# hide_edge_borders
+################################################################################
+
+$config = <<'EOT';
+hide_edge_borders none
+hide_edge_borders vertical
+hide_edge_borders horizontal
+hide_edge_borders both
+EOT
+
+$expected = <<'EOT';
+cfg_hide_edge_borders(none)
+cfg_hide_edge_borders(vertical)
+cfg_hide_edge_borders(horizontal)
+cfg_hide_edge_borders(both)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'hide_edge_borders ok');
+
+################################################################################
+# focus_follows_mouse
+################################################################################
+
+$config = <<'EOT';
+focus_follows_mouse yes
+focus_follows_mouse no
+EOT
+
+$expected = <<'EOT';
+cfg_focus_follows_mouse(yes)
+cfg_focus_follows_mouse(no)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'focus_follows_mouse ok');
+
+################################################################################
+# force_display_urgency_hint
+################################################################################
+
+is(parser_calls('force_display_urgency_hint 300'),
+   "cfg_force_display_urgency_hint(300)\n",
+   'force_display_urgency_hint ok');
+
+is(parser_calls('force_display_urgency_hint 500 ms'),
+   "cfg_force_display_urgency_hint(500)\n",
+   'force_display_urgency_hint ok');
+
+is(parser_calls('force_display_urgency_hint 700ms'),
+   "cfg_force_display_urgency_hint(700)\n",
+   'force_display_urgency_hint ok');
+
+$config = <<'EOT';
+force_display_urgency_hint 300
+force_display_urgency_hint 500 ms
+force_display_urgency_hint 700ms
+force_display_urgency_hint 700
+EOT
+
+$expected = <<'EOT';
+cfg_force_display_urgency_hint(300)
+cfg_force_display_urgency_hint(500)
+cfg_force_display_urgency_hint(700)
+cfg_force_display_urgency_hint(700)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'force_display_urgency_hint ok');
+
+################################################################################
+# workspace
+################################################################################
+
+$config = <<'EOT';
+workspace 3 output VGA-1
+workspace "4: output" output VGA-2
+workspace bleh output LVDS1/I_1
+EOT
+
+$expected = <<'EOT';
+cfg_workspace(3, VGA-1)
+cfg_workspace(4: output, VGA-2)
+cfg_workspace(bleh, LVDS1/I_1)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'workspace ok');
+
+################################################################################
+# ipc-socket
+################################################################################
+
+$config = <<'EOT';
+ipc-socket /tmp/i3.sock
+ipc_socket ~/.i3/i3.sock
+EOT
+
+$expected = <<'EOT';
+cfg_ipc_socket(/tmp/i3.sock)
+cfg_ipc_socket(~/.i3/i3.sock)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'ipc-socket ok');
+
+################################################################################
+# colors
+################################################################################
+
+$config = <<'EOT';
+client.focused          #4c7899 #285577 #ffffff #2e9ef4
+client.focused_inactive #333333 #5f676a #ffffff #484e50
+client.unfocused        #333333 #222222 #888888 #292d2e
+client.urgent           #2f343a #900000 #ffffff #900000
+EOT
+
+$expected = <<'EOT';
+cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4)
+cfg_color(client.focused_inactive, #333333, #5f676a, #ffffff, #484e50)
+cfg_color(client.unfocused, #333333, #222222, #888888, #292d2e)
+cfg_color(client.urgent, #2f343a, #900000, #ffffff, #900000)
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'colors ok');
+
+################################################################################
+# Error message with 2+2 lines of context
+################################################################################
+
+$config = <<'EOT';
+# i3 config file (v4)
+
+font foobar
+
+unknown qux
+
+# yay
+# this should not show up
+EOT
+
+$expected = <<'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'
+ERROR: CONFIG: (in file <stdin>)
+ERROR: CONFIG: Line   3: font foobar
+ERROR: CONFIG: Line   4: 
+ERROR: CONFIG: Line   5: unknown qux
+ERROR: CONFIG:           ^^^^^^^^^^^
+ERROR: CONFIG: Line   6: 
+ERROR: CONFIG: Line   7: # yay
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'error message (2+2 context) ok');
+
+################################################################################
+# Error message with 0+0 lines of context
+################################################################################
+
+$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'
+ERROR: CONFIG: (in file <stdin>)
+ERROR: CONFIG: Line   1: unknown qux
+ERROR: CONFIG:           ^^^^^^^^^^^
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'error message (0+0 context) ok');
+
+################################################################################
+# Error message with 1+0 lines of context
+################################################################################
+
+$config = <<'EOT';
+# context before
+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'
+ERROR: CONFIG: (in file <stdin>)
+ERROR: CONFIG: Line   1: # context before
+ERROR: CONFIG: Line   2: unknown qux
+ERROR: CONFIG:           ^^^^^^^^^^^
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'error message (1+0 context) ok');
+
+################################################################################
+# Error message with 0+1 lines of context
+################################################################################
+
+$config = <<'EOT';
+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'
+ERROR: CONFIG: (in file <stdin>)
+ERROR: CONFIG: Line   1: unknown qux
+ERROR: CONFIG:           ^^^^^^^^^^^
+ERROR: CONFIG: Line   2: # context after
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'error message (0+1 context) ok');
+
+################################################################################
+# Error message with 0+2 lines of context
+################################################################################
+
+$config = <<'EOT';
+unknown qux
+# context after
+# 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'
+ERROR: CONFIG: (in file <stdin>)
+ERROR: CONFIG: Line   1: unknown qux
+ERROR: CONFIG:           ^^^^^^^^^^^
+ERROR: CONFIG: Line   2: # context after
+ERROR: CONFIG: Line   3: # context 2 after
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'error message (0+2 context) ok');
+
+################################################################################
+# Error message within mode blocks
+################################################################################
+
+$config = <<'EOT';
+mode "yo" {
+    bindsym x resize shrink left
+    unknown qux
+}
+EOT
+
+$expected = <<'EOT';
+cfg_enter_mode(yo)
+cfg_mode_binding(bindsym, (null), x, (null), resize shrink left)
+ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', '}'
+ERROR: CONFIG: (in file <stdin>)
+ERROR: CONFIG: Line   1: mode "yo" {
+ERROR: CONFIG: Line   2:     bindsym x resize shrink left
+ERROR: CONFIG: Line   3:     unknown qux
+ERROR: CONFIG:               ^^^^^^^^^^^
+ERROR: CONFIG: Line   4: }
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'error message (mode block) ok');
+
+################################################################################
+# Error message within bar blocks
+################################################################################
+
+$config = <<'EOT';
+bar {
+    output LVDS-1
+    unknown qux
+}
+EOT
+
+$expected = <<'EOT';
+cfg_bar_output(LVDS-1)
+ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'modifier', 'position', 'output', 'tray_output', 'font', 'workspace_buttons', 'verbose', 'colors', '}'
+ERROR: CONFIG: (in file <stdin>)
+ERROR: CONFIG: Line   1: bar {
+ERROR: CONFIG: Line   2:     output LVDS-1
+ERROR: CONFIG: Line   3:     unknown qux
+ERROR: CONFIG:               ^^^^^^^^^^^
+ERROR: CONFIG: Line   4: }
+EOT
+
+is(parser_calls($config),
+   $expected,
+   'error message (bar block) ok');
+
+done_testing;
index 7a976271c12ce64ed0c4a232ba0890c1cde178ea..c087f9f5ad117946b493383a5bbd893bf84922e3 100644 (file)
@@ -133,6 +133,26 @@ is($old_rect->{y}, $new_rect->{y}, 'y coordinate unchanged');
 is($old_rect->{width}, $new_rect->{width}, 'width unchanged');
 is($old_rect->{height}, $new_rect->{height}, 'height unchanged');
 
+################################################################################
+# Verify that empty workspaces get cleaned up when moving a different workspace
+# to that output.
+################################################################################
+
+my $empty_ws = fresh_workspace(output => 0);
+my $other_output_ws = fresh_workspace(output => 1);
+cmd 'open';
+
+($x0, $x1) = workspaces_per_screen();
+ok($empty_ws ~~ @$x0, 'empty_ws on fake-0');
+ok($other_output_ws ~~ @$x1, 'other_output_ws on fake-1');
+
+cmd 'move workspace to output left';
+
+($x0, $x1) = workspaces_per_screen();
+ok(!($empty_ws ~~ @$x0), 'empty_ws not on fake-0');
+ok(!($empty_ws ~~ @$x1), 'empty_ws not on fake-1');
+ok($other_output_ws ~~ @$x0, 'other_output_ws on fake-0');
+
 exit_gracefully($pid);
 
 done_testing;
diff --git a/testcases/t/506-focus-right.t b/testcases/t/506-focus-right.t
new file mode 100644 (file)
index 0000000..3f58b00
--- /dev/null
@@ -0,0 +1,179 @@
+#!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 focus output right works with monitor setups that don’t line up
+# on their x/y coordinates.
+#
+# ticket #771, bug still present in commit dd743f3b55b2f86d9f1f69ef7952ae8ece4de504
+#
+use i3test i3_autostart => 0;
+
+sub test_focus_left_right {
+    my ($config) = @_;
+
+    my $pid = launch_with_config($config);
+
+    my $i3 = i3(get_socket_path(0));
+
+    $x->root->warp_pointer(0, 0);
+    sync_with_i3;
+
+    ############################################################################
+    # Ensure that moving left and right works.
+    ############################################################################
+
+    # First ensure both workspaces have something to focus
+    cmd "workspace 1";
+    my $left_win = open_window;
+
+    cmd "workspace 2";
+    my $right_win = open_window;
+
+    is($x->input_focus, $right_win->id, 'right window focused');
+
+    cmd "focus output left";
+    is($x->input_focus, $left_win->id, 'left window focused');
+
+    cmd "focus output right";
+    is($x->input_focus, $right_win->id, 'right window focused');
+
+    cmd "focus output right";
+    is($x->input_focus, $left_win->id, 'left window focused (wrapping)');
+
+    cmd "focus output left";
+    is($x->input_focus, $right_win->id, 'right window focused (wrapping)');
+
+    ############################################################################
+    # Ensure that moving down/up from S0 doesn’t crash i3 and is a no-op.
+    ############################################################################
+
+    my $second = fresh_workspace(output => 1);
+    my $third_win = open_window;
+
+    cmd "focus output down";
+    is($x->input_focus, $third_win->id, 'right window still focused');
+
+    cmd "focus output up";
+    is($x->input_focus, $third_win->id, 'right window still focused');
+
+    does_i3_live;
+
+    exit_gracefully($pid);
+}
+
+# Screen setup looks like this:
+# +----+
+# |    |--------+
+# | S1 |   S2   |
+# |    |--------+
+# +----+
+#
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1080x1920+0+0,1920x1080+1080+500
+EOT
+
+test_focus_left_right($config);
+
+# Screen setup looks like this:
+# +----+--------+
+# |    |   S2   |
+# | S1 |--------+
+# |    |
+# +----+
+#
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1080x1920+0+0,1920x200+1080+0
+EOT
+
+test_focus_left_right($config);
+
+# Screen setup looks like this:
+# +----+
+# |    |
+# | S1 |--------+
+# |    |   S2   |
+# +----+--------+
+#
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1080x1920+0+0,1920x200+1080+1720
+EOT
+
+test_focus_left_right($config);
+
+# Screen setup looks like this:
+# +----+        +----+
+# |    |        |    |
+# | S1 |--------+ S3 |
+# |    |   S2   |    |
+# +----+--------+----+
+#
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1080x1920+0+0,1920x200+1080+1720,1080x1920+1280+0
+EOT
+
+my $pid = launch_with_config($config);
+
+my $i3 = i3(get_socket_path(0));
+
+$x->root->warp_pointer(0, 0);
+sync_with_i3;
+
+############################################################################
+# Ensure that focusing right/left works in the expected order.
+############################################################################
+
+sub get_focused_output {
+    my $tree = i3(get_socket_path())->get_tree->recv;
+    my ($focused_id) = @{$tree->{focus}};
+    my ($output) = grep { $_->{id} == $focused_id } @{$tree->{nodes}};
+    return $output->{name};
+}
+
+is(get_focused_output(), 'fake-0', 'focus on fake-0');
+
+cmd 'focus output right';
+is(get_focused_output(), 'fake-1', 'focus on fake-1');
+
+cmd 'focus output right';
+is(get_focused_output(), 'fake-2', 'focus on fake-2');
+
+cmd 'focus output left';
+is(get_focused_output(), 'fake-1', 'focus on fake-1');
+
+cmd 'focus output left';
+is(get_focused_output(), 'fake-0', 'focus on fake-0');
+
+cmd 'focus output left';
+is(get_focused_output(), 'fake-2', 'focus on fake-2 (wrapping)');
+
+cmd 'focus output right';
+is(get_focused_output(), 'fake-0', 'focus on fake-0 (wrapping)');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/507-workspace-move-crash.t b/testcases/t/507-workspace-move-crash.t
new file mode 100644 (file)
index 0000000..9e80553
--- /dev/null
@@ -0,0 +1,48 @@
+#!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 i3 crashes on cross-output moves with one workspace per output.
+# Ticket: #827
+# Bug still in: 4.3-78-g66b389c
+#
+use List::Util qw(first);
+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);
+
+################################################################################
+# Setup workspaces so that they stay open (with an empty container).
+################################################################################
+
+is(focused_ws, '1', 'starting on workspace 1');
+
+cmd 'move workspace to output fake-1';
+
+does_i3_live;
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/508-move-workspace-focus.t b/testcases/t/508-move-workspace-focus.t
new file mode 100644 (file)
index 0000000..7d42ff4
--- /dev/null
@@ -0,0 +1,46 @@
+#!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;
diff --git a/testcases/t/510-focus-across-outputs.t b/testcases/t/510-focus-across-outputs.t
new file mode 100644 (file)
index 0000000..7f68a2d
--- /dev/null
@@ -0,0 +1,146 @@
+#!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 that switching workspaces via 'focus $dir' never leaves a floating
+# window focused.
+#
+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,1024x768+0+768,1024x768+1024+768
+EOT
+my $pid = launch_with_config($config);
+
+my $s0_ws = fresh_workspace;
+my $first = open_window;
+my $second = open_window;
+my $third = open_window;
+cmd 'floating toggle';
+
+# Focus screen 1
+$x->root->warp_pointer(1025, 0);
+my $s1_ws = fresh_workspace;
+sync_with_i3;
+
+my $fourth = open_window;
+
+# Focus screen 2
+$x->root->warp_pointer(0, 769);
+my $s2_ws = fresh_workspace;
+sync_with_i3;
+
+my $fifth = open_window;
+
+# Focus screen 3
+$x->root->warp_pointer(1025, 769);
+my $s3_ws = fresh_workspace;
+sync_with_i3;
+
+my $sixth = open_window;
+my $seventh = open_window;
+my $eighth = open_window;
+cmd 'floating toggle';
+
+# Layout should look like this (windows 3 and 8 are floating):
+#     S0      S1
+# ┌───┬───┬───────┐
+# │ ┌─┴─┐ │       │
+# │1│ 3 │2│   4   │
+# │ └─┬─┘ │       │
+# ├───┴───┼───┬───┤
+# │       │ ┌─┴─┐ │
+# │   5   │6│ 8 │7│
+# │       │ └─┬─┘ │
+# └───────┴───┴───┘
+#    S2       S3
+#
+###################################################################
+# Test that focus (left|down|right|up) doesn't focus floating
+# windows when moving into horizontally-split workspaces.
+###################################################################
+
+sub reset_focus {
+    my $ws = shift;
+    cmd "workspace $ws; focus floating";
+}
+
+cmd "workspace $s1_ws";
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+reset_focus $s0_ws;
+
+cmd "workspace $s1_ws";
+cmd 'focus down';
+is($x->input_focus, $seventh->id, 'seventh window focused');
+reset_focus $s3_ws;
+
+cmd "workspace $s2_ws";
+cmd 'focus right';
+is($x->input_focus, $sixth->id, 'sixth window focused');
+reset_focus $s3_ws;
+
+cmd "workspace $s2_ws";
+cmd 'focus up';
+is($x->input_focus, $second->id, 'second window focused');
+reset_focus $s0_ws;
+
+###################################################################
+# Put the workspaces on screens 0 and 3 into vertical split mode
+# and test focus (left|down|right|up) again.
+###################################################################
+
+cmd "workspace $s0_ws";
+is($x->input_focus, $third->id, 'third window focused');
+cmd 'focus parent';
+cmd 'focus parent';
+cmd 'split v';
+reset_focus $s0_ws;
+
+cmd "workspace $s3_ws";
+is($x->input_focus, $eighth->id, 'eighth window focused');
+cmd 'focus parent';
+cmd 'focus parent';
+cmd 'split v';
+reset_focus $s3_ws;
+
+cmd "workspace $s1_ws";
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+reset_focus $s0_ws;
+
+cmd "workspace $s1_ws";
+cmd 'focus down';
+is($x->input_focus, $sixth->id, 'sixth window focused');
+reset_focus $s3_ws;
+
+cmd "workspace $s2_ws";
+cmd 'focus right';
+is($x->input_focus, $sixth->id, 'sixth window focused');
+
+cmd "workspace $s2_ws";
+cmd 'focus up';
+is($x->input_focus, $second->id, 'second window focused');
+
+exit_gracefully($pid);
+
+done_testing;