]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'next'
authorMichael Stapelberg <michael@stapelberg.de>
Fri, 11 Nov 2011 22:49:20 +0000 (22:49 +0000)
committerMichael Stapelberg <michael@stapelberg.de>
Fri, 11 Nov 2011 22:49:20 +0000 (22:49 +0000)
325 files changed:
.gitignore
CMDMODE [deleted file]
DEPENDS
LICENSE
Makefile
PACKAGE-MAINTAINER
RELEASE-NOTES-4.1 [new file with mode: 0644]
TODO [deleted file]
common.mk
debian/changelog
debian/compat
debian/control
debian/i3-wm.docs
debian/i3-wm.manpages [new file with mode: 0644]
debian/patches/manpage-x-terminal-emulator.patch [new file with mode: 0644]
debian/patches/series [new file with mode: 0644]
debian/patches/use-x-terminal-emulator.patch [new file with mode: 0644]
debian/rules
docs/Makefile
docs/asciidoc-git.conf [new file with mode: 0644]
docs/i3-sync-working.dia [new file with mode: 0644]
docs/i3-sync-working.png [new file with mode: 0644]
docs/i3-sync.dia [new file with mode: 0644]
docs/i3-sync.png [new file with mode: 0644]
docs/ipc
docs/multi-monitor
docs/testsuite [new file with mode: 0644]
docs/userguide
i3-config-wizard/Makefile
i3-config-wizard/cfgparse.y
i3-config-wizard/ipc.c [deleted file]
i3-config-wizard/ipc.h [deleted file]
i3-config-wizard/main.c
i3-config-wizard/xcb.c [deleted file]
i3-config-wizard/xcb.h
i3-input/Makefile
i3-input/i3-input.h
i3-input/ipc.c [deleted file]
i3-input/main.c
i3-input/ucs2_to_utf8.c
i3-input/xcb.c [deleted file]
i3-migrate-config-to-v4
i3-msg/Makefile
i3-msg/main.c
i3-nagbar/Makefile
i3-nagbar/i3-nagbar.h
i3-nagbar/main.c
i3-nagbar/xcb.c [deleted file]
i3-sensible-editor [new file with mode: 0755]
i3-sensible-pager [new file with mode: 0755]
i3-sensible-terminal [new file with mode: 0755]
i3.config
i3.config.keycodes
i3bar/Makefile
i3bar/doc/i3bar.man
i3bar/include/child.h
i3bar/include/common.h
i3bar/include/config.h
i3bar/include/ipc.h
i3bar/include/outputs.h
i3bar/include/queue.h [deleted file]
i3bar/include/trayclients.h [new file with mode: 0644]
i3bar/include/ucs2_to_utf8.h
i3bar/include/util.h
i3bar/include/workspaces.h
i3bar/include/xcb.h
i3bar/include/xcb_atoms.def
i3bar/src/child.c
i3bar/src/config.c [new file with mode: 0644]
i3bar/src/ipc.c
i3bar/src/main.c
i3bar/src/outputs.c
i3bar/src/ucs2_to_utf8.c
i3bar/src/workspaces.c
i3bar/src/xcb.c
include/all.h
include/assignments.h
include/atoms.xmacro
include/click.h
include/cmdparse.h
include/con.h
include/config.h
include/data.h
include/debug.h
include/ewmh.h
include/floating.h
include/handlers.h
include/i3.h
include/i3/ipc.h
include/ipc.h
include/libi3.h [new file with mode: 0644]
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 [new file with mode: 0644]
include/render.h
include/resize.h
include/sd-daemon.h [new file with mode: 0644]
include/sighandler.h
include/startup.h [new file with mode: 0644]
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
libi3/Makefile [new file with mode: 0644]
libi3/README [new file with mode: 0644]
libi3/fake_configure_notify.c [new file with mode: 0644]
libi3/get_colorpixel.c [new file with mode: 0644]
libi3/get_mod_mask.c [new file with mode: 0644]
libi3/get_socket_path.c [new file with mode: 0644]
libi3/ipc_connect.c [new file with mode: 0644]
libi3/ipc_recv_message.c [new file with mode: 0644]
libi3/ipc_send_message.c [new file with mode: 0644]
libi3/load_font.c [new file with mode: 0644]
libi3/safewrappers.c [new file with mode: 0644]
libi3/strndup.c [new file with mode: 0644]
man/Makefile
man/asciidoc.conf
man/i3-input.man
man/i3-sensible-editor.man [new file with mode: 0644]
man/i3-sensible-pager.man [new file with mode: 0644]
man/i3-sensible-terminal.man [new file with mode: 0644]
man/i3.man
src/assignments.c
src/cfgparse.l
src/cfgparse.y
src/click.c
src/cmdparse.l
src/cmdparse.y
src/con.c
src/config.c
src/debug.c
src/ewmh.c
src/floating.c
src/handlers.c
src/ipc.c
src/load_layout.c
src/log.c
src/main.c
src/manage.c
src/match.c
src/move.c
src/output.c
src/randr.c
src/regex.c [new file with mode: 0644]
src/render.c
src/resize.c
src/sd-daemon.c [new file with mode: 0644]
src/sighandler.c
src/startup.c [new file with mode: 0644]
src/tree.c
src/util.c
src/window.c
src/workspace.c
src/x.c
src/xcb.c
src/xcursor.c
src/xinerama.c
testcases/Makefile [deleted file]
testcases/Makefile.PL [new file with mode: 0755]
testcases/complete-run.pl
testcases/lib/SocketActivation.pm [new file with mode: 0644]
testcases/lib/StartXDummy.pm [new file with mode: 0644]
testcases/lib/StatusLine.pm [new file with mode: 0644]
testcases/lib/i3test.pm [new file with mode: 0644]
testcases/t/00-load.t [deleted file]
testcases/t/000-load-deps.t [new file with mode: 0644]
testcases/t/001-tile.t [new file with mode: 0644]
testcases/t/002-i3-sync.t [new file with mode: 0644]
testcases/t/003-ipc.t [new file with mode: 0644]
testcases/t/004-unmanaged.t [new file with mode: 0644]
testcases/t/005-floating.t [new file with mode: 0644]
testcases/t/01-tile.t [deleted file]
testcases/t/02-fullscreen.t [deleted file]
testcases/t/03-unmanaged.t [deleted file]
testcases/t/04-floating.t [deleted file]
testcases/t/05-ipc.t [deleted file]
testcases/t/06-focus.t [deleted file]
testcases/t/07-move.t [deleted file]
testcases/t/08-focus-stack.t [deleted file]
testcases/t/09-stacking.t [deleted file]
testcases/t/10-dock.t [deleted file]
testcases/t/100-fullscreen.t [new file with mode: 0644]
testcases/t/101-focus.t [new file with mode: 0644]
testcases/t/102-dock.t [new file with mode: 0644]
testcases/t/103-move.t [new file with mode: 0644]
testcases/t/104-focus-stack.t [new file with mode: 0644]
testcases/t/105-stacking.t [new file with mode: 0644]
testcases/t/11-goto.t [deleted file]
testcases/t/111-goto.t [new file with mode: 0644]
testcases/t/112-floating-resize.t [new file with mode: 0644]
testcases/t/113-urgent.t [new file with mode: 0644]
testcases/t/114-client-leader.t [new file with mode: 0644]
testcases/t/115-ipc-workspaces.t [new file with mode: 0644]
testcases/t/116-nestedcons.t [new file with mode: 0644]
testcases/t/117-workspace.t [new file with mode: 0644]
testcases/t/118-openkill.t [new file with mode: 0644]
testcases/t/119-match.t [new file with mode: 0644]
testcases/t/12-floating-resize.t [deleted file]
testcases/t/120-multiple-cmds.t [new file with mode: 0644]
testcases/t/121-next-prev.t [new file with mode: 0644]
testcases/t/122-split.t [new file with mode: 0644]
testcases/t/124-move.t [new file with mode: 0644]
testcases/t/126-regress-close.t [new file with mode: 0644]
testcases/t/127-regress-floating-parent.t [new file with mode: 0644]
testcases/t/128-open-order.t [new file with mode: 0644]
testcases/t/129-focus-after-close.t [new file with mode: 0644]
testcases/t/13-urgent.t [deleted file]
testcases/t/130-close-empty-split.t [new file with mode: 0644]
testcases/t/131-stacking-order.t [new file with mode: 0644]
testcases/t/132-move-workspace.t [new file with mode: 0644]
testcases/t/133-size-hints.t [new file with mode: 0644]
testcases/t/134-invalid-command.t [new file with mode: 0644]
testcases/t/135-floating-focus.t [new file with mode: 0644]
testcases/t/136-floating-ws-empty.t [new file with mode: 0644]
testcases/t/137-floating-unmap.t [new file with mode: 0644]
testcases/t/138-floating-attach.t [new file with mode: 0644]
testcases/t/139-ws-numbers.t [new file with mode: 0644]
testcases/t/14-client-leader.t [deleted file]
testcases/t/140-focus-lost.t [new file with mode: 0644]
testcases/t/141-resize.t [new file with mode: 0644]
testcases/t/142-regress-move-floating.t [new file with mode: 0644]
testcases/t/143-regress-floating-restart.t [new file with mode: 0644]
testcases/t/144-regress-floating-resize.t [new file with mode: 0644]
testcases/t/145-flattening.t [new file with mode: 0644]
testcases/t/146-floating-reinsert.t [new file with mode: 0644]
testcases/t/147-regress-floatingmove.t [new file with mode: 0644]
testcases/t/148-regress-floatingmovews.t [new file with mode: 0644]
testcases/t/15-ipc-workspaces.t [deleted file]
testcases/t/150-regress-dock-restart.t [new file with mode: 0644]
testcases/t/151-regress-float-size.t [new file with mode: 0644]
testcases/t/152-regress-level-up.t [new file with mode: 0644]
testcases/t/153-floating-originalsize.t [new file with mode: 0644]
testcases/t/154-regress-multiple-dock.t [new file with mode: 0644]
testcases/t/155-floating-split-size.t [new file with mode: 0644]
testcases/t/156-fullscreen-focus.t [new file with mode: 0644]
testcases/t/157-regress-fullscreen-level-up.t [new file with mode: 0644]
testcases/t/158-wm_take_focus.t [new file with mode: 0644]
testcases/t/159-socketpaths.t [new file with mode: 0644]
testcases/t/16-nestedcons.t [deleted file]
testcases/t/161-regress-borders-restart.t [new file with mode: 0644]
testcases/t/162-regress-dock-urgent.t [new file with mode: 0644]
testcases/t/163-wm-state.t [new file with mode: 0644]
testcases/t/164-kill-win-vs-client.t [new file with mode: 0644]
testcases/t/165-for_window.t [new file with mode: 0644]
testcases/t/166-assign.t [new file with mode: 0644]
testcases/t/167-workspace_layout.t [new file with mode: 0644]
testcases/t/168-regress-fullscreen-restart.t [new file with mode: 0644]
testcases/t/169-border-toggle.t [new file with mode: 0644]
testcases/t/17-workspace.t [deleted file]
testcases/t/170-force_focus_wrapping.t [new file with mode: 0644]
testcases/t/171-config-migrate.t [new file with mode: 0644]
testcases/t/172-start-on-named-ws.t [new file with mode: 0644]
testcases/t/173-get-marks.t [new file with mode: 0644]
testcases/t/173-regress-focus-assign.t [new file with mode: 0644]
testcases/t/174-border-config.t [new file with mode: 0644]
testcases/t/174-regress-focus-toggle.t [new file with mode: 0644]
testcases/t/175-startup-notification.t [new file with mode: 0644]
testcases/t/176-workspace-baf.t [new file with mode: 0644]
testcases/t/177-bar-config.t [new file with mode: 0644]
testcases/t/178-regress-workspace-open.t [new file with mode: 0644]
testcases/t/179-regress-multiple-ws.t [new file with mode: 0644]
testcases/t/18-openkill.t [deleted file]
testcases/t/19-match.t [deleted file]
testcases/t/20-multiple-cmds.t [deleted file]
testcases/t/21-next-prev.t [deleted file]
testcases/t/22-split.t [deleted file]
testcases/t/24-move.t [deleted file]
testcases/t/26-regress-close.t [deleted file]
testcases/t/27-regress-floating-parent.t [deleted file]
testcases/t/28-open-order.t [deleted file]
testcases/t/29-focus-after-close.t [deleted file]
testcases/t/30-close-empty-split.t [deleted file]
testcases/t/31-stacking-order.t [deleted file]
testcases/t/32-move-workspace.t [deleted file]
testcases/t/33-size-hints.t [deleted file]
testcases/t/34-invalid-command.t [deleted file]
testcases/t/35-floating-focus.t [deleted file]
testcases/t/36-floating-ws-empty.t [deleted file]
testcases/t/37-floating-unmap.t [deleted file]
testcases/t/38-floating-attach.t [deleted file]
testcases/t/39-ws-numbers.t [deleted file]
testcases/t/40-focus-lost.t [deleted file]
testcases/t/41-resize.t [deleted file]
testcases/t/42-regress-move-floating.t [deleted file]
testcases/t/43-regress-floating-restart.t [deleted file]
testcases/t/44-regress-floating-resize.t [deleted file]
testcases/t/45-flattening.t [deleted file]
testcases/t/46-floating-reinsert.t [deleted file]
testcases/t/47-regress-floatingmove.t [deleted file]
testcases/t/48-regress-floatingmovews.t [deleted file]
testcases/t/50-regress-dock-restart.t [deleted file]
testcases/t/51-regress-float-size.t [deleted file]
testcases/t/52-regress-level-up.t [deleted file]
testcases/t/53-floating-originalsize.t [deleted file]
testcases/t/54-regress-multiple-dock.t [deleted file]
testcases/t/55-floating-split-size.t [deleted file]
testcases/t/56-fullscreen-focus.t [deleted file]
testcases/t/57-regress-fullscreen-level-up.t [deleted file]
testcases/t/58-wm_take_focus.t [deleted file]
testcases/t/59-socketpaths.t [deleted file]
testcases/t/61-regress-borders-restart.t [deleted file]
testcases/t/62-regress-dock-urgent.t [deleted file]
testcases/t/63-wm-state.t [deleted file]
testcases/t/64-kill-win-vs-client.t [deleted file]
testcases/t/65-for_window.t [deleted file]
testcases/t/66-assign.t [deleted file]
testcases/t/67-workspace_layout.t [deleted file]
testcases/t/68-regress-fullscreen-restart.t [deleted file]
testcases/t/69-border-toggle.t [deleted file]
testcases/t/70-force_focus_wrapping.t [deleted file]
testcases/t/71-config-migrate.t [deleted file]
testcases/t/72-start-on-named-ws.t [deleted file]
testcases/t/73-regress-focus-assign.t [deleted file]
testcases/t/74-regress-focus-toggle.t [deleted file]
testcases/t/lib/i3test.pm [deleted file]

index 454b2e3ac3b74ecfac12936862d1ed203e993e6d..cad6ad9a2120daffdde2a58c3b705fa253c0a77a 100644 (file)
@@ -31,6 +31,7 @@ i3-input/i3-input
 i3-nagbar/i3-nagbar
 i3-msg/i3-msg
 i3-config-wizard/i3-config-wizard
+libi3/libi3.a
 docs/*.html
 docs/*.aux
 docs/*.out
diff --git a/CMDMODE b/CMDMODE
deleted file mode 100644 (file)
index 95cb5bc..0000000
--- a/CMDMODE
+++ /dev/null
@@ -1,47 +0,0 @@
----------------------
-- Command mode
----------------------
-
-This is the grammar for the 'command mode' (your configuration file
-uses these commands, too).
-
-left  := <h> | <cursor-left>
-right := <l> | <cursor-right>
-up    := <j> | <cursor-up>
-down  := <k> | <cursor-down>
-
-where := <left|right|up|down> | <tag>
-move  := <m>
-snap  := <s>
-
-cmd     := [ <times> ] [ <move> | <snap> ] <where>
-with    := <w> { [ <times> ] <where> }+ <space> <cmd>
-jump    := [ "<window class>[/<window title>]" | <workspace> [ <column> <row> ] ]
-focus   := focus [ <times> | floating | tiling | ft ]
-  (travels the focus stack backwards, <times> number of times (by default 1).
-   So by specifying "focus 1" it selects the window which last had the focus
-   before you focused the current one window.
-   The following 3 special values are also valid:
-    'floating' (select the next floating window).
-      'tiling' (select the next tiling window).
-          'ft' (toggle tiling/floating: if the current window is floating,
-                select the next tiling window and vice-versa)
-special := [ exec <path> | kill | exit | restart ]
-
-input   := [ <cmd> | <with> | <jump> | <focus> | <special> ]
-
-you can cancel command mode by pressing escape anytime.
-
-Some examples:
-
-Select the window on the left:
-h
-
-Select the window two places on the left:
-2h
-
-Move window to the right:
-ml
-
-Move window and window on the bottom to the right:
-wk ml
diff --git a/DEPENDS b/DEPENDS
index ea7133a5552f7f8ae263538be754677fc685856a..61fb958697263632b09f5d1466748e1b3d81e39c 100644 (file)
--- a/DEPENDS
+++ b/DEPENDS
@@ -11,7 +11,7 @@
 │ xcb-proto   │ 1.3    │ 1.6    │ http://xcb.freedesktop.org/dist/       │
 │ libxcb      │ 1.1.93 │ 1.7    │ http://xcb.freedesktop.org/dist/       │
 │ xcb-util    │ 0.3.3  │ 0.3.8  │ http://xcb.freedesktop.org/dist/       │
-│ libev       │ 3.0    │ 4.04   │ http://libev.schmorp.de/               │
+│ libev       │ 4.0    │ 4.04   │ http://libev.schmorp.de/               │
 │ flex        │ 2.5.35 │ 2.5.35 │ http://flex.sourceforge.net/           │
 │ bison       │ 2.4.1  │ 2.4.1  │ http://www.gnu.org/software/bison/     │
 │ yajl        │ 1.0.8  │ 2.0.1  │ http://lloyd.github.com/yajl/          │
 │ docbook-xml │ 4.5    │ 4.5    │ http://www.methods.co.nz/asciidoc/     │
 │ libxcursor  │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/  │
 │ Xlib        │ 1.3.3  │ 1.4.3  │ http://ftp.x.org/pub/current/src/lib/  │
+│ PCRE        │ 8.12   │ 8.12   │ http://www.pcre.org/                   │
+│ libsn¹      │ 0.10   │ 0.12   │ http://freedesktop.org/wiki/Software/startup-notification
 └─────────────┴────────┴────────┴────────────────────────────────────────┘
+ ¹ libsn = libstartup-notification
 
- i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new
- dependencies.
-
- i3-wsbar is implemented in Perl and has the following dependencies:
-
-   • IPC::Run
-   • Try::Tiny
-   • AnyEvent
-   • AnyEvent::I3
-
- All of them are available at CPAN, see http://search.cpan.org/
- Use your distribution’s packages or cpan(1) to install them.
+ i3bar, i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any
+ new dependencies.
 
  i3-migrate-config-to-v4 is implemented in Perl, but it has no dependencies
  besides Perl 5.10.
diff --git a/LICENSE b/LICENSE
index 5771597b048a787f7a28cdfb68f1f269a383fe51..05f078e2b6e05ab9580079694d4ec830c90eab12 100644 (file)
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2009, Michael Stapelberg
+Copyright © 2009-2011, Michael Stapelberg and contributors
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
index 15ccf0e648f49385be5ef237cbf5cc53dc92d8d2..c0797839c97e9ffe5cd61ad98908b0caac8f0447 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -18,18 +18,21 @@ else
 UNUSED:=$(shell $(MAKE) loglevels.h)
 endif
 
-SUBDIRS=i3-msg i3-input i3-nagbar i3-config-wizard i3bar
+SUBDIRS:=i3-msg i3-input i3-nagbar i3-config-wizard i3bar
 
 # Depend on the specific file (.c for each .o) and on all headers
 src/%.o: src/%.c ${HEADERS}
-       echo "CC $<"
+       echo "[i3] CC $<"
        $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $<
 
 all: i3 subdirs
 
-i3: src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES}
-       echo "LINK i3"
-       $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+i3: libi3/libi3.a src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES}
+       echo "[i3] LINK i3"
+       $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
+
+libi3/%.a: libi3/*.c
+       $(MAKE) -C libi3
 
 subdirs:
        for dir in $(SUBDIRS); do \
@@ -39,7 +42,7 @@ subdirs:
        done
 
 loglevels.h:
-       echo "LOGLEVELS"
+       echo "[i3] LOGLEVELS"
        for file in $$(ls src/*.c src/*.y src/*.l | grep -v 'cfgparse.\(tab\|yy\).c'); \
        do \
                echo $$(basename $$file .c); \
@@ -51,35 +54,38 @@ loglevels.h:
        echo "};") > include/loglevels.h;
 
 src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS}
-       echo "LEX $<"
+       echo "[i3] LEX $<"
        flex -i -o$(@:.o=.c) $<
        $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
 
 src/cmdparse.yy.o: src/cmdparse.l src/cmdparse.y.o ${HEADERS}
-       echo "LEX $<"
+       echo "[i3] LEX $<"
        flex -Pcmdyy -i -o$(@:.o=.c) $<
        $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
 
 
 src/cfgparse.y.o: src/cfgparse.y ${HEADERS}
-       echo "YACC $<"
+       echo "[i3] YACC $<"
        bison --debug --verbose -b $(basename $< .y) -d $<
        $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
 
 src/cmdparse.y.o: src/cmdparse.y ${HEADERS}
-       echo "YACC $<"
+       echo "[i3] YACC $<"
        bison -p cmdyy --debug --verbose -b $(basename $< .y) -d $<
        $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
 
 
 install: all
-       echo "INSTALL"
+       echo "[i3] INSTALL"
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
        $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
        $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/
        $(INSTALL) -m 0755 i3-migrate-config-to-v4 $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(PREFIX)/bin/
        test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
        test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes
        $(INSTALL) -m 0644 i3.welcome $(DESTDIR)$(SYSCONFDIR)/i3/welcome
@@ -93,20 +99,20 @@ dist: distclean
        [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION}
        [ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
        mkdir i3-${VERSION}
-       cp i3-migrate-config-to-v4 i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION}
-       cp -r src i3-msg i3-nagbar i3-config-wizard i3bar yajl-fallback include man i3-${VERSION}
+       cp i3-migrate-config-to-v4 i3-sensible-* i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION}
+       cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar yajl-fallback include man i3-${VERSION}
        # Only copy toplevel documentation (important stuff)
        mkdir i3-${VERSION}/docs
        # Pre-generate documentation
-       make -C docs
-       make -C i3bar/doc
+       $(MAKE) -C docs
+       $(MAKE) -C i3bar/doc
        # Cleanup τεχ output files
        find docs -regex ".*\.\(aux\|out\|log\|toc\|bm\|dvi\|log\)" -exec rm '{}' \;
        find docs -maxdepth 1 -type f ! \( -name "*.xcf" -or -name "*.svg" \) -exec cp '{}' i3-${VERSION}/docs \;
        # Only copy source code from i3-input
        mkdir i3-${VERSION}/i3-input
        find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \;
-       sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION:=$(shell echo '${GIT_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
+       sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION:=$(shell /bin/echo '${GIT_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
        # Pre-generate a manpage to allow distributors to skip this step and save some dependencies
        $(MAKE) -C man
        cp man/*.1 i3-${VERSION}/man/
@@ -117,6 +123,7 @@ dist: distclean
 clean:
        rm -f src/*.o src/*.gcno src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.{output,dot} src/cmdparse.tab.{c,h} src/cmdparse.yy.c src/cmdparse.{output,dot} loglevels.tmp include/loglevels.h
        (which lcov >/dev/null 2>&1 && lcov -d . --zerocounters) || true
+       $(MAKE) -C libi3 clean
        $(MAKE) -C docs clean
        $(MAKE) -C man clean
        for dir in $(SUBDIRS); do \
index 3d9a8e1812f995df02520f0c5588dbc6096f9ccd..269ce0fd12992be1670ca400ce6113c53df83731 100644 (file)
@@ -10,15 +10,21 @@ packages for them.
 Please make sure the manpage for i3 will be properly created and installed
 in your package.
 
-Also please provide the path to a suitable terminal emulator which is installed
-as a dependency of your package (e.g. urxvt). On systems which have a special
-commend to launch the best available terminal emulator, please use this one
-(e.g. x-terminal-emulator on debian).
+Please check the i3-sensible-{editor,pager,terminal} scripts and modify them if
+necessary. The former two provide fallbacks in case $PAGER or $EDITOR is not
+set (which might be more common than you think, because they have to be set in
+~/.xsession, not in the shell configuration!) while the latter tries to launch
+a terminal emulator. The scripts are most prominently used in i3-nagbar, which
+alerts the user when the configuration is broken for some reason. Also,
+i3-sensible-terminal is used in the default configuration.
 
-On debian, this looks like this:
+If your distribution has a mechanism to get the preferred terminal, such as the
+x-terminal-emulator symlink in Debian, please use it in i3-sensible-terminal.
+
+On debian, compilation and installing the manpages looks like this:
 
        # Compilation
-       $(MAKE) TERM_EMU=x-terminal-emulator
+       $(MAKE)
        $(MAKE) -C man
 
        # Installation
diff --git a/RELEASE-NOTES-4.1 b/RELEASE-NOTES-4.1
new file mode 100644 (file)
index 0000000..de06c3a
--- /dev/null
@@ -0,0 +1,100 @@
+
+ ┌────────────────────────────┐
+ │ Release notes for i3 v4.1  │
+ └────────────────────────────┘
+
+This is the second release of the new major version of i3, v4.1. It brings some
+new (and long-awaited) features, the most prominent being tray support in i3bar
+(for NetworkManager, Skype, etc.). 
+
+The assign syntax has changed to support criteria now. Also, criteria support
+regular expressions (using PCRE) now. Check the userguide for the new syntax.
+i3-nagbar will automatically display a warning when you use the old syntax.
+
+i3 now supports startup notifications, meaning that during an application
+starts up, the mouse cursor will change to 'watch' on the root window. Also,
+the application window will appear on the workspace on which it was launched
+(not on the currently focused workspace). Some applications don’t support
+startup notifications. If the cursor change bothers you, turn it off by using
+the --no-startup-id flag (see the userguide).
+
+This release has been in use by many users and is considered stable. Please
+upgrade.
+
+
+ ┌────────────────────────────┐
+ │ New features               │
+ └────────────────────────────┘
+
+  • Switch to dpkg-source 3.0 (quilt) and compat level 7
+  • Implement system tray support in i3bar (for NetworkManager, Skype, …)
+  • i3bar is now configurable in the i3 configfile
+  • Implement support for PCRE regular expressions in criteria
+  • Implement a new assign syntax which uses criteria
+  • Sort named workspaces whose name starts with a number accordingly
+  • Warn on duplicate bindings for the same key
+  • Restrict 'resize' command to left/right for horizontal containers, up/down
+    for vertical containers
+  • Implement support for startup notifications (cursor will change to 'watch',
+    started applications show up on the workspace they have been launched on)
+  • Implement the GET_MARKS IPC request to get all marks
+  • Implement the new_float config option (border style for floating windows)
+  • Implement passing IPC sockets to i3 (systemd-style socket activation)
+  • Implement the 'move output' command to move containers to a specific output
+  • Implement focus switching for floating windows
+  • Implement the window_role criterion (for matching multi-window apps)
+  • Implement a force_xinerama configuration directive
+  • Implement the --get-socketpath, useful for scripts using the IPC interface
+  • Implement the 'move workspace next' and 'move workspace prev' commands
+  • Implement the 'workspace back_and_forth' command and related configuration
+    option
+  • Implement the move command for floating windows
+  • i3 will now handle arbitrary text arguments by sending them as an IPC
+    command, like i3-msg: 'i3 reload' or 'i3 move workspace 3'
+  • Introduce the i3-sensible-{pager,editor,terminal} scripts to execute
+    $PAGER, $EDITOR or an available terminal emulator
+  • i3-input: implement -F (format) option
+
+
+ ┌────────────────────────────┐
+ │ Bugfixes                   │
+ └────────────────────────────┘
+
+  • Bugfix: Preserve marks when restarting
+  • Bugfix: Correctly free old assignments when reloading
+  • Bugfix: Fix flickering when moving floating windows between monitors
+  • Bugfix: Correctly handle ConfigureRequests for floating windows in a
+    multi-monitor environment.
+  • Bugfix: Fix size of floating windows with X11 borders
+  • Bugfix: Always adjust floating window position when moving to another
+    output
+  • Bugfix: Avoid out-of-bounds coordinates when moving floating windows
+  • Bugfix: Don’t steal focus when a window gets destroyed
+  • Bugfix: Correctly split key/value when parsing variables
+  • Bugfix: Correctly revert focus to other floating windows when closing a
+    floating window
+  • Bugfix: Don’t leak the error logfile file descriptor
+  • Bugfix: Don’t steal focus when a window opens on an invisible workspace due
+    to assignments
+  • Bugfix: Fix handling of Mode_switch in i3-input
+  • Bugfix: Close invisible workspaces when they become empty
+  • Bugfix: Don’t invoke interactive resizing when clicking on the decoration
+    of a split container with more than one child (switch focus instead)
+  • Bugfix: Make named workspace assignments work again
+  • Bugfix: RandR: Correctly keep focus on the focused workspace when an output
+    disappears
+  • Bugfix: Insert container at the correct position on workspace level when
+    workspace_layout == default
+
+
+ ┌────────────────────────────┐
+ │ Thanks!                    │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+  aksr, alexanderb, atsutane, bacardi55, bjonnh, brian, cls, don, donald,
+  eeemsi, f8l, fernandotcl, isolnchip, julien, motif, mw, mxf, phnom, pl,
+  pnutzh4x0r, raphael, sardemff7, stfn, thomasba, xeen
+
+-- Michael Stapelberg, 2011-11-11
diff --git a/TODO b/TODO
deleted file mode 100644 (file)
index 40b56ce..0000000
--- a/TODO
+++ /dev/null
@@ -1,7 +0,0 @@
-
-Please see http://i3wm.org/ for our bugtracker.
-
-Some old notes, just to not lose them:
- * was passiert, wenn zwei fenster fullscreen wollen auf dem selben workspace?
- * OpenOffice
- * funktioniert xinerama etc. mit --below? insbesondere das fokus-verschieben in andere screens
index ce41f287394af52d3400517ab19875dcda845f7f..a9d1661879f5c9f55d8e396f6bb71a5624988ee1 100644 (file)
--- a/common.mk
+++ b/common.mk
@@ -12,7 +12,6 @@ ifndef SYSCONFDIR
     SYSCONFDIR=$(PREFIX)/etc
   endif
 endif
-TERM_EMU=xterm
 # The escaping is absurd, but we need to escape for shell, sed, make, define
 GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f $(TOPDIR)/.git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' $(TOPDIR)/.git/HEAD || echo 'unknown'))"
 VERSION:=$(shell git describe --tags --abbrev=0)
@@ -22,9 +21,17 @@ $(error "pkg-config was not found")
 endif
 
 # An easier way to get CFLAGS and LDFLAGS falling back in case there's
-# no pkg-config support for certain libraries
-cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1))
-ldflags_for_lib = $(shell pkg-config --exists $(1) && pkg-config --libs $(1) || echo -l$(2))
+# no pkg-config support for certain libraries.
+#
+# NOTE that you must not use a blank after comma when calling this:
+#     $(call ldflags_for_lib name, fallback) # bad
+#     $(call ldflags_for_lib name,fallback) # good
+# Otherwise, the compiler will get -l foo instead of -lfoo
+#
+# We redirect stderr to /dev/null because pkg-config prints an error if support
+# for gnome-config was enabled but gnome-config is not actually installed.
+cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1) 2>/dev/null)
+ldflags_for_lib = $(shell pkg-config --exists 2>/dev/null $(1) && pkg-config --libs $(1) 2>/dev/null || echo -l$(2))
 
 CFLAGS += -std=c99
 CFLAGS += -pipe
@@ -34,7 +41,7 @@ CFLAGS += -Wall
 CFLAGS += -Wunused-value
 CFLAGS += -Iinclude
 CFLAGS += $(call cflags_for_lib, xcb-keysyms)
-ifeq ($(shell pkg-config --exists xcb-util || echo 1),1)
+ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1)
 CPPFLAGS += -DXCB_COMPAT
 CFLAGS += $(call cflags_for_lib, xcb-atom)
 CFLAGS += $(call cflags_for_lib, xcb-aux)
@@ -49,27 +56,35 @@ CFLAGS += $(call cflags_for_lib, xcursor)
 CFLAGS += $(call cflags_for_lib, x11)
 CFLAGS += $(call cflags_for_lib, yajl)
 CFLAGS += $(call cflags_for_lib, libev)
+CFLAGS += $(call cflags_for_lib, libpcre)
+CFLAGS += $(call cflags_for_lib, libstartup-notification-1.0)
 CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
 CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
-CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\"
+
+ifeq ($(shell pkg-config --atleast-version=8.10 libpcre 2>/dev/null && echo 1),1)
+CPPFLAGS += -DPCRE_HAS_UCP=1
+endif
 
 LIBS += -lm
-LIBS += $(call ldflags_for_lib, xcb-event, xcb-event)
-LIBS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms)
-ifeq ($(shell pkg-config --exists xcb-util || echo 1),1)
-LIBS += $(call ldflags_for_lib, xcb-atom, xcb-atom)
-LIBS += $(call ldflags_for_lib, xcb-aux, xcb-aux)
+LIBS += -L $(TOPDIR)/libi3 -li3
+LIBS += $(call ldflags_for_lib, xcb-event,xcb-event)
+LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms)
+ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1)
+LIBS += $(call ldflags_for_lib, xcb-atom,xcb-atom)
+LIBS += $(call ldflags_for_lib, xcb-aux,xcb-aux)
 else
 LIBS += $(call ldflags_for_lib, xcb-util)
 endif
-LIBS += $(call ldflags_for_lib, xcb-icccm, xcb-icccm)
-LIBS += $(call ldflags_for_lib, xcb-xinerama, xcb-xinerama)
-LIBS += $(call ldflags_for_lib, xcb-randr, xcb-randr)
-LIBS += $(call ldflags_for_lib, xcb, xcb)
-LIBS += $(call ldflags_for_lib, xcursor, Xcursor)
-LIBS += $(call ldflags_for_lib, x11, X11)
-LIBS += $(call ldflags_for_lib, yajl, yajl)
-LIBS += $(call ldflags_for_lib, libev, ev)
+LIBS += $(call ldflags_for_lib, xcb-icccm,xcb-icccm)
+LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama)
+LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr)
+LIBS += $(call ldflags_for_lib, xcb,xcb)
+LIBS += $(call ldflags_for_lib, xcursor,Xcursor)
+LIBS += $(call ldflags_for_lib, x11,X11)
+LIBS += $(call ldflags_for_lib, yajl,yajl)
+LIBS += $(call ldflags_for_lib, libev,ev)
+LIBS += $(call ldflags_for_lib, libpcre,pcre)
+LIBS += $(call ldflags_for_lib, libstartup-notification-1.0,startup-notification-1)
 
 # Please test if -Wl,--as-needed works on your platform and send me a patch.
 # it is known not to work on Darwin (Mac OS X)
index 990badb2dd93e221e0e782d2ab1a5d1bd76560b6..be459af507f92a103d9a2c1b42f616358210e5f3 100644 (file)
@@ -1,8 +1,60 @@
-i3-wm (4.0.3-0) unstable; urgency=low
-
-  * NOT YET RELEASED!
-
- -- Michael Stapelberg <michael@stapelberg.de>  Sun, 28 Aug 2011 20:17:31 +0200
+i3-wm (4.1-1) unstable; urgency=low
+
+  * Switch to dpkg-source 3.0 (quilt) and compat level 7
+  * Implement system tray support in i3bar (for NetworkManager, Skype, …)
+  * i3bar is now configurable in the i3 configfile
+  * Implement support for PCRE regular expressions in criteria
+  * Implement a new assign syntax which uses criteria
+  * Sort named workspaces whose name starts with a number accordingly
+  * Warn on duplicate bindings for the same key
+  * Restrict 'resize' command to left/right for horizontal containers, up/down
+    for vertical containers
+  * Implement support for startup notifications (cursor will change to 'watch',
+    started applications show up on the workspace they have been launched on)
+  * Implement the GET_MARKS IPC request to get all marks
+  * Implement the new_float config option (border style for floating windows)
+  * Implement passing IPC sockets to i3 (systemd-style socket activation)
+  * Implement the 'move output' command to move containers to a specific output
+  * Implement focus switching for floating windows
+  * Implement the window_role criterion (for matching multi-window apps)
+  * Implement a force_xinerama configuration directive
+  * Implement the --get-socketpath, useful for scripts using the IPC interface
+  * Implement the 'move workspace next' and 'move workspace prev' commands
+  * Implement the 'workspace back_and_forth' command and related configuration
+    option
+  * Implement the move command for floating windows
+  * i3 will now handle arbitrary text arguments by sending them as an IPC
+    command, like i3-msg: 'i3 reload' or 'i3 move workspace 3'
+  * Introduce the i3-sensible-{pager,editor,terminal} scripts to execute
+    $PAGER, $EDITOR or an available terminal emulator
+  * i3-input: implement -F (format) option
+  * Bugfix: Preserve marks when restarting
+  * Bugfix: Correctly free old assignments when reloading
+  * Bugfix: Fix flickering when moving floating windows between monitors
+  * Bugfix: Correctly handle ConfigureRequests for floating windows in a
+    multi-monitor environment.
+  * Bugfix: Fix size of floating windows with X11 borders
+  * Bugfix: Always adjust floating window position when moving to another
+    output
+  * Bugfix: Avoid out-of-bounds coordinates when moving floating windows
+  * Bugfix: Don’t steal focus when a window gets destroyed
+  * Bugfix: Correctly split key/value when parsing variables
+  * Bugfix: Correctly revert focus to other floating windows when closing a
+    floating window
+  * Bugfix: Don’t leak the error logfile file descriptor
+  * Bugfix: Don’t steal focus when a window opens on an invisible workspace due
+    to assignments
+  * Bugfix: Fix handling of Mode_switch in i3-input
+  * Bugfix: Close invisible workspaces when they become empty
+  * Bugfix: Don’t invoke interactive resizing when clicking on the decoration
+    of a split container with more than one child (switch focus instead)
+  * Bugfix: Make named workspace assignments work again
+  * Bugfix: RandR: Correctly keep focus on the focused workspace when an output
+    disappears
+  * Bugfix: Insert container at the correct position on workspace level when
+    workspace_layout == default
+
+ -- Michael Stapelberg <michael@stapelberg.de>  Fri, 11 Nov 2011 21:28:15 +0000
 
 i3-wm (4.0.2-1) unstable; urgency=low
 
index 1e8b314962144c26d5e0e50fd29d2ca327864913..7f8f011eb73d6043d2e6db9d2c101195ae2801f2 100644 (file)
@@ -1 +1 @@
-6
+7
index b546e650d5555687fdea71f6bcbd83f65275a8c2..e3786cfb4f19854133381b873192cdd4b91f7cd1 100644 (file)
@@ -3,7 +3,7 @@ Section: utils
 Priority: extra
 Maintainer: Michael Stapelberg <michael@stapelberg.de>
 DM-Upload-Allowed: yes
-Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra
+Build-Depends: debhelper (>= 7.0.50~), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra, libpcre3-dev, libstartup-notification0-dev (>= 0.10)
 Standards-Version: 3.9.2
 Homepage: http://i3wm.org/
 
@@ -14,25 +14,22 @@ Depends: i3-wm, ${misc:Depends}
 Recommends: i3lock, suckless-tools, i3status
 Description: metapackage (i3 window manager, screen locker, menu, statusbar)
  This metapackage installs the i3 window manager (i3-wm), the i3lock screen
- locker (slightly improved version of slock), suckless-tools which contains
- dmenu and i3status, which displays useful information about your system in
- combination with dzen2. These are all the tools you need to use the i3 window
- manager efficiently.
+ locker, i3status (for system information) and suckless-tools (for dmenu).
+ These are all the tools you need to use the i3 window manager efficiently.
 
 Package: i3-wm
 Architecture: any
 Section: x11
-Depends: ${shlibs:Depends}, ${misc:Depends}, x11-utils
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, x11-utils
 Provides: x-window-manager
 Suggests: rxvt-unicode | x-terminal-emulator
-Recommends: xfonts-base, libanyevent-i3-perl, libanyevent-perl, libipc-run-perl
+Recommends: xfonts-base
 Description: improved dynamic tiling window manager
- Key features of i3 are correct implementation of Xinerama (workspaces are
- assigned to virtual screens, i3 does the right thing when attaching new
- monitors), XrandR support (not done yet), horizontal and vertical columns
- (think of a table) in tiling. Also, special focus is on writing clean,
- readable and well documented code. i3 uses xcb for asynchronous
- communication with X11, and has several measures to be very fast.
+ Key features of i3 are good documentation, reasonable defaults (changeable in
+ a simple configuration file) and good multi-monitor support. The user
+ interface is designed for power users and emphasizes keyboard usage. i3 uses
+ XCB for asynchronous communication with X11 and aims to be fast and
+ light-weight.
  .
  Please be aware i3 is primarily targeted at advanced users and developers.
 
index d59c57367353fc2c7078e45e4d2baadedccefe01..e5896855a0c396d0e49fc3741669a71d0da233d3 100644 (file)
@@ -14,3 +14,6 @@ docs/wsbar.html
 docs/wsbar.png
 docs/keyboard-layer1.png
 docs/keyboard-layer2.png
+docs/testsuite.html
+docs/i3-sync-working.png
+docs/i3-sync.png
diff --git a/debian/i3-wm.manpages b/debian/i3-wm.manpages
new file mode 100644 (file)
index 0000000..2de3a99
--- /dev/null
@@ -0,0 +1,10 @@
+man/i3.1
+man/i3-msg.1
+man/i3-input.1
+man/i3-nagbar.1
+man/i3-config-wizard.1
+man/i3-migrate-config-to-v4.1
+man/i3-sensible-pager.1
+man/i3-sensible-editor.1
+man/i3-sensible-terminal.1
+i3bar/doc/i3bar.1
diff --git a/debian/patches/manpage-x-terminal-emulator.patch b/debian/patches/manpage-x-terminal-emulator.patch
new file mode 100644 (file)
index 0000000..61ff016
--- /dev/null
@@ -0,0 +1,14 @@
+## Description: Document Debian-specific x-terminal-emulator in the manpage.
+## Origin/Author: Michael Stapelberg
+Index: i3-4.1/man/i3-sensible-terminal.man
+===================================================================
+--- i3-4.1.orig/man/i3-sensible-terminal.man   2011-11-11 22:38:06.508025537 +0000
++++ i3-4.1/man/i3-sensible-terminal.man        2011-11-11 22:38:04.752994892 +0000
+@@ -22,6 +22,7 @@
+ It tries to start one of the following (in that order):
+ * $TERMINAL (this is a non-standard variable)
++* x-terminal-emulator (only on Debian)
+ * xterm
+ * urxvt
+ * rxvt
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644 (file)
index 0000000..08d60ae
--- /dev/null
@@ -0,0 +1,2 @@
+use-x-terminal-emulator.patch
+manpage-x-terminal-emulator.patch
diff --git a/debian/patches/use-x-terminal-emulator.patch b/debian/patches/use-x-terminal-emulator.patch
new file mode 100644 (file)
index 0000000..fd515c1
--- /dev/null
@@ -0,0 +1,21 @@
+## Description: Use Debian-specific x-terminal-emulator in i3-sensible-terminal
+## Origin/Author: Michael Stapelberg
+--- a/i3-sensible-terminal.O   2011-11-11 22:03:52.414218386 +0000
++++ b/i3-sensible-terminal     2011-11-11 22:04:38.372020210 +0000
+@@ -1,13 +1,11 @@
+ #!/bin/sh
+ # This script tries to exec a terminal emulator by trying some known terminal
+ # emulators.
+-#
+-# 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.
+-# Please don't touch the first line, though:
+ which $TERMINAL >/dev/null && exec $TERMINAL "$@"
++# Debian-specific: use x-terminal-emulator
++which x-terminal-emulator >/dev/null && exec x-terminal-emulator "$@"
++
+ # Hopefully one of these is installed:
+ which xterm >/dev/null && exec xterm "$@"
+ which urxvt >/dev/null && exec urxvt "$@"
index e62dd5eec074e09b51bfc3de626a378c4e590489..a488469d717579930d52268fa827d506f79aa760 100755 (executable)
@@ -1,85 +1,37 @@
 #!/usr/bin/make -f
-# -*- makefile -*-
-# Sample debian/rules that uses debhelper.
-# This file was originally written by Joey Hess and Craig Small.
-# As a special exception, when this file is copied by dh-make into a
-# dh-make output file, you may use that output file without restriction.
-# This special exception was added by Craig Small in version 0.37 of dh-make.
+# vi: ts=8 sw=8 noet
 
-# Uncomment this to turn on verbose mode.
-#export DH_VERBOSE=1
-
-config.status: configure
-       dh_testdir
-       touch $@
-
-build: build-arch build-indep
-
-build-arch: build-stamp
-build-indep: build-stamp
+DPKG_EXPORT_BUILDFLAGS = 1
+-include /usr/share/dpkg/buildflags.mk
 
+build: build-stamp
 build-stamp:
-       dh_testdir
+       dh build
+       touch build-stamp
 
-       # Add here commands to compile the package.
-       $(MAKE) TERM_EMU=x-terminal-emulator
-       $(MAKE) -C man
-       $(MAKE) -C docs
+clean:
+       dh clean
 
-       touch $@
+install: build install-stamp
+install-stamp:
+       dh install
+       touch install-stamp
 
-clean: 
-       dh_testdir
-       dh_testroot
-       rm -f build-stamp
+binary-arch: install
+       dh binary-arch
 
-       # Add here commands to clean up after the build process.
-       [ ! -f Makefile ] || $(MAKE) distclean
+binary-indep: install
+       dh binary-indep
 
-       dh_clean 
+binary: binary-arch binary-indep
 
-install: build
-       dh_testdir
-       dh_testroot
-       dh_clean -k 
-       dh_installdirs
+override_dh_auto_build:
+       $(MAKE)
+       $(MAKE) -C man
+       $(MAKE) -C docs
 
-       # Add here commands to install the package into debian/i3-wm
+override_dh_install:
        $(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install
-       mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1
-       cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
-       cp man/i3-msg.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
-       cp man/i3-input.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
-       cp man/i3-nagbar.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
-       cp man/i3-config-wizard.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
-       cp man/i3-migrate-config-to-v4.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
-       cp i3bar/doc/i3bar.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
-
 
-# Build architecture-independent files here.
-binary-indep: build install
-# We have nothing to do by default.
-
-# Build architecture-dependent files here.
-binary-arch: build install
-       dh_testdir
-       dh_testroot
-       dh_installchangelogs 
-       dh_installdocs
-       dh_installexamples
-       dh_installdebconf       
-       dh_installinit
-       dh_installman
-       dh_installwm
-       dh_link
+override_dh_strip:
        dh_strip --dbg-package=i3-wm-dbg
-       dh_compress
-       dh_fixperms
-       dh_installdeb
-       dh_shlibdeps
-       dh_gencontrol
-       dh_md5sums
-       dh_builddeb
-
-binary: binary-indep binary-arch
-.PHONY: build clean binary-indep binary-arch binary install 
index 9d70243df13be1154cb72a2d97bb1f0cae252535..d1e0768ae4f8a086e174df2c46d783450b0ad611 100644 (file)
@@ -1,23 +1,28 @@
+# To pass additional parameters for asciidoc
+ASCIIDOC=asciidoc
 
-all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf
+all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf testsuite.html
 
 hacking-howto.html: hacking-howto
-       asciidoc -a toc -n $<
+       $(ASCIIDOC) -a toc -n $<
 
 debugging.html: debugging
-       asciidoc -n $<
+       $(ASCIIDOC) -n $<
 
 userguide.html: userguide
-       asciidoc -a toc -n $<
+       $(ASCIIDOC) -a toc -n $<
+
+testsuite.html: testsuite
+       $(ASCIIDOC) -a toc -n $<
 
 ipc.html: ipc
-       asciidoc -a toc -n $<
+       $(ASCIIDOC) -a toc -n $<
 
 multi-monitor.html: multi-monitor
-       asciidoc -a toc -n $<
+       $(ASCIIDOC) -a toc -n $<
 
 wsbar.html: wsbar
-       asciidoc -a toc -n $<
+       $(ASCIIDOC) -a toc -n $<
 
 refcard.pdf: refcard.tex
        pdflatex refcard.tex && pdflatex refcard.tex
diff --git a/docs/asciidoc-git.conf b/docs/asciidoc-git.conf
new file mode 100644 (file)
index 0000000..24dcb59
--- /dev/null
@@ -0,0 +1,662 @@
+#\r
+# xhtml11.conf\r
+#\r
+# Asciidoc configuration file.\r
+# xhtml11 backend, generates XHTML 1.1 conformant markup.\r
+#\r
+\r
+[miscellaneous]\r
+outfilesuffix=.html\r
+\r
+[attributes]\r
+basebackend=html\r
+basebackend-html=\r
+basebackend-xhtml11=\r
+\r
+[replacements2]\r
+# Line break.\r
+(?m)^(.*)\s\+$=\1<br />\r
+\r
+[replacements]\r
+ifdef::asciidoc7compatible[]\r
+# Superscripts.\r
+\^(.+?)\^=<sup>\1</sup>\r
+# Subscripts.\r
+~(.+?)~=<sub>\1</sub>\r
+endif::asciidoc7compatible[]\r
+\r
+[ruler-blockmacro]\r
+<hr />\r
+\r
+[pagebreak-blockmacro]\r
+<div style="page-break-after:always"></div>\r
+\r
+[blockdef-pass]\r
+asciimath-style=template="asciimathblock",subs=[]\r
+latexmath-style=template="latexmathblock",subs=[]\r
+\r
+[macros]\r
+# math macros.\r
+# Special characters are escaped in HTML math markup.\r
+(?su)[\\]?(?P<name>asciimath|latexmath):(?P<subslist>\S*?)\[(?P<passtext>.*?)(?<!\\)\]=[specialcharacters]\r
+(?u)^(?P<name>asciimath|latexmath)::(?P<subslist>\S*?)(\[(?P<passtext>.*?)\])$=#[specialcharacters]\r
+\r
+[asciimath-inlinemacro]\r
+`{passtext}`\r
+\r
+[asciimath-blockmacro]\r
+<div class="mathblock{role? {role}}"{id? id="{id}"}>\r
+<div class="content">\r
+<div class="title">{title}</div>\r
+`{passtext}`\r
+</div></div>\r
+\r
+[asciimathblock]\r
+<div class="mathblock{role? {role}}"{id? id="{id}"}>\r
+<div class="content">\r
+<div class="title">{title}</div>\r
+`|`\r
+</div></div>\r
+\r
+[latexmath-inlinemacro]\r
+{passtext}\r
+\r
+[latexmath-blockmacro]\r
+<div class="mathblock{role? {role}}"{id? id="{id}"}>\r
+<div class="content">\r
+<div class="title">{title}</div>\r
+{passtext}\r
+</div></div>\r
+\r
+[latexmathblock]\r
+<div class="mathblock{role? {role}}"{id? id="{id}"}>\r
+<div class="content">\r
+<div class="title">{title}</div>\r
+|\r
+</div></div>\r
+\r
+[image-inlinemacro]\r
+<span class="image{role? {role}}">\r
+<a class="image" href="{link}">\r
+{data-uri%}<img src="{imagesdir=}{imagesdir?/}{target}" alt="{alt={target}}"{width? width="{width}"}{height? height="{height}"}{title? title="{title}"} />\r
+{data-uri#}<img alt="{alt={target}}"{width? width="{width}"}{height? height="{height}"}{title? title="{title}"} src="data:image/{eval:os.path.splitext('{target}')[1][1:]};base64,\r
+{data-uri#}{sys3:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{imagesdir=}","{target}")}"}" />\r
+{link#}</a>\r
+</span>\r
+\r
+[image-blockmacro]\r
+<div class="imageblock{style? {style}}{role? {role}}"{id? id="{id}"}{align? style="text-align:{align};"}{float? style="float:{float};"}>\r
+<div class="content">\r
+<a class="image" href="{link}">\r
+{data-uri%}<img src="{imagesdir=}{imagesdir?/}{target}" alt="{alt={target}}"{width? width="{width}"}{height? height="{height}"} />\r
+{data-uri#}<img alt="{alt={target}}"{width? width="{width}"}{height? height="{height}"} src="data:image/{eval:os.path.splitext('{target}')[1][1:]};base64,\r
+{data-uri#}{sys:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{imagesdir=}","{target}")}"}" />\r
+{link#}</a>\r
+</div>\r
+<div class="title">{caption={figure-caption} {counter:figure-number}. }{title}</div>\r
+</div>\r
+\r
+[unfloat-blockmacro]\r
+<div style="clear:both;"></div>\r
+\r
+[indexterm-inlinemacro]\r
+# Index term.\r
+{empty}\r
+\r
+[indexterm2-inlinemacro]\r
+# Index term.\r
+# Single entry index term that is visible in the primary text flow.\r
+{1}\r
+\r
+[footnote-inlinemacro]\r
+# footnote:[<text>].\r
+<span class="footnote"><br />[{0}]<br /></span>\r
+\r
+[footnoteref-inlinemacro]\r
+# footnoteref:[<id>], create reference to footnote.\r
+{2%}<span class="footnoteref"><br /><a href="#_footnote_{1}">[{1}]</a><br /></span>\r
+# footnoteref:[<id>,<text>], create footnote with ID.\r
+{2#}<span class="footnote" id="_footnote_{1}"><br />[{2}]<br /></span>\r
+\r
+[callout-inlinemacro]\r
+ifndef::icons[]\r
+<b>&lt;{index}&gt;</b>\r
+endif::icons[]\r
+ifdef::icons[]\r
+ifndef::data-uri[]\r
+<img src="{icon={iconsdir}/callouts/{index}.png}" alt="{index}" />\r
+endif::data-uri[]\r
+ifdef::data-uri[]\r
+<img alt="{index}" src="data:image/png;base64,\r
+{sys:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{icon={iconsdir}/callouts/{index}.png}")}"}" />\r
+endif::data-uri[]\r
+endif::icons[]\r
+\r
+# Comment line macros.\r
+[comment-inlinemacro]\r
+{showcomments#}<br /><span class="comment">{passtext}</span><br />\r
+\r
+[comment-blockmacro]\r
+{showcomments#}<p><span class="comment">{passtext}</span></p>\r
+\r
+[literal-inlinemacro]\r
+# Inline literal.\r
+<tt>{passtext}</tt>\r
+\r
+# List tags.\r
+[listtags-bulleted]\r
+list=<div class="ulist{style? {style}}{compact-option? compact}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ul>|</ul></div>\r
+item=<li>|</li>\r
+text=<p>|</p>\r
+\r
+[listtags-numbered]\r
+# The start attribute is not valid XHTML 1.1 but all browsers support it.\r
+list=<div class="olist{style? {style}}{compact-option? compact}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ol class="{style}"{start? start="{start}"}>|</ol></div>\r
+item=<li>|</li>\r
+text=<p>|</p>\r
+\r
+[listtags-labeled]\r
+list=<div class="dlist{compact-option? compact}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<dl>|</dl></div>\r
+entry=\r
+label=\r
+term=<dt class="hdlist1{strong-option? strong}">|</dt>\r
+item=<dd>|</dd>\r
+text=<p>|</p>\r
+\r
+[listtags-horizontal]\r
+list=<div class="hdlist{compact-option? compact}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<table>{labelwidth?<col width="{labelwidth}%" />}{itemwidth?<col width="{itemwidth}%" />}|</table></div>\r
+label=<td class="hdlist1{strong-option? strong}">|</td>\r
+term=|<br />\r
+entry=<tr>|</tr>\r
+item=<td class="hdlist2">|</td>\r
+text=<p style="margin-top: 0;">|</p>\r
+\r
+[listtags-qanda]\r
+list=<div class="qlist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ol>|</ol></div>\r
+entry=<li>|</li>\r
+label=\r
+term=<p><em>|</em></p>\r
+item=\r
+text=<p>|</p>\r
+\r
+[listtags-callout]\r
+ifndef::icons[]\r
+list=<div class="colist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ol>|</ol></div>\r
+item=<li>|</li>\r
+text=<p>|</p>\r
+endif::icons[]\r
+ifdef::icons[]\r
+list=<div class="colist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<table>|</table></div>\r
+ifndef::data-uri[]\r
+item=<tr><td><img src="{iconsdir}/callouts/{listindex}.png" alt="{listindex}" /></td><td>|</td></tr>\r
+endif::data-uri[]\r
+ifdef::data-uri[]\r
+item=<tr><td><img alt="{listindex}" src="data:image/png;base64, {sys:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{icon={iconsdir}/callouts/{listindex}.png}")}"}" /></td><td>|</td></tr>\r
+endif::data-uri[]\r
+text=|\r
+endif::icons[]\r
+\r
+[listtags-glossary]\r
+list=<div class="dlist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<dl>|</dl></div>\r
+label=\r
+entry=\r
+term=<dt>|</dt>\r
+item=<dd>|</dd>\r
+text=<p>|</p>\r
+\r
+[listtags-bibliography]\r
+list=<div class="ulist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ul>|</ul></div>\r
+item=<li>|</li>\r
+text=<p>|</p>\r
+\r
+[tags]\r
+# Quoted text.\r
+emphasis=<em>{1?<span class="{1}">}|{1?</span>}</em>\r
+strong=<strong>{1?<span class="{1}">}|{1?</span>}</strong>\r
+monospaced=<tt>{1?<span class="{1}">}|{1?</span>}</tt>\r
+singlequoted={lsquo}{1?<span class="{1}">}|{1?</span>}{rsquo}\r
+doublequoted={ldquo}{1?<span class="{1}">}|{1?</span>}{rdquo}\r
+unquoted={1?<span class="{1}">}|{1?</span>}\r
+superscript=<sup>{1?<span class="{1}">}|{1?</span>}</sup>\r
+subscript=<sub>{1?<span class="{1}">}|{1?</span>}</sub>\r
+\r
+ifdef::deprecated-quotes[]\r
+# Override with deprecated quote attributes.\r
+emphasis={role?<span class="{role}">}<em{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</em>{role?</span>}\r
+strong={role?<span class="{role}">}<strong{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</strong>{role?</span>}\r
+monospaced={role?<span class="{role}">}<tt{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</tt>{role?</span>}\r
+singlequoted={role?<span class="{role}">}{1,2,3?<span style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?">}{amp}#8216;|{amp}#8217;{1,2,3?</span>}{role?</span>}\r
+doublequoted={role?<span class="{role}">}{1,2,3?<span style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?">}{amp}#8220;|{amp}#8221;{1,2,3?</span>}{role?</span>}\r
+unquoted={role?<span class="{role}">}{1,2,3?<span style="{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}">}|{1,2,3?</span>}{role?</span>}\r
+superscript={role?<span class="{role}">}<sup{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</sup>{role?</span>}\r
+subscript={role?<span class="{role}">}<sub{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</sub>{role?</span>}\r
+endif::deprecated-quotes[]\r
+\r
+# Inline macros\r
+[http-inlinemacro]\r
+<a href="{name}:{target}">{0={name}:{target}}</a>\r
+[https-inlinemacro]\r
+<a href="{name}:{target}">{0={name}:{target}}</a>\r
+[ftp-inlinemacro]\r
+<a href="{name}:{target}">{0={name}:{target}}</a>\r
+[file-inlinemacro]\r
+<a href="{name}:{target}">{0={name}:{target}}</a>\r
+[irc-inlinemacro]\r
+<a href="{name}:{target}">{0={name}:{target}}</a>\r
+[mailto-inlinemacro]\r
+<a href="mailto:{target}">{0={target}}</a>\r
+[link-inlinemacro]\r
+<a href="{target}">{0={target}}</a>\r
+[callto-inlinemacro]\r
+<a href="{name}:{target}">{0={target}}</a>\r
+# anchor:id[text]\r
+[anchor-inlinemacro]\r
+<a id="{target}"></a>\r
+# [[id,text]]\r
+[anchor2-inlinemacro]\r
+<a id="{1}"></a>\r
+# [[[id]]]\r
+[anchor3-inlinemacro]\r
+<a id="{1}"></a>[{1}]\r
+# xref:id[text]\r
+[xref-inlinemacro]\r
+<a href="#{target}">{0=[{target}]}</a>\r
+# <<id,text>>\r
+[xref2-inlinemacro]\r
+<a href="#{1}">{2=[{1}]}</a>\r
+\r
+# Special word substitution.\r
+[emphasizedwords]\r
+<em>{words}</em>\r
+[monospacedwords]\r
+<tt>{words}</tt>\r
+[strongwords]\r
+<strong>{words}</strong>\r
+\r
+# Paragraph substitution.\r
+[paragraph]\r
+<div class="paragraph{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<p>\r
+|\r
+</p></div>\r
+\r
+[admonitionparagraph]\r
+template::[admonitionblock]\r
+\r
+# Delimited blocks.\r
+[listingblock]\r
+<div class="listingblock{role? {role}}"{id? id="{id}"}>\r
+<div class="title">{caption=}{title}</div>\r
+<div class="content">\r
+<pre><tt>\r
+|\r
+</tt></pre>\r
+</div></div>\r
+\r
+[literalblock]\r
+<div class="literalblock{role? {role}}"{id? id="{id}"}>\r
+<div class="title">{title}</div>\r
+<div class="content">\r
+<pre><tt>\r
+|\r
+</tt></pre>\r
+</div></div>\r
+\r
+[sidebarblock]\r
+<div class="sidebarblock{role? {role}}"{id? id="{id}"}>\r
+<div class="content">\r
+<div class="title">{title}</div>\r
+|\r
+</div></div>\r
+\r
+[openblock]\r
+<div class="openblock{role? {role}}"{id? id="{id}"}>\r
+<div class="title">{title}</div>\r
+<div class="content">\r
+|\r
+</div></div>\r
+\r
+[partintroblock]\r
+template::[openblock]\r
+\r
+[abstractblock]\r
+template::[quoteblock]\r
+\r
+[quoteblock]\r
+<div class="quoteblock{role? {role}}"{id? id="{id}"}>\r
+<div class="title">{title}</div>\r
+<div class="content">\r
+|\r
+</div>\r
+<div class="attribution">\r
+<em>{citetitle}</em>{attribution?<br />}\r
+&#8212; {attribution}\r
+</div></div>\r
+\r
+[verseblock]\r
+<div class="verseblock{role? {role}}"{id? id="{id}"}>\r
+<div class="title">{title}</div>\r
+<pre class="content">\r
+|\r
+</pre>\r
+<div class="attribution">\r
+<em>{citetitle}</em>{attribution?<br />}\r
+&#8212; {attribution}\r
+</div></div>\r
+\r
+[exampleblock]\r
+<div class="exampleblock{role? {role}}"{id? id="{id}"}>\r
+<div class="title">{caption={example-caption} {counter:example-number}. }{title}</div>\r
+<div class="content">\r
+|\r
+</div></div>\r
+\r
+[admonitionblock]\r
+<div class="admonitionblock{role? {role}}"{id? id="{id}"}>\r
+<table><tr>\r
+<td class="icon">\r
+{data-uri%}{icons#}<img src="{icon={iconsdir}/{name}.png}" alt="{caption}" />\r
+{data-uri#}{icons#}<img alt="{caption}" src="data:image/png;base64,\r
+{data-uri#}{icons#}{sys:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{icon={iconsdir}/{name}.png}")}"}" />\r
+{icons%}<div class="title">{caption}</div>\r
+</td>\r
+<td class="content">\r
+<div class="title">{title}</div>\r
+|\r
+</td>\r
+</tr></table>\r
+</div>\r
+\r
+# Tables.\r
+[tabletags-default]\r
+colspec=<col{autowidth-option! width="{colpcwidth}%"} />\r
+bodyrow=<tr>|</tr>\r
+headdata=<th {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }align="{halign}" valign="{valign}">|</th>\r
+bodydata=<td {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }align="{halign}" valign="{valign}">|</td>\r
+paragraph=<p class="table">|</p>\r
+\r
+[tabletags-header]\r
+paragraph=<p class="table header">|</p>\r
+\r
+[tabletags-emphasis]\r
+paragraph=<p class="table"><em>|</em></p>\r
+\r
+[tabletags-strong]\r
+paragraph=<p class="table"><strong>|</strong></p>\r
+\r
+[tabletags-monospaced]\r
+paragraph=<p class="table"><tt>|</tt></p>\r
+\r
+[tabletags-verse]\r
+bodydata=<td {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }align="{halign}" valign="{valign}"><div class="verse">|</div></td>\r
+paragraph=\r
+\r
+[tabletags-literal]\r
+bodydata=<td {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }align="{halign}" valign="{valign}"><div class="literal"><pre><tt>|</tt></pre></div></td>\r
+paragraph=\r
+\r
+[tabletags-asciidoc]\r
+bodydata=<td {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }align="{halign}" valign="{valign}"><div>|</div></td>\r
+paragraph=\r
+\r
+[table]\r
+<div class="tableblock{role? {role}}"{id? id="{id}"}>\r
+<table rules="{grid=all}"\r
+style="margin-left:{align@left:0}{align@center|right:auto}; margin-right:{align@left|center:auto}{align@right:0};"\r
+style="float:{float};"\r
+{autowidth-option%}width="{tablepcwidth}%"\r
+{autowidth-option#}{width#width="{tablepcwidth}%"}\r
+frame="{frame%border}"\r
+frame="{frame@topbot:hsides}{frame@all:border}{frame@none:void}{frame@sides:vsides}"\r
+cellspacing="0" cellpadding="4">\r
+<caption class="title">{caption={table-caption} {counter:table-number}. }{title}</caption>\r
+{colspecs}\r
+{headrows#}<thead>\r
+{headrows}\r
+{headrows#}</thead>\r
+{footrows#}<tfoot>\r
+{footrows}\r
+{footrows#}</tfoot>\r
+<tbody>\r
+{bodyrows}\r
+</tbody>\r
+</table>\r
+</div>\r
+\r
+#--------------------------------------------------------------------\r
+# Deprecated old table definitions.\r
+#\r
+\r
+[miscellaneous]\r
+# Screen width in pixels.\r
+pagewidth=800\r
+pageunits=\r
+\r
+[old_tabledef-default]\r
+template=old_table\r
+colspec=<col width="{colwidth}{pageunits}" />\r
+bodyrow=<tr>|</tr>\r
+headdata=<th align="{colalign}">|</th>\r
+footdata=<td align="{colalign}">|</td>\r
+bodydata=<td align="{colalign}">|</td>\r
+\r
+[old_table]\r
+<div class="tableblock"{id? id="{id}"}>\r
+<table rules="{grid=none}"\r
+frame="{frame%hsides}"\r
+frame="{frame@topbot:hsides}{frame@all:border}{frame@none:void}{frame@sides:vsides}"\r
+cellspacing="0" cellpadding="4">\r
+<caption class="title">{caption={table-caption}}{title}</caption>\r
+{colspecs}\r
+{headrows#}<thead>\r
+{headrows}\r
+{headrows#}</thead>\r
+{footrows#}<tfoot>\r
+{footrows}\r
+{footrows#}</tfoot>\r
+<tbody valign="top">\r
+{bodyrows}\r
+</tbody>\r
+</table>\r
+</div>\r
+\r
+# End of deprecated old table definitions.\r
+#--------------------------------------------------------------------\r
+\r
+[floatingtitle]\r
+<h{level@0:1}{level@1:2}{level@2:3}{level@3:4}{level@4:5}{id? id="{id}"} class="float">{title}</h{level@0:1}{level@1:2}{level@2:3}{level@3:4}{level@4:5}>\r
+\r
+[preamble]\r
+# Untitled elements between header and first section title.\r
+<div id="preamble">\r
+<div class="sectionbody">\r
+|\r
+</div>\r
+</div>\r
+\r
+# Document sections.\r
+[sect0]\r
+<h1{id? id="{id}"}>{title}</h1>\r
+|\r
+\r
+[sect1]\r
+<div class="sect1{style? {style}}{role? {role}}">\r
+<h2{id? id="{id}"}>{numbered?{sectnum} }{title}</h2>\r
+<div class="sectionbody">\r
+|\r
+</div>\r
+</div>\r
+\r
+[sect2]\r
+<div class="sect2{style? {style}}{role? {role}}">\r
+<h3{id? id="{id}"}>{numbered?{sectnum} }{title}</h3>\r
+|\r
+</div>\r
+\r
+[sect3]\r
+<div class="sect3{style? {style}}{role? {role}}">\r
+<h4{id? id="{id}"}>{numbered?{sectnum} }{title}</h4>\r
+|\r
+</div>\r
+\r
+[sect4]\r
+<div class="sect4{style? {style}}{role? {role}}">\r
+<h5{id? id="{id}"}>{title}</h5>\r
+|\r
+</div>\r
+\r
+[appendix]\r
+<div class="sect1{style? {style}}{role? {role}}">\r
+<h2{id? id="{id}"}>{numbered?{sectnum} }{appendix-caption} {counter:appendix-number:A}: {title}</h2>\r
+<div class="sectionbody">\r
+|\r
+</div>\r
+</div>\r
+\r
+[toc]\r
+<div id="toc">\r
+  <div id="toctitle">{toc-title}</div>\r
+  <noscript><p><b>JavaScript must be enabled in your browser to display the table of contents.</b></p></noscript>\r
+</div>\r
+\r
+[header]\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"\r
+    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="{lang=en}">\r
+<head>\r
+<link rel="icon" type="image/png" href="/favicon.png">\r
+<meta http-equiv="Content-Type" content="{quirks=application/xhtml+xml}{quirks?text/html}; charset={encoding}" />\r
+<meta name="generator" content="AsciiDoc {asciidoc-version}" />\r
+<meta name="description" content="{description}" />\r
+<meta name="keywords" content="{keywords}" />\r
+<title>i3: {title}</title>\r
+{title%}<title>i3: {doctitle=}</title>\r
+<link rel="stylesheet" href="{stylesdir=.}/style.css" type="text/css" />\r
+ifdef::linkcss[]\r
+<link rel="stylesheet" href="{stylesdir=.}/{theme={backend}}.css" type="text/css" />\r
+{doctype-manpage}<link rel="stylesheet" href="{stylesdir=.}/{theme={backend}}-manpage.css" type="text/css" />\r
+ifdef::quirks[]\r
+<link rel="stylesheet" href="{stylesdir=.}/{theme={backend}}-quirks.css" type="text/css" />\r
+endif::quirks[]\r
+<link rel="stylesheet" href="{stylesdir=.}/{stylesheet}" type="text/css" />\r
+ifdef::pygments[<link rel="stylesheet" href="{stylesdir=.}/pygments.css" type="text/css" />]\r
+endif::linkcss[]\r
+ifndef::linkcss[]\r
+<style type="text/css">\r
+include1::{stylesdir=./stylesheets}/{theme={backend}}.css[]\r
+ifdef::doctype-manpage[]\r
+include1::{stylesdir=./stylesheets}/{theme={backend}}-manpage.css[]\r
+endif::doctype-manpage[]\r
+ifdef::quirks[]\r
+include1::{stylesdir=./stylesheets}/{theme={backend}}-quirks.css[]\r
+endif::quirks[]\r
+include1::{stylesheet}[]\r
+ifdef::pygments[]\r
+include1::{stylesdir=./stylesheets}/pygments.css[]\r
+endif::pygments[]\r
+</style>\r
+endif::linkcss[]\r
+ifndef::disable-javascript[]\r
+ifdef::linkcss[]\r
+<script type="text/javascript">\r
+# Escape as CDATA to pass validators.\r
+/*<![CDATA[*/\r
+window.onload = function()\{asciidoc.footnotes();{toc? asciidoc.toc({toclevels});}\}\r
+/*]]>*/\r
+</script>\r
+<script type="text/javascript" src="{scriptsdir=.}/asciidoc-xhtml11.js"></script>\r
+endif::linkcss[]\r
+ifndef::linkcss[]\r
+<script type="text/javascript">\r
+# Escape as CDATA to pass validators.\r
+/*<![CDATA[*/\r
+window.onload = function()\{asciidoc.footnotes();{toc? asciidoc.toc({toclevels});}\}\r
+include1::{scriptsdir=./javascripts}/asciidoc-xhtml11.js[]\r
+/*]]>*/\r
+</script>\r
+endif::linkcss[]\r
+endif::disable-javascript[]\r
+ifdef::asciimath[]\r
+ifdef::linkcss[]\r
+<script type="text/javascript" src="{scriptsdir=.}/ASCIIMathML.js"></script>\r
+endif::linkcss[]\r
+ifndef::linkcss[]\r
+<script type="text/javascript">\r
+# Escape as CDATA to pass validators.\r
+/*<![CDATA[*/\r
+include1::{scriptsdir=./javascripts}/ASCIIMathML.js[]\r
+/*]]>*/\r
+</script>\r
+endif::linkcss[]\r
+endif::asciimath[]\r
+ifdef::latexmath[]\r
+ifdef::linkcss[]\r
+<script type="text/javascript" src="{scriptsdir=.}/LaTeXMathML.js"></script>\r
+endif::linkcss[]\r
+ifndef::linkcss[]\r
+<script type="text/javascript">\r
+# Escape as CDATA to pass validators.\r
+/*<![CDATA[*/\r
+include1::{scriptsdir=./javascripts}/LaTeXMathML.js[]\r
+/*]]>*/\r
+</script>\r
+endif::linkcss[]\r
+endif::latexmath[]\r
+{docinfo1,docinfo2#}{include:{docdir}/docinfo.html}\r
+{docinfo,docinfo2#}{include:{docdir}/{docname}-docinfo.html}\r
+</head>\r
+<body class="{doctype}"{max-width? style="max-width:{max-width}"}>\r
+\r
+        <div id="main">\r
+            <a href="/"><h1 id="title">i3 - improved tiling WM</h1></a>\r
+                       <ul id="nav">\r
+                               <li style=" background-color: #FFD000; font-size: 2em;padding: 0.25em;-webkit-border-radius: 0.25em;border: 4px dashed black;color:  #000000;">latest git docs</li>\r
+                       </ul>\r
+       <br style="clear: both">\r
+<div id="content">\r
+# Article, book header.\r
+ifndef::doctype-manpage[]\r
+<div id="header">\r
+ifndef::notitle[<h1>{doctitle}</h1>]\r
+ifdef::doctitle[]\r
+<span id="author">{author}</span><br />\r
+<span id="email"><tt>&lt;<a href="mailto:{email}">{email}</a>&gt;</tt></span><br />\r
+<span id="revnumber">version {revnumber}{revdate?,}</span>\r
+<span id="revdate">{revdate}</span>\r
+<br /><span id="revremark">{revremark}</span>\r
+endif::doctitle[]\r
+ifdef::toc[{template:toc}]\r
+</div>\r
+endif::doctype-manpage[]\r
+# Man page header.\r
+ifdef::doctype-manpage[]\r
+<div id="header">\r
+<h1>\r
+{doctitle} Manual Page\r
+</h1>\r
+ifdef::toc[{template:toc}]\r
+<h2>{manname-title}</h2>\r
+<div class="sectionbody">\r
+<p>{manname} -\r
+   {manpurpose}\r
+</p>\r
+</div>\r
+</div>\r
+endif::doctype-manpage[]\r
+\r
+[footer]\r
+</div>\r
+{disable-javascript%<div id="footnotes"><hr /></div>}\r
+<div id="footer" lang="de">\r
+© 2009-2011 Michael Stapelberg, <a href="/impress.html">Impressum</a>\r
+</div>\r
+</body>\r
+</html>\r
+\r
+ifdef::doctype-manpage[]\r
+[synopsis]\r
+template::[sect1]\r
+endif::doctype-manpage[]\r
+\r
+ifdef::quirks[]\r
+include::{backend}-quirks.conf[]\r
+endif::quirks[]\r
diff --git a/docs/i3-sync-working.dia b/docs/i3-sync-working.dia
new file mode 100644 (file)
index 0000000..9f1c3bc
Binary files /dev/null and b/docs/i3-sync-working.dia differ
diff --git a/docs/i3-sync-working.png b/docs/i3-sync-working.png
new file mode 100644 (file)
index 0000000..dce44ac
Binary files /dev/null and b/docs/i3-sync-working.png differ
diff --git a/docs/i3-sync.dia b/docs/i3-sync.dia
new file mode 100644 (file)
index 0000000..0945ae2
Binary files /dev/null and b/docs/i3-sync.dia differ
diff --git a/docs/i3-sync.png b/docs/i3-sync.png
new file mode 100644 (file)
index 0000000..b64cce2
Binary files /dev/null and b/docs/i3-sync.png differ
index 7e71326022606aa2b4d1abf93d4d77d3c448fe1a..fc46590e0777d8973a5771e061c861631604a1a4 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -1,7 +1,7 @@
 IPC interface (interprocess communication)
 ==========================================
 Michael Stapelberg <michael+i3@stapelberg.de>
-March 2010
+October 2011
 
 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
@@ -12,7 +12,7 @@ The method of choice for IPC in our case is a unix socket because it has very
 little overhead on both sides and is usually available without headaches in
 most languages. In the default configuration file, the ipc-socket gets created
 in +/tmp/i3-%u/ipc-socket.%p+ where +%u+ is your UNIX username and +%p+ is the
-PID of i3.
+PID of i3. You can get the socketpath from i3 by 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.
@@ -24,7 +24,8 @@ snippet illustrates this in Perl:
 
 -------------------------------------------------------------
 use IO::Socket::UNIX;
-my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
+chomp(my $path = qx(i3 --get-socketpath));
+my $sock = IO::Socket::UNIX->new(Peer => $path);
 -------------------------------------------------------------
 
 == Sending messages to i3
@@ -59,6 +60,14 @@ GET_TREE (4)::
        Gets the layout tree. i3 uses a tree as data structure which includes
        every container. The reply will be the JSON-encoded tree (see the reply
        section).
+GET_MARKS (5)::
+       Gets a list of marks (identifiers for containers to easily jump to them
+       later). The reply will be a JSON-encoded list of window marks (see
+       reply section).
+GET_BAR_CONFIG (6)::
+       Gets the configuration (as JSON map) of the workspace bar with the
+       given ID. If no ID is provided, an array with all configured bar IDs is
+       returned instead.
 
 So, a typical message could look like this:
 --------------------------------------------------
@@ -110,6 +119,10 @@ GET_OUTPUTS (3)::
        Reply to the GET_OUTPUTS message.
 GET_TREE (4)::
        Reply to the GET_TREE message.
+GET_MARKS (5)::
+       Reply to the GET_MARKS message.
+GET_BAR_CONFIG (6)::
+       Reply to the GET_BAR_CONFIG message.
 
 === COMMAND reply
 
@@ -418,6 +431,98 @@ JSON dump:
 }
 ------------------------
 
+=== GET_MARKS reply
+
+The reply consists of a single array of strings for each container that has a
+mark. The order of that array is undefined. If more than one container has the
+same mark, it will be represented multiple times in the reply (the array
+contents are not unique).
+
+If no window has a mark the response will be the empty array [].
+
+=== GET_BAR_CONFIG reply
+
+This can be used by third-party workspace bars (especially i3bar, but others
+are free to implement compatible alternatives) to get the +bar+ block
+configuration from i3.
+
+Depending on the input, the reply is either:
+
+empty input::
+       An array of configured bar IDs
+Bar ID::
+       A JSON map containing the configuration for the specified bar.
+
+Each bar configuration has the following properties:
+
+id (string)::
+       The ID for this bar. Included in case you request multiple
+       configurations and want to differentiate the different replies.
+mode (string)::
+       Either +dock+ (the bar sets the dock window type) or +hide+ (the bar
+       does not show unless a specific key is pressed).
+position (string)::
+       Either +bottom+ or +top+ at the moment.
+status_command (string)::
+       Command which will be run to generate a statusline. Each line on stdout
+       of this command will be displayed in the bar. At the moment, no
+       formatting is supported.
+font (string)::
+       The font to use for text on the bar.
+workspace_buttons (boolean)::
+       Display workspace buttons or not? Defaults to true.
+verbose (boolean)::
+       Should the bar enable verbose output for debugging? Defaults to false.
+colors (map)::
+       Contains key/value pairs of colors. Each value is a color code in hex,
+       formatted #rrggbb (like in HTML).
+
+The following colors can be configured at the moment:
+
+background::
+       Background color of the bar.
+statusline::
+       Text color to be used for the statusline.
+focused_workspace_text/focused_workspace_bg::
+       Text color/background color for a workspace button when the workspace
+       has focus.
+active_workspace_text/active_workspace_bg::
+       Text color/background color for a workspace button when the workspace
+       is active (visible) on some output, but the focus is on another one.
+       You can only tell this apart from the focused workspace when you are
+       using multiple monitors.
+inactive_workspace_text/inactive_workspace_bg::
+       Text color/background color for a workspace button when the workspace
+       does not have focus and is not active (visible) on any output. This
+       will be the case for most workspaces.
+urgent_workspace_text/urgent_workspace_bar::
+       Text color/background color for workspaces which contain at least one
+       window with the urgency hint set.
+
+
+*Example of configured bars:*
+--------------
+["bar-bxuqzf"]
+--------------
+
+*Example of bar configuration:*
+--------------
+{
+ "id": "bar-bxuqzf",
+ "mode": "dock",
+ "position": "bottom",
+ "status_command": "i3status",
+ "font": "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1",
+ "workspace_buttons": true,
+ "verbose": false,
+ "colors": {
+   "background": "#c0c0c0",
+   "statusline": "#00ff00",
+   "focused_workspace_text": "#ffffff",
+   "focused_workspace_bg": "#000000"
+ }
+}
+--------------
 
 == Events
 
index ec0256c053c70c5aeee874a710dda7622cdf1b41..a1fd6dc039273b454c2ddc9ddcd322c4726d754c 100644 (file)
@@ -1,7 +1,7 @@
 The multi-monitor situation
 ===========================
 Michael Stapelberg <michael+i3@stapelberg.de>
-March 2010
+September 2011
 
 …or: oh no, I have an nVidia graphics card!
 
@@ -16,6 +16,8 @@ i3, like so:
 exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1
 ----------------------------------------------
 
+…or use +force_xinerama yes+ in your configuration file.
+
 == The explanation
 
 Starting with version 3.ε, i3 uses the RandR (Rotate and Resize) API instead
@@ -50,9 +52,13 @@ these are two screens).
 
 For this very reason, we decided to implement the following workaround: As
 long as the nVidia driver does not support RandR, an option called
-+--force-xinerama+ is available in i3. This option gets the list of screens
-*once* when starting, and never updates it. As the nVidia driver cannot do
-dynamic configuration anyways, this is not a big deal.
++--force-xinerama+ is available in i3 (alternatively, you can use the
++force_xinerama+ configuration file directive). This option gets the list of
+screens *once* when starting, and never updates it. As the nVidia driver cannot
+do dynamic configuration anyways, this is not a big deal.
+
+Also note that your output names are not descriptive (like +HDMI1+) when using
+Xinerama, instead they are counted up, starting at 0: +xinerama-0+, +xinerama-1+, …
 
 == See also
 
diff --git a/docs/testsuite b/docs/testsuite
new file mode 100644 (file)
index 0000000..720ff39
--- /dev/null
@@ -0,0 +1,540 @@
+i3 testsuite
+============
+Michael Stapelberg <michael+i3@stapelberg.de>
+September 2011
+
+This document explains how the i3 testsuite works, how to use it and extend it.
+It is targeted at developers who not necessarily have been doing testing before
+or have not been testing in Perl before. In general, the testsuite is not of
+interest for end users.
+
+
+== Introduction
+
+The i3 testsuite is a collection of files which contain testcases for various
+i3 features. Some of them test if a certain workflow works correctly (moving
+windows, focus behaviour, …). Others are regression tests and contain code
+which previously made i3 crash or lead to unexpected behaviour. They then check
+if i3 still runs (meaning it did not crash) and if it handled everything
+correctly.
+
+The goal of having these tests is to automatically find problems and to
+automatically get a feel for whether a change in the source code breaks any
+existing feature. After every modification of the i3 sourcecode, the developer
+should run the full testsuite. If one of the tests fails, the corresponding
+problem should be fixed (or, in some cases, the testcase has to be modified).
+For every bugreport, a testcase should be written to test the correct
+behaviour. Initially, it will fail, but after fixing the bug, it will pass.
+This ensures (or increases the chance) that bugs which have been fixed once
+will never be found again.
+
+Also, when implementing a new feature, a testcase might be a good way to be
+able to easily test if the feature is working correctly. Many developers will
+test manually if everything works. Having a testcase not only helps you with
+that, but it will also be useful for every future change.
+
+== Implementation
+
+For several reasons, the i3 testsuite has been implemented in Perl:
+
+1. Perl has a long tradition of testing. Every popular/bigger Perl module which
+   you can find on CPAN will not only come with documentation, but also with
+   tests. Therefore, the available infrastructure for tests is comprehensive.
+   See for example the excellent http://search.cpan.org/perldoc?Test::More
+   and the referenced http://search.cpan.org/perldoc?Test::Tutorial.
+
+2. Perl is widely available and has a well-working package infrastructure.
+3. The author is familiar with Perl :).
+
+Please do not start programming language flamewars at this point.
+
+=== Mechanisms
+
+==== Script: complete-run
+
+The testcases are run by a script called +complete-run.pl+. It runs all
+testcases by default, but you can be more specific and let it only run one or
+more testcases. Also, it takes care of starting up a separate instance of i3
+with an appropriate configuration file and creates a folder for each run
+containing the appropriate i3 logfile for each testcase. The latest folder can
+always be found under the symlink +latest/+. Unless told differently, it will
+run the tests on a separate X server instance (using the Xdummy script).
+
+.Example invocation of complete-run.pl+
+---------------------------------------
+$ cd ~/i3/testcases
+
+$ ./complete-run.pl
+# output omitted because it is very long
+All tests successful.
+Files=78, Tests=734, 27 wallclock secs ( 0.38 usr  0.48 sys + 17.65 cusr  3.21 csys = 21.72 CPU)
+Result: PASS
+
+$ ./complete-run.pl t/04-floating.t
+[:3] i3 startup: took 0.07s, status = 1
+[:3] Running t/04-floating.t with logfile testsuite-2011-09-24-16-06-04-4.0.2-226-g1eb011a/i3-log-for-04-floating.t
+[:3] t/04-floating.t finished
+[:3] killing i3
+output for t/04-floating.t:
+ok 1 - use X11::XCB::Window;
+ok 2 - The object isa X11::XCB::Window
+ok 3 - Window is mapped
+ok 4 - i3 raised the width to 75
+ok 5 - i3 raised the height to 50
+ok 6 - i3 did not map it to (0x0)
+ok 7 - The object isa X11::XCB::Window
+ok 8 - i3 let the width at 80
+ok 9 - i3 let the height at 90
+ok 10 - i3 mapped it to x=1
+ok 11 - i3 mapped it to y=18
+ok 12 - The object isa X11::XCB::Window
+ok 13 - i3 let the width at 80
+ok 14 - i3 let the height at 90
+1..14
+
+All tests successful.
+Files=1, Tests=14,  0 wallclock secs ( 0.01 usr  0.00 sys +  0.19 cusr  0.03 csys =  0.23 CPU)
+Result: PASS
+
+$ less latest/i3-log-for-04-floating.t
+----------------------------------------
+
+==== IPC interface
+
+The testsuite makes extensive use of the IPC (Inter-Process Communication)
+interface which i3 provides. It is used for the startup process of i3, for
+terminating it cleanly and (most importantly) for modifying and getting the
+current state (layout tree).
+
+See [http://i3wm.org/docs/ipc.html] for documentation on the IPC interface.
+
+==== X11::XCB
+
+In order to open new windows, change attributes, get events, etc., the
+testsuite uses X11::XCB, a new (and quite specific to i3 at the moment) Perl
+module which uses the XCB protocol description to generate Perl bindings to
+X11. They work in a very similar way to libxcb (which i3 uses) and provide
+relatively high-level interfaces (objects such as +X11::XCB::Window+) aswell as
+access to the low-level interface, which is very useful when testing a window
+manager.
+
+=== Filesystem structure
+
+In the git root of i3, the testcases live in the folder +testcases+. This
+folder contains the +complete-run.pl+ and +Xdummy+ scripts and a base
+configuration file which will be used for the tests. The different testcases
+(their file extension is .t, not .pl) themselves can be found in the
+conventionally named subfolder +t+:
+
+.Filesystem structure
+--------------------------------------------
+├── testcases
+│   ├── complete-run.pl
+│   ├── i3-test.config
+│   ├── lib
+│   │   ├── i3test.pm
+│   │   ├── SocketActivation.pm
+│   │   └── StartXDummy.pm
+│   ├── t
+│   │   ├── 00-load.t
+│   │   ├── 01-tile.t
+│   │   ├── 02-fullscreen.t
+│   │   ├── ...
+│   │   ├── omitted for brevity
+│   │   ├── ...
+│   │   └── 74-regress-focus-toggle.t
+│   └── Xdummy
+--------------------------------------------
+
+== Anatomy of a testcase
+
+Learning by example is definitely a good strategy when you are wondering how to
+write a testcase. Let's take +t/11-goto.t+ as an easy example and go through it
+step by step:
+
+.t/11-goto.t: Boilerplate
+----------------------
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use File::Temp;
+
+my $x = X11::XCB::Connection->new;
+-----------------------
+
+This is what we call boilerplate. It exists at the top of every test file (to
+some extent). The first line is the shebang, which specifies that this file is
+a Perl script. The second line contains VIM specific settings on how to
+edit/format this file (use spaces instead of tabs, indent using 4 spaces).
+Afterwards, the +i3test+ module is used. This module contains i3 testsuite
+specific functions which you are strongly encouraged to use. They make writing
+testcases a lot easier and will make it easier for other people to read your
+tests.
+
+The next line uses the +File::Temp+ module. This is specific to this testcase,
+because it needs to generate a temporary name during the test. Many testcases
+use only the +i3test+ module.
+
+The last line opens a connection to X11. You might or might not need this in
+your testcase, depending on whether you are going to open windows (etc.) or
+only use i3 commands.
+
+.t/11-goto.t: Setup
+----------------------
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+----------------------
+
+The first line calls i3test's +fresh_workspace+ function which looks for a
+currently unused workspace, switches to it, and returns its name. The variable
++$tmp+ will end up having a value such as +"/tmp/87kBVcHbA9"+. Note that this
+is not (necessarily) a valid path, it's just a random workspace name.
+
+So, now that we are on a new workspace, we ensure that the workspace uses
+horizontal orientation by issuing the +split h+ command (see the i3 User's
+Guide for a list of commands). This is not strictly necessary, but good style.
+In general, the +cmd+ function executes the specified i3 command by using the
+IPC interface and returns once i3 acknowledged the command.
+
+.t/11-goto.t: Setup
+----------------------
+#####################################################################
+# Create two windows and make sure focus switching works
+#####################################################################
+
+my $top = open_window($x);
+my $mid = open_window($x);
+my $bottom = open_window($x);
+----------------------
+
+In every major section of a testcase, you should put a comment like the one
+above. This makes it immediately clear how the file is structured.
+
+The +open_window+ function opens a standard window, which will then be put into
+tiling mode by i3. If you want a floating window, use the
++open_floating_window+ function. These functions accept the same parameters as
++X11::XCB::Window->new+, see the i3test documentation at TODO.
+
+.t/11-goto.t: Helper function
+----------------------
+#
+# Returns the input focus after sending the given command to i3 via IPC
+# and syncing with i3
+#
+sub focus_after {
+    my $msg = shift;
+
+    cmd $msg;
+    sync_with_i3 $x;
+    return $x->input_focus;
+}
+----------------------
+
+This section defines a helper function which will be used over and over in this
+testcase. If you have code which gets executed more than once or twice
+(depending on the length of your test, use your best judgement), please put it
+in a function. Tests should be short, concise and clear.
+
+The +focus_after+ function executes a command and returns the X11 focus after
+the command was executed. The +sync_with_i3+ command makes sure that i3 could
+push its state to X11. See <<i3_sync>> to learn how this works exactly.
+
+.t/11-goto.t: Test assumptions
+----------------------
+$focus = $x->input_focus;
+is($focus, $bottom->id, "Latest window focused");
+
+$focus = focus_after('focus left');
+is($focus, $mid->id, "Middle window focused");
+----------------------
+
+Now, we run the first two real tests. They use +Test::More+'s +is+ function,
+which compares two values and prints the differences if they are not the same.
+After the arguments, we supply a short comment to indicate what we are testing
+here. This makes it vastly more easy for the developer to spot which testcase
+is the problem in case one fails.
+
+The first test checks that the most recently opened window is focused.
+Afterwards, the command +focus left+ is issued and it is verified that the
+middle window now has focus.
+
+Note that this is not a comprehensive test of the +focus+ command -- we would
+have to test wrapping, focus when using a more complex layout, focusing the
+parent/child containers, etc. But that is not the point of this testcase.
+Instead, we just want to know if +$x->input_focus+ corresponds with what we are
+expecting. If not, something is completely wrong with the test environment and
+this trivial test will fail.
+
+.t/11-goto.t: Test that the feature does not work (yet)
+----------------------
+#####################################################################
+# Now goto a mark which does not exist
+#####################################################################
+
+my $random_mark = mktemp('mark.XXXXXX');
+
+$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "focus unchanged");
+----------------------
+
+Syntax hint: The qq keyword is the interpolating quote operator. It lets you
+chose a quote character (in this case the +|+ character, a pipe). This makes
+having double quotes in our string easy.
+
+In this new major section, a random mark (mark is an identifier for a window,
+see "VIM-like marks" in the i3 User’s Guide) will be generated. Afterwards, we
+test that trying to focus that mark will not do anything. This is important: Do
+not only test that using a feature has the expected outcome, but also test that
+using it without properly initializing it does no harm. This command could for
+example have changed focus anyways (a bug) or crash i3 (obviously a bug).
+
+.t/11-goto.t: Test that the feature does work
+----------------------
+cmd "mark $random_mark";
+
+$focus = focus_after('focus left');
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "goto worked");
+----------------------
+
+Remember: Focus was on the middle window (we verified that earlier in "Test
+assumptions"). We now mark the middle window with our randomly generated mark.
+Afterwards, we switch focus away from the middle window to be able to tell if
+focusing it via its mark will work. If the test works, the goto command seems
+to be working.
+
+.t/11-goto.t: Test corner case
+----------------------
+# check that we can specify multiple criteria
+
+$focus = focus_after('focus left');
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after(qq|[con_mark="$random_mark" con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "goto worked");
+----------------------
+
+Now we test the same feature, but specifying the mark twice in the command.
+This should have no effect, but let’s be sure: test it and see if things go
+wrong.
+
+.t/11-goto.t: Test second code path
+----------------------
+#####################################################################
+# Check whether the focus command will switch to a different
+# workspace if necessary
+#####################################################################
+
+my $tmp2 = fresh_workspace;
+
+is(focused_ws(), $tmp2, 'tmp2 now focused');
+
+cmd qq|[con_mark="$random_mark"] focus|;
+
+is(focused_ws(), $tmp, 'tmp now focused');
+----------------------
+
+This part of the test checks that focusing windows by mark works across
+workspaces. It uses i3test's +focused_ws+ function to get the current
+workspace.
+
+.t/11-goto.t: Test second code path
+----------------------
+done_testing;
+----------------------
+
+The end of every testcase has to contain the +done_testing+ line. This tells
++complete-run.pl+ that the test was finished successfully. If it does not
+occur, the test might have crashed during execution -- some of the reasons why
+that could happen are bugs in the used modules, bugs in the testcase itself or
+an i3 crash resulting in the testcase being unable to communicate with i3 via
+IPC anymore.
+
+[[i3_sync]]
+== Appendix A: The i3 sync protocol
+
+Consider the following situation: You open two windows in your testcase, then
+you use +focus left+ and want to verify that the X11 focus has been updated
+properly. Sounds simple, right? Let’s assume you use this straight-forward
+implementation:
+
+.Racey focus testcase
+-----------
+my $left = open_window($x);
+my $right = open_window($x);
+cmd 'focus left';
+is($x->input_focus, $left->id, 'left window focused');
+----------
+
+However, the test fails. Sometimes. Apparantly, there is a race condition in
+your test. If you think about it, this is because you are using two different
+pieces of software: You tell i3 to update focus, i3 confirms that, and then you
+ask X11 to give you the current focus. There is a certain time i3 needs to
+update the X11 state. If the testcase gets CPU time before X11 processed i3's
+requests, the test will fail.
+
+image::i3-sync.png["Diagram of the race condition", title="Diagram of the race condition"]
+
+One way to "solve" this would be to add +sleep 0.5;+ after the +cmd+ call.
+After 0.5 seconds it should be safe to assume that focus has been updated,
+right?
+
+In practice, this usually works. However, it has several problems:
+
+1. This is obviously not a clean solution, but a workaround. Ugly.
+2. On very slow machines, this might not work. Unlikely, but in different
+   situations (a delay to wait for i3 to startup) the necessary time is much
+   harder to guess, even for fast machines.
+3. This *wastes a lot of time*. Usually, your computer is much faster than 0.5s
+   to update the status. However, sometimes, it might take 0.4s, so we can’t
+   make it +sleep 0.1+.
+
+To illustrate how grave the problem with wasting time actually is: Before
+removing all sleeps from the testsuite, a typical run using 4 separate X
+servers took around 50 seconds on my machine. After removing all the sleeps,
+we achieved times of about 25 seconds. This is very significant and influences
+the way you think about tests -- the faster they are, the more likely you are
+to check whether everything still works quite often (which you should).
+
+What I am trying to say is: Delays adds up quickly and make the test suite
+less robust.
+
+The real solution for this problem is a mechanism which I call "the i3 sync
+protocol". The idea is to send a request (which does not modify state) via X11
+to i3 which will then be answered. Due to the request's position in the event
+queue (*after* all previous events), you can be sure that by the time you
+receive the reply, all other events have been dealt with by i3 (and, more
+importantly, X11).
+
+image::i3-sync-working.png["Diagram of the i3 sync solution", title="Diagram of the i3 sync solution"]
+
+=== Implementation details
+
+The client which wants to sync with i3 initiates the protocol by sending a
+ClientMessage to the X11 root window:
+
+.Send ClientMessage
+-------------------
+# Generate a ClientMessage, see xcb_client_message_t
+my $msg = pack "CCSLLLLLLL",
+    CLIENT_MESSAGE, # response_type
+    32,     # format
+    0,      # sequence
+    $root,  # destination window
+    $x->atom(name => 'I3_SYNC')->id,
+
+    $_sync_window->id,    # data[0]: our own window id
+    $myrnd, # data[1]: a random value to identify the request
+    0,
+    0,
+    0;
+
+# Send it to the root window -- since i3 uses the SubstructureRedirect
+# event mask, it will get the ClientMessage.
+$x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+-------------------
+
+i3 will then reply with the same ClientMessage, sent to the window specified in
++data[0]+. In the reply, +data[0]+ and +data[1]+ are exactly the same as in the
+request. You should use a random value in +data[1]+ and check that you received
+the same one when getting the reply.
+
+== Appendix B: Socket activation
+
+Socket activation is a mechanism which was made popular by systemd, an init
+replacement. It basically describes creating a listening socket before starting
+a program.  systemd will invoke the program only when an actual connection to
+the socket is made, hence the term socket activation.
+
+The interesting part of this (in the i3 context) is that you can very precisely
+detect when the program is ready (finished its initialization).
+
+=== Preparing the listening socket
+
++complete-run.pl+ will create a listening UNIX socket which it will then pass
+to i3. This socket will be used by i3 as an additional IPC socket, just like
+the one it will create on its own. Passing the socket happens implicitly
+because children will inherit the parent’s sockets when fork()ing and sockets
+will continue to exist after an exec() call (unless CLOEXEC is set of course).
+
+The only explicit things +complete-run.pl+ has to do is setting the +LISTEN_FDS+
+environment variable to the number of sockets which exist (1 in our case) and
+setting the +LISTEN_PID+ environment variable to the current process ID. Both
+variables are necessary so that the program (i3) knows how many sockets it
+should use and if the environment variable is actually intended for it. i3 will
+then start looking for sockets at file descriptor 3 (since 0, 1 and 2 are used
+for stdin, stdout and stderr, respectively).
+
+The actual Perl code which sets up the socket, fork()s, makes sure the socket
+has file descriptor 3 and sets up the environment variables follows (shortened
+a bit):
+
+
+.Setup socket and environment
+-----------------------------
+my $socket = IO::Socket::UNIX->new(
+    Listen => 1,
+    Local => $args{unix_socket_path},
+);
+
+my $pid = fork;
+if ($pid == 0) {
+    $ENV{LISTEN_PID} = $$;
+    $ENV{LISTEN_FDS} = 1;
+
+    # Only pass file descriptors 0 (stdin), 1 (stdout),
+    # 2 (stderr) and 3 (socket) to the child.
+    $^F = 3;
+
+    # If the socket does not use file descriptor 3 by chance
+    # already, we close fd 3 and dup2() the socket to 3.
+    if (fileno($socket) != 3) {
+        POSIX::close(3);
+        POSIX::dup2(fileno($socket), 3);
+    }
+
+    exec "/usr/bin/i3";
+}
+-----------------------------
+
+=== Waiting for a reply
+
+In the parent process, we want to know when i3 is ready to answer our IPC
+requests and handle our windows. Therefore, after forking, we immediately close
+the listening socket (i3 will handle this side of the socket) and connect to it
+(remember, we are talking about a named UNIX socket) as a client. This connect
+call will immediately succeed because the kernel buffers it. Then, we send a
+request (of type GET_TREE, but that is not really relevant). Writing data to
+the socket will also succeed immediately because, again, the kernel buffers it
+(only up to a certain amount of data of course).
+
+Afterwards, we just blockingly wait until we get an answer. In the child
+process, i3 will setup the listening socket in its event loop. Immediately
+after actually starting the event loop, it will notice a new client connecting
+(the parent process) and handle its request. Since all initialization has been
+completed successfully by the time the event loop is entered, we can now assume
+that i3 is ready.
+
+=== Timing and conclusion
+
+A beautiful feature of this mechanism is that it does not depend on timing. It
+does not matter when the child process gets CPU time or when the parent process
+gets CPU time. On heavily loaded machines (or machines with multiple CPUs,
+cores or unreliable schedulers), this makes waiting for i3 much more robust.
+
+Before using socket activation, we typically used a +sleep(1)+ and hoped that
+i3 was initialized by that time. Of course, this breaks on some (slow)
+computers and wastes a lot of time on faster computers. By using socket
+activation, we decreased the total amount of time necessary to run all tests
+(72 files at the time of writing) from > 100 seconds to 16 seconds. This makes
+it significantly more attractive to run the test suite more often (or at all)
+during development.
+
+An alternative approach to using socket activation is polling for the existance
+of the IPC socket and connecting to it. While this might be slightly easier to
+implement, it wastes CPU time and is considerably uglier than this solution
+:). After all, +lib/SocketActivation.pm+ contains only 54 SLOC.
index a8317d0421f8e24ee1224b2706e8e586570ba565..3d78d16f83020e1f838df9cd119dd7d24eb7bdd1 100644 (file)
@@ -1,7 +1,7 @@
 i3 User’s Guide
 ===============
 Michael Stapelberg <michael+i3@stapelberg.de>
-August 2011
+October 2011
 
 This document contains all the information you need to configure and use the i3
 window manager. If it does not, please contact us on IRC (preferred) or post your
@@ -357,7 +357,8 @@ it to the position you want.
 
 When holding the floating modifier, you can resize a floating window by
 pressing the right mouse button on it and moving around while holding it. If
-you hold the shift button as well, the resize will be proportional.
+you hold the shift button as well, the resize will be proportional (the aspect
+ratio will be preserved).
 
 *Syntax*:
 --------------------------------
@@ -431,7 +432,7 @@ change their border style, for example.
 
 *Syntax*:
 -----------------------------
-for_window [criteria] command
+for_window <criteria> command
 -----------------------------
 
 *Examples*:
@@ -478,37 +479,59 @@ configuration file and run it before starting i3 (for example in your
 
 [[assign_workspace]]
 
-Specific windows can be matched by window class and/or window title. It is
-recommended that you match on window classes instead of window titles whenever
-possible because some applications first create their window, and then worry
-about setting the correct title. Firefox with Vimperator comes to mind. The
-window starts up being named Firefox, and only when Vimperator is loaded does
-the title change. As i3 will get the title as soon as the application maps the
+To automatically make a specific window show up on a specific workspace, you
+can use an *assignment*. You can match windows by using any criteria,
+see <<command_criteria>>. It is recommended that you match on window classes
+(and instances, when appropriate) instead of window titles whenever possible
+because some applications first create their window, and then worry about
+setting the correct title. Firefox with Vimperator comes to mind. The window
+starts up being named Firefox, and only when Vimperator is loaded does the
+title change. As i3 will get the title as soon as the application maps the
 window (mapping means actually displaying it on the screen), you’d need to have
 to match on 'Firefox' in this case.
 
-You can prefix or suffix workspaces with a `~` to specify that matching clients
-should be put into floating mode. If you specify only a `~`, the client will
-not be put onto any workspace, but will be set floating on the current one.
-
 *Syntax*:
 ------------------------------------------------------------
-assign ["]window class[/window title]["] [→] [workspace]
+assign <criteria> [→] workspace
 ------------------------------------------------------------
 
 *Examples*:
 ----------------------
-assign urxvt 2
-assign urxvt → 2
-assign urxvt → work
-assign "urxvt" → 2
-assign "urxvt/VIM" → 3
-assign "gecko" → 4
+# Assign URxvt terminals to workspace 2
+assign [class="URxvt"] 2
+
+# Same thing, but more precise (exact match instead of substring)
+assign [class="^URxvt$"] 2
+
+# Same thing, but with a beautiful arrow :)
+assign [class="^URxvt$"] → 2
+
+# Assignment to a named workspace
+assign [class="^URxvt$"] → work
+
+# Start urxvt -name irssi
+assign [class="^URxvt$" instance="^irssi$"] → 3
 ----------------------
 
 Note that the arrow is not required, it just looks good :-). If you decide to
 use it, it has to be a UTF-8 encoded arrow, not `->` or something like that.
 
+To get the class and instance, you can use +xprop+. After clicking on the
+window, you will see the following output:
+
+*xwininfo*:
+-----------------------------------
+WM_CLASS(STRING) = "irssi", "URxvt"
+-----------------------------------
+
+The first part of the WM_CLASS is the instance ("irssi" in this example), the
+second part is the class ("URxvt" in this example).
+
+Should you have any problems with assignments, make sure to check the i3
+logfile first (see http://i3wm.org/docs/debugging.html). It includes more
+details about the matching process and the window’s actual class, instance and
+title when starting up.
+
 === Automatically starting applications on i3 startup
 
 By using the +exec+ keyword outside a keybinding, you can configure
@@ -519,16 +542,21 @@ keyword. These commands will be run in order.
 
 *Syntax*:
 -------------------
-exec command
-exec_always command
+exec [--no-startup-id] command
+exec_always [--no-startup-id] command
 -------------------
 
 *Examples*:
 --------------------------------
-exec i3status | i3bar -d
+exec chromium
 exec_always ~/my_script.sh
+
+# Execute the terminal emulator urxvt, which is not yet startup-notification aware.
+exec --no-startup-id urxvt
 --------------------------------
 
+The flag --no-startup-id is explained in <<exec>>.
+
 [[workspace_screen]]
 
 === Automatically putting workspaces on specific screens
@@ -541,17 +569,20 @@ the second screen and so on).
 
 *Syntax*:
 ----------------------------------
-workspace <number> output <output>
+workspace <workspace> output <output>
 ----------------------------------
 
 The 'output' is the name of the RandR output you attach your screen to. On a
 laptop, you might have VGA1 and LVDS1 as output names. You can see the
 available outputs by running +xrandr --current+.
 
+If you use named workspaces, they must be quoted:
+
 *Examples*:
 ---------------------------
 workspace 1 output LVDS1
 workspace 5 output VGA1
+workspace "2: vim" output VGA1
 ---------------------------
 
 === Changing colors
@@ -690,6 +721,270 @@ force_focus_wrapping <yes|no>
 force_focus_wrapping yes
 ------------------------
 
+=== Forcing Xinerama
+
+As explained in-depth in <http://i3wm.org/docs/multi-monitor.html>, some X11
+video drivers (especially the nVidia binary driver) only provide support for
+Xinerama instead of RandR. In such a situation, i3 must be told to use the
+inferior Xinerama API explicitly and therefore don’t provide support for
+reconfiguring your screens on the fly (they are read only once on startup and
+that’s it).
+
+For people who do cannot modify their +~/.xsession+ to add the
++--force-xinerama+ commandline parameter, a configuration option is provided:
+
+*Syntax*:
+-----------------------
+force_xinerama <yes|no>
+-----------------------
+
+*Example*:
+------------------
+force_xinerama yes
+------------------
+
+Also note that your output names are not descriptive (like +HDMI1+) when using
+Xinerama, instead they are counted up, starting at 0: +xinerama-0+, +xinerama-1+, …
+
+=== Automatic back-and-forth when switching to the current workspace
+
+This configuration directive enables automatic +workspace back_and_forth+ (see
+<<back_and_forth>>) when switching to the workspace that is currently focused.
+
+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".
+
+*Syntax*:
+--------------------------------------
+workspace_auto_back_and_forth <yes|no>
+--------------------------------------
+
+*Example*:
+---------------------------------
+workspace_auto_back_and_forth yes
+---------------------------------
+
+== Configuring i3bar
+
+The bar at the bottom of your monitor is drawn by a separate process called
+i3bar. Having this part of "the i3 user interface" in a separate process has
+several advantages:
+
+1. It is a modular approach. If you don’t need a workspace bar at all, or if
+   you prefer a different one (dzen2, xmobar, maybe even gnome-panel?), you can
+   just remove the i3bar configuration and start your favorite bar instead.
+2. It follows the UNIX philosophy of "Make each program do one thing well".
+   While i3 manages your windows well, i3bar is good at displaying a bar on
+   each monitor (unless you configure it otherwise).
+3. It leads to two separate, clean codebases. If you want to understand i3, you
+   don’t need to bother with the details of i3bar and vice versa.
+
+That said, i3bar is configured in the same configuration file as i3. This is
+because it is tightly coupled with i3 (in contrary to i3lock or i3status which
+are useful for people using other window managers). Therefore, it makes no
+sense to use a different configuration place when we already have a good
+configuration infrastructure in place.
+
+Configuring your workspace bar starts with opening a +bar+ block. You can have
+multiple bar blocks to use different settings for different outputs (monitors):
+
+*Example*:
+---------------------------
+bar {
+    status_command i3status
+}
+---------------------------
+
+=== Statusline command
+
+i3bar can run a program and display every line of its +stdout+ output on the
+right hand side of the bar. This is useful to display system information like
+your current IP address, battery status or date/time.
+
+The specified command will be passed to +sh -c+, so you can use globbing and
+have to have correct quoting etc.
+
+*Syntax*:
+----------------------
+status_command command
+----------------------
+
+*Example*:
+-------------------------------------------------
+status_command i3status --config ~/.i3status.conf
+-------------------------------------------------
+
+=== Display mode
+
+You can have i3bar either be visible permanently at one edge of the screen
+(+dock+ mode) or make it show up when you press your modifier key (+hide+
+mode).
+
+The hide mode maximizes screen space that can be used for actual windows. Also,
+i3bar sends the +SIGSTOP+ and +SIGCONT+ signals to the statusline process to
+save battery power.
+
+The default is dock mode.
+
+*Syntax*:
+----------------
+mode <dock|hide>
+----------------
+
+*Example*:
+----------------
+mode hide
+----------------
+
+=== Position
+
+This option determines in which edge of the screen i3bar should show up.
+
+The default is bottom.
+
+*Syntax*:
+---------------------
+position <top|bottom>
+---------------------
+
+*Example*:
+---------------------
+position top
+---------------------
+
+=== Output(s)
+
+You can restrict i3bar to one or more outputs (monitors). The default is to
+handle all outputs. Restricting the outputs is useful for using different
+options for different outputs by using multiple 'bar' blocks.
+
+*Syntax*:
+---------------
+output <output>
+---------------
+
+*Example*:
+-------------------------------
+# big monitor: everything
+bar {
+       output HDMI2
+       status_command i3status
+}
+
+# laptop monitor: bright colors and i3status with less modules.
+bar {
+       output LVDS1
+       status_command i3status --config ~/.i3status-small.conf
+       colors {
+               background #000000
+               statusline #ffffff
+       }
+}
+-------------------------------
+
+=== Tray output
+
+i3bar by default provides a system tray area where programs such as
+NetworkManager, VLC, Pidgin, etc. can place little icons.
+
+You can configure on which output (monitor) the icons should be displayed or
+you can turn off the functionality entirely.
+
+*Syntax*:
+-------------------------
+tray_output <none|output>
+-------------------------
+
+*Example*:
+-------------------------
+# disable system tray
+tray_output none
+
+# show tray icons on the big monitor
+tray_output HDMI2
+-------------------------
+
+=== Font
+
+Specifies the font (again, X core font, not Xft, just like in i3) to be used in
+the bar.
+
+*Syntax*:
+---------------------
+font <font>
+---------------------
+
+*Example*:
+--------------------------------------------------------------
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+--------------------------------------------------------------
+
+=== Workspace buttons
+
+Specifies whether workspace buttons should be shown or not. This is useful if
+you want to display a statusline-only bar containing additional information.
+
+The default is to show workspace buttons.
+
+*Syntax*:
+--------------------------
+workspace_buttons <yes|no>
+--------------------------
+
+*Example*:
+--------------------
+workspace_buttons no
+--------------------
+
+=== Colors
+
+As with i3, colors are in HTML hex format (#rrggbb). The following colors can
+be configured at the moment:
+
+background::
+       Background color of the bar.
+statusline::
+       Text color to be used for the statusline.
+focused_workspace::
+       Text color/background color for a workspace button when the workspace
+       has focus.
+active_workspace::
+       Text color/background color for a workspace button when the workspace
+       is active (visible) on some output, but the focus is on another one.
+       You can only tell this apart from the focused workspace when you are
+       using multiple monitors.
+inactive_workspace::
+       Text color/background color for a workspace button when the workspace
+       does not have focus and is not active (visible) on any output. This
+       will be the case for most workspaces.
+urgent_workspace::
+       Text color/background color for workspaces which contain at least one
+       window with the urgency hint set.
+
+*Syntax*:
+----------------------------------------
+colors {
+    background <color>
+    statusline <color>
+
+    colorclass <foreground> <background>
+}
+----------------------------------------
+
+*Example*:
+--------------------------------------
+colors {
+    background #000000
+    statusline #ffffff
+
+    focused_workspace  #ffffff #285577
+    active_workspace   #ffffff #333333
+    inactive_workspace #888888 #222222
+    urgent_workspace   #ffffff #900000
+}
+--------------------------------------
+
 == List of commands
 
 Commands are what you bind to specific keypresses. You can also issue commands
@@ -721,6 +1016,9 @@ which have the class Firefox, use:
 *Example*:
 ------------------------------------
 bindsym mod+x [class="Firefox"] kill
+
+# same thing, but case-insensitive
+bindsym mod+x [class="(?i)firefox"] kill
 ------------------------------------
 
 The criteria which are currently implemented are:
@@ -729,6 +1027,8 @@ class::
        Compares the window class (the second part of WM_CLASS)
 instance::
        Compares the window instance (the first part of WM_CLASS)
+window_role::
+       Compares the window role (WM_WINDOW_ROLE).
 id::
        Compares the X11 window ID, which you can get via +xwininfo+ for example.
 title::
@@ -739,8 +1039,40 @@ con_id::
        Compares the i3-internal container ID, which you can get via the IPC
        interface. Handy for scripting.
 
-Note that currently all criteria are compared case-insensitive and do not
-support regular expressions. This is planned to change in the future.
+The criteria +class+, +instance+, +role+, +title+ and +mark+ are actually
+regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
+information on how to use them.
+
+[[exec]]
+
+=== Executing applications (exec)
+
+What good is a window manager if you can’t actually start any applications?
+The exec command starts an application by passing the command you specify to a
+shell. This implies that you can use globbing (wildcards) and programs will be
+searched in your $PATH.
+
+*Syntax*:
+------------------------------
+exec [--no-startup-id] command
+------------------------------
+
+*Example*:
+------------------------------
+# Start the GIMP
+bindsym mod+g exec gimp
+
+# Start the terminal emulator urxvt which is not yet startup-notification-aware
+bindsym mod+enter exec --no-startup-id urxvt
+------------------------------
+
+The +--no-startup-id+ parameter disables startup-notification support for this
+particular exec command. With startup-notification, i3 can make sure that a
+window appears on the workspace on which you used the exec command. Also, it
+will change the X11 cursor to +watch+ (a clock) while the application is
+launching. So, if an application is not startup-notification aware (most GTK
+and Qt using applications seem to be, though), you will end up with a watch
+cursor for 60 seconds.
 
 === Splitting containers
 
@@ -806,9 +1138,19 @@ mode_toggle::
 
 For moving, use +move left+, +move right+, +move down+ and +move up+.
 
+*Syntax*:
+-----------------------------------
+focus <left|right|down|up>
+focus <parent|child|floating|tiling|mode_toggle>
+move <left|right|down|up> [<px> px]
+-----------------------------------
+
+Note that the amount of pixels you can specify for the +move+ command is only
+relevant for floating containers. The default amount is 10 pixels.
+
 *Examples*:
 ----------------------
-# Focus clients on the left, bottom, top, right:
+# Focus container on the left, bottom, top, right:
 bindsym mod+j focus left
 bindsym mod+k focus down
 bindsym mod+l focus up
@@ -820,11 +1162,15 @@ bindsym mod+u focus parent
 # Focus last floating/tiling container
 bindsym mod+g focus mode_toggle
 
-# Move client to the left, bottom, top, right:
+# 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
+
+# Move container, but make floating containers
+# move more than the default
+bindsym mod+j move left 20 px
 ----------------------
 
 === Changing (named) workspaces/moving to workspaces
@@ -836,7 +1182,17 @@ number or name of the workspace. To move containers to specific workspaces, use
 You can also switch to the next and previous workspace with the commands
 +workspace next+ and +workspace prev+, which is handy, for example, if you have
 workspace 1, 3, 4 and 9 and you want to cycle through them with a single key
-combination.
+combination. Similarily, you can use +move workspace next+ and +move workspace
+prev+ to move a container to the next/previous workspace.
+
+[[back_and_forth]]
+To switch back to the previously focused workspace, use +workspace
+back_and_forth+.
+
+To move a container to another xrandr output such as +LVDS1+ or +VGA1+, you can
+use the +move output+ command followed by the name of the target output. You
+may also use +left+, +right+, +up+, +down+ instead of the xrandr output name to
+move to the the next output in the specified direction.
 
 *Examples*:
 -------------------------
@@ -847,6 +1203,9 @@ bindsym mod+2 workspace 2
 bindsym mod+Shift+1 move workspace 1
 bindsym mod+Shift+2 move workspace 2
 ...
+
+# switch between the current and the previously focused one
+bindsym mod+b workspace back_and_forth
 -------------------------
 
 ==== Named workspaces
@@ -889,9 +1248,9 @@ resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt]
 
 Direction can be one of +up+, +down+, +left+ or +right+. The optional pixel
 argument specifies by how many pixels a *floating container* should be grown or
-shrinked (the default is 10 pixels). The ppt argument means percentage points
+shrunk (the default is 10 pixels). The ppt argument means percentage points
 and specifies by how many percentage points a *tiling container* should be
-grown or shrinked (the default is 10 percentage points).
+grown or shrunk (the default is 10 percentage points).
 
 I recommend using the resize command inside a so called +mode+:
 
index 43c3a1ff4c1fe55933f044dac2701810e98ad85a..27d5bf543c5e339e58bae65ffa8754e11205dcbf 100644 (file)
@@ -8,30 +8,35 @@ AUTOGENERATED:=cfgparse.tab.c cfgparse.yy.c
 FILES:=$(patsubst %.c,%.o,$(filter-out $(AUTOGENERATED),$(wildcard *.c)))
 HEADERS:=$(wildcard *.h)
 
+CPPFLAGS += -I$(TOPDIR)/include
+
 # Depend on the specific file (.c for each .o) and on all headers
 %.o: %.c ${HEADERS}
-       echo "CC $<"
+       echo "[i3-config-wizard] CC $<"
        $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
 
 all: i3-config-wizard
 
-i3-config-wizard: cfgparse.y.o cfgparse.yy.o ${FILES}
-       echo "LINK i3-config-wizard"
-       $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+i3-config-wizard: $(TOPDIR)/libi3/libi3.a cfgparse.y.o cfgparse.yy.o ${FILES}
+       echo "[i3-config-wizard] LINK i3-config-wizard"
+       $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
+
+$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
+       $(MAKE) -C $(TOPDIR)/libi3
 
 cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS}
-       echo "LEX $<"
+       echo "[i3-config-wizard] LEX $<"
        flex -i -o$(@:.o=.c) $<
        $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(@:.o=.c)
 
 cfgparse.y.o: cfgparse.y ${HEADERS}
-       echo "YACC $<"
+       echo "[i3-config-wizard] YACC $<"
        bison --debug --verbose -b $(basename $< .y) -d $<
        $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(<:.y=.tab.c)
 
 
 install: all
-       echo "INSTALL"
+       echo "[i3-config-wizard] INSTALL"
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
        $(INSTALL) -m 0755 i3-config-wizard $(DESTDIR)$(PREFIX)/bin/
 
index 018b37b2220682efdcd712c4b66d0bdc32eb53d4..bbe119371430b0084605096dd9e0e8293fe824ab 100644 (file)
@@ -14,6 +14,8 @@
 
 #include <X11/Xlib.h>
 
+#include "libi3.h"
+
 extern Display *dpy;
 
 struct context {
@@ -141,7 +143,7 @@ bindcode:
         char *str = XKeysymToString(sym);
         char *modifiers = modifier_to_string($<number>3);
         // TODO: modifier to string
-        asprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $<string>6);
+        sasprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $<string>6);
         free(modifiers);
     }
     ;
diff --git a/i3-config-wizard/ipc.c b/i3-config-wizard/ipc.c
deleted file mode 100644 (file)
index 597a86e..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdint.h>
-#include <string.h>
-#include <stdlib.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <unistd.h>
-#include <err.h>
-
-/*
- * Formats a message (payload) of the given size and type and sends it to i3 via
- * the given socket file descriptor.
- *
- */
-void ipc_send_message(int sockfd, uint32_t message_size,
-                      uint32_t message_type, uint8_t *payload) {
-        int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
-        char msg[buffer_size];
-        char *walk = msg;
-
-        strcpy(walk, "i3-ipc");
-        walk += strlen("i3-ipc");
-        memcpy(walk, &message_size, sizeof(uint32_t));
-        walk += sizeof(uint32_t);
-        memcpy(walk, &message_type, sizeof(uint32_t));
-        walk += sizeof(uint32_t);
-        memcpy(walk, payload, message_size);
-
-        int sent_bytes = 0;
-        int bytes_to_go = buffer_size;
-        while (sent_bytes < bytes_to_go) {
-                int n = write(sockfd, msg + sent_bytes, bytes_to_go);
-                if (n == -1)
-                        err(EXIT_FAILURE, "write() failed");
-
-                sent_bytes += n;
-                bytes_to_go -= n;
-        }
-}
-
-/*
- * Connects to the i3 IPC socket and returns the file descriptor for the
- * socket. die()s if anything goes wrong.
- *
- */
-int connect_ipc(char *socket_path) {
-       int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
-        if (sockfd == -1)
-                err(EXIT_FAILURE, "Could not create socket");
-
-        struct sockaddr_un addr;
-        memset(&addr, 0, sizeof(struct sockaddr_un));
-        addr.sun_family = AF_LOCAL;
-        strcpy(addr.sun_path, socket_path);
-        if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
-                err(EXIT_FAILURE, "Could not connect to i3");
-
-       return sockfd;
-}
diff --git a/i3-config-wizard/ipc.h b/i3-config-wizard/ipc.h
deleted file mode 100644 (file)
index c40c909..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-#ifndef _IPC_H
-#define _IPC_H
-
-void ipc_send_message(int sockfd, uint32_t message_size,
-                      uint32_t message_type, uint8_t *payload);
-
-int connect_ipc(char *socket_path);
-
-#endif
index da1f76a3664338854aec7ea9f260b7e6930d07f6..cdce0653be25f34053274615d5b68666a27406e3 100644 (file)
@@ -2,13 +2,10 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-config-wizard: Program to convert configs using keycodes to configs using
- * keysyms.
+ *                   keysyms.
  *
  */
 #include <ev.h>
 while (0)
 
 #include "xcb.h"
-#include "ipc.h"
+#include "libi3.h"
 
 enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME;
 enum { MOD_Mod1, MOD_Mod4 } modifier = MOD_Mod4;
 
 static char *config_path;
-static xcb_connection_t *conn;
+static uint32_t xcb_numlock_mask;
+xcb_connection_t *conn;
 static xcb_get_modifier_mapping_reply_t *modmap_reply;
-static uint32_t font_id;
-static uint32_t font_bold_id;
+static i3Font font;
+static i3Font bold_font;
 static char *socket_path;
-static int font_height;
-static int font_bold_height;
 static xcb_window_t win;
 static xcb_pixmap_t pixmap;
 static xcb_gcontext_t pixmap_gc;
@@ -74,30 +70,6 @@ Display *dpy;
 char *rewrite_binding(const char *bindingline);
 static void finish();
 
-#if defined(__APPLE__)
-
-/*
- * Taken from FreeBSD
- * Returns a pointer to a new string which is a duplicate of the
- * string, but only copies at most n characters.
- *
- */
-char *strndup(const char *str, size_t n) {
-    size_t len;
-    char *copy;
-
-    for (len = 0; len < n && str[len]; len++)
-        continue;
-
-    if ((copy = malloc(len + 1)) == NULL)
-        return (NULL);
-    memcpy(copy, str, len);
-    copy[len] = '\0';
-    return (copy);
-}
-
-#endif
-
 /*
  * This function resolves ~ in pathnames.
  * It may resolve wildcards in the first part of the path, but if no match
@@ -130,59 +102,23 @@ static char *resolve_tilde(const char *path) {
     return result;
 }
 
-/*
- * Try to get the socket path from X11 and return NULL if it doesn’t work.
- * As i3-msg is a short-running tool, we don’t bother with cleaning up the
- * connection and leave it up to the operating system on exit.
- *
- */
-static char *socket_path_from_x11() {
-    xcb_connection_t *conn;
-    int screen;
-    if ((conn = xcb_connect(NULL, &screen)) == NULL ||
-        xcb_connection_has_error(conn))
-        return NULL;
-    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
-    xcb_window_t root = root_screen->root;
-
-    xcb_intern_atom_cookie_t atom_cookie;
-    xcb_intern_atom_reply_t *atom_reply;
-
-    atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
-    atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
-    if (atom_reply == NULL)
-        return NULL;
-
-    xcb_get_property_cookie_t prop_cookie;
-    xcb_get_property_reply_t *prop_reply;
-    prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
-                                             XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
-    prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
-    if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
-        return NULL;
-    if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
-                 (char*)xcb_get_property_value(prop_reply)) == -1)
-        return NULL;
-    return socket_path;
-}
-
 /*
  * Handles expose events, that is, draws the window contents.
  *
  */
 static int handle_expose() {
     /* re-draw the background */
-    xcb_rectangle_t border = {0, 0, 300, (15*font_height) + 8};
-    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
+    xcb_rectangle_t border = {0, 0, 300, (15 * font.height) + 8};
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#000000") });
     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
 
-    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
 
-#define txt(x, row, text) xcb_image_text_8(conn, strlen(text), pixmap, pixmap_gc, x, (row * font_height) + 2, text)
+#define txt(x, row, text) xcb_image_text_8(conn, strlen(text), pixmap, pixmap_gc, x, (row * font.height) + 2, text)
 
     if (current_step == STEP_WELCOME) {
         /* restore font color */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
+        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
 
         txt(10, 2, "You have not configured i3 yet.");
         txt(10, 3, "Do you want me to generate ~/.i3/config?");
@@ -190,16 +126,16 @@ static int handle_expose() {
         txt(85, 7, "No, I will use the defaults");
 
         /* green */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#00FF00"));
+        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#00FF00") });
         txt(25, 5, "<Enter>");
 
         /* red */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
+        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
         txt(31, 7, "<ESC>");
     }
 
     if (current_step == STEP_GENERATE) {
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
+        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
 
         txt(10, 2, "Please choose either:");
         txt(85, 4, "Win as default modifier");
@@ -214,20 +150,19 @@ static int handle_expose() {
         else txt(31, 4, "<Win>");
 
         /* the selected modifier */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_bold_id);
+        xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ bold_font.id });
         if (modifier == MOD_Mod4)
             txt(31, 4, "<Win>");
         else txt(31, 5, "<Alt>");
 
         /* green */
-        uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_FONT;
-        uint32_t values[] = { get_colorpixel(conn, "#00FF00"), font_id };
-        xcb_change_gc(conn, pixmap_gc, mask, values);
+        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_FONT,
+                      (uint32_t[]) { get_colorpixel("#00FF00"), font.id });
 
         txt(25, 9, "<Enter>");
 
         /* red */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
+        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
         txt(31, 10, "<ESC>");
     }
 
@@ -406,7 +341,7 @@ static void finish() {
     fclose(ks_config);
 
     /* tell i3 to reload the config file */
-    int sockfd = connect_ipc(socket_path);
+    int sockfd = ipc_connect(socket_path);
     ipc_send_message(sockfd, strlen("reload"), 0, (uint8_t*)"reload");
     close(sockfd);
 
@@ -485,6 +420,7 @@ int main(int argc, char *argv[]) {
 
     xcb_get_modifier_mapping_cookie_t modmap_cookie;
     modmap_cookie = xcb_get_modifier_mapping(conn);
+    symbols = xcb_key_symbols_alloc(conn);
 
     /* Place requests for the atoms we need as soon as possible */
     #define xmacro(atom) \
@@ -498,17 +434,31 @@ int main(int argc, char *argv[]) {
     if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL)))
         errx(EXIT_FAILURE, "Could not get modifier mapping\n");
 
-    /* XXX: we should refactor xcb_get_numlock_mask so that it uses the
-     * modifier mapping we already have */
-    xcb_get_numlock_mask(conn);
-
-    symbols = xcb_key_symbols_alloc(conn);
+    xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply);
 
-    font_id = get_font_id(conn, pattern, &font_height);
-    font_bold_id = get_font_id(conn, patternbold, &font_bold_height);
+    font = load_font(pattern, true);
+    bold_font = load_font(patternbold, true);
 
     /* Open an input window */
-    win = open_input_window(conn, 300, 205);
+    win = xcb_generate_id(conn);
+    xcb_create_window(
+        conn,
+        XCB_COPY_FROM_PARENT,
+        win, /* the window id */
+        root, /* parent == root */
+        490, 297, 300, 205, /* dimensions */
+        0, /* X11 border = 0, we draw our own */
+        XCB_WINDOW_CLASS_INPUT_OUTPUT,
+        XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+        XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
+        (uint32_t[]){
+            0, /* back pixel: black */
+            XCB_EVENT_MASK_EXPOSURE |
+            XCB_EVENT_MASK_BUTTON_PRESS
+        });
+
+    /* Map the window (make it visible) */
+    xcb_map_window(conn, win);
 
     /* Setup NetWM atoms */
     #define xmacro(name) \
diff --git a/i3-config-wizard/xcb.c b/i3-config-wizard/xcb.c
deleted file mode 100644 (file)
index 461dfd5..0000000
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <err.h>
-
-#include <xcb/xcb.h>
-#include <xcb/xcb_keysyms.h>
-
-#include <X11/keysym.h>
-
-#include "xcb.h"
-
-extern xcb_window_t root;
-unsigned int xcb_numlock_mask;
-
-/*
- * Convenience-wrapper around xcb_change_gc which saves us declaring a variable
- *
- */
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
-        xcb_change_gc(conn, gc, mask, &value);
-}
-
-/*
- * Returns the colorpixel to use for the given hex color (think of HTML).
- *
- * The hex_color has to start with #, for example #FF00FF.
- *
- * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
- * This has to be done by the caller.
- *
- */
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
-        char strgroups[3][3] = {{hex[1], hex[2], '\0'},
-                                {hex[3], hex[4], '\0'},
-                                {hex[5], hex[6], '\0'}};
-        uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
-                             (strtol(strgroups[1], NULL, 16)),
-                             (strtol(strgroups[2], NULL, 16))};
-
-        return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
-}
-
-/*
- * Returns the mask for Mode_switch (to be used for looking up keysymbols by
- * keycode).
- *
- */
-uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) {
-       xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(conn);
-
-       xcb_get_modifier_mapping_reply_t *modmap_r;
-       xcb_keycode_t *modmap, kc;
-       xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, keycode);
-       if (modeswitchcodes == NULL)
-               return 0;
-
-       modmap_r = xcb_get_modifier_mapping_reply(conn, xcb_get_modifier_mapping(conn), NULL);
-       modmap = xcb_get_modifier_mapping_keycodes(modmap_r);
-
-       for (int i = 0; i < 8; i++)
-               for (int j = 0; j < modmap_r->keycodes_per_modifier; j++) {
-                       kc = modmap[i * modmap_r->keycodes_per_modifier + j];
-                       for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) {
-                               if (*ktest != kc)
-                                       continue;
-
-                               free(modeswitchcodes);
-                               free(modmap_r);
-                               return (1 << i);
-                       }
-               }
-
-       return 0;
-}
-
-/*
- * Opens the window we use for input/output and maps it
- *
- */
-xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
-        xcb_window_t win = xcb_generate_id(conn);
-        //xcb_cursor_t cursor_id = xcb_generate_id(conn);
-
-#if 0
-        /* Use the default cursor (left pointer) */
-        if (cursor > -1) {
-                i3Font *cursor_font = load_font(conn, "cursor");
-                xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
-                                XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
-                                0, 0, 0, 65535, 65535, 65535);
-        }
-#endif
-
-        uint32_t mask = 0;
-        uint32_t values[3];
-
-        mask |= XCB_CW_BACK_PIXEL;
-        values[0] = 0;
-
-       mask |= XCB_CW_EVENT_MASK;
-       values[1] = XCB_EVENT_MASK_EXPOSURE |
-                    XCB_EVENT_MASK_BUTTON_PRESS;
-
-        xcb_create_window(conn,
-                          XCB_COPY_FROM_PARENT,
-                          win, /* the window id */
-                          root, /* parent == root */
-                          490, 297, width, height, /* dimensions */
-                          0, /* border = 0, we draw our own */
-                          XCB_WINDOW_CLASS_INPUT_OUTPUT,
-                          XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
-                          mask,
-                          values);
-
-#if 0
-        if (cursor > -1)
-                xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
-#endif
-
-        /* Map the window (= make it visible) */
-        xcb_map_window(conn, win);
-
-       return win;
-}
-
-/*
- * Returns the ID of the font matching the given pattern and stores the height
- * of the font (in pixels) in *font_height. die()s if no font matches.
- *
- */
-int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
-        xcb_void_cookie_t font_cookie;
-        xcb_list_fonts_with_info_cookie_t info_cookie;
-
-        /* Send all our requests first */
-        int result;
-        result = xcb_generate_id(conn);
-        font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern);
-        info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
-        xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
-        if (error != NULL) {
-                fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code);
-                exit(1);
-        }
-
-        /* Get information (height/name) for this font */
-        xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
-        if (reply == NULL)
-                errx(1, "Could not load font \"%s\"\n", pattern);
-
-        *font_height = reply->font_ascent + reply->font_descent;
-
-        return result;
-}
-
-/*
- * Finds out which modifier mask is the one for numlock, as the user may change this.
- *
- */
-void xcb_get_numlock_mask(xcb_connection_t *conn) {
-    xcb_key_symbols_t *keysyms;
-    xcb_get_modifier_mapping_cookie_t cookie;
-    xcb_get_modifier_mapping_reply_t *reply;
-    xcb_keycode_t *modmap;
-    int mask, i;
-    const int masks[8] = { XCB_MOD_MASK_SHIFT,
-                           XCB_MOD_MASK_LOCK,
-                           XCB_MOD_MASK_CONTROL,
-                           XCB_MOD_MASK_1,
-                           XCB_MOD_MASK_2,
-                           XCB_MOD_MASK_3,
-                           XCB_MOD_MASK_4,
-                           XCB_MOD_MASK_5 };
-
-    /* Request the modifier map */
-    cookie = xcb_get_modifier_mapping_unchecked(conn);
-
-    /* Get the keysymbols */
-    keysyms = xcb_key_symbols_alloc(conn);
-
-    if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) {
-        xcb_key_symbols_free(keysyms);
-        return;
-    }
-
-    modmap = xcb_get_modifier_mapping_keycodes(reply);
-
-    /* Get the keycode for numlock */
-#ifdef OLD_XCB_KEYSYMS_API
-    xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
-#else
-    /* For now, we only use the first keysymbol. */
-    xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
-    if (numlock_syms == NULL)
-        return;
-    xcb_keycode_t numlock = *numlock_syms;
-    free(numlock_syms);
-#endif
-
-    /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
-    for (mask = 0; mask < 8; mask++)
-        for (i = 0; i < reply->keycodes_per_modifier; i++)
-            if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
-                xcb_numlock_mask = masks[mask];
-
-    xcb_key_symbols_free(keysyms);
-    free(reply);
-}
-
index 40aeec8864fdec22fb2a8e86a232fb8fef981061..4ed182c4af900ae450ef28e588a2423264395e74 100644 (file)
@@ -8,17 +8,4 @@
 #include "atoms.xmacro"
 #undef xmacro
 
-extern unsigned int xcb_numlock_mask;
-
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
-uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode);
-xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
-int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
-/**
- * Finds out which modifier mask is the one for numlock, as the user may change this.
- *
- */
-void xcb_get_numlock_mask(xcb_connection_t *conn);
-
 #endif
index 45653dadbc44884e61355f6dd66b91009ce07094..493df784d7e1aacfe5c549fa6dd60dbbf8c09240 100644 (file)
@@ -3,23 +3,28 @@ TOPDIR=..
 
 include $(TOPDIR)/common.mk
 
+CPPFLAGS += -I$(TOPDIR)/include
+
 # Depend on the object files of all source-files in src/*.c and on all header files
 FILES=$(patsubst %.c,%.o,$(wildcard *.c))
 HEADERS=$(wildcard *.h)
 
 # Depend on the specific file (.c for each .o) and on all headers
 %.o: %.c ${HEADERS}
-       echo "CC $<"
+       echo "[i3-input] CC $<"
        $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
 
 all: i3-input
 
-i3-input: ${FILES}
-       echo "LINK i3-input"
-       $(CC) $(LDFLAGS) -o $@ ${FILES} $(LIBS)
+i3-input: $(TOPDIR)/libi3/libi3.a ${FILES}
+       echo "[i3-input] LINK i3-input"
+       $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
+
+$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
+       $(MAKE) -C $(TOPDIR)/libi3
 
 install: all
-       echo "INSTALL"
+       echo "[i3-input] INSTALL"
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
        $(INSTALL) -m 0755 i3-input $(DESTDIR)$(PREFIX)/bin/
 
index 581203d4b19682683954200142eb42852de5cd7b..d97807d1749decec42982bf935e50fb37d6028f3 100644 (file)
@@ -16,13 +16,5 @@ extern xcb_window_t root;
 
 char *convert_ucs_to_utf8(char *input);
 char *convert_utf8_to_ucs2(char *input, int *real_strlen);
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
-uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode);
-int connect_ipc(char *socket_path);
-void ipc_send_message(int sockfd, uint32_t message_size,
-                      uint32_t message_type, uint8_t *payload);
-xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
-int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
 
 #endif
diff --git a/i3-input/ipc.c b/i3-input/ipc.c
deleted file mode 100644 (file)
index 2d11f0e..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdint.h>
-#include <string.h>
-#include <stdlib.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <unistd.h>
-#include <err.h>
-
-/*
- * Formats a message (payload) of the given size and type and sends it to i3 via
- * the given socket file descriptor.
- *
- */
-void ipc_send_message(int sockfd, uint32_t message_size,
-                      uint32_t message_type, uint8_t *payload) {
-        int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
-        char msg[buffer_size];
-        char *walk = msg;
-
-        strcpy(walk, "i3-ipc");
-        walk += strlen("i3-ipc");
-        memcpy(walk, &message_size, sizeof(uint32_t));
-        walk += sizeof(uint32_t);
-        memcpy(walk, &message_type, sizeof(uint32_t));
-        walk += sizeof(uint32_t);
-        memcpy(walk, payload, message_size);
-
-        int sent_bytes = 0;
-        int bytes_to_go = buffer_size;
-        while (sent_bytes < bytes_to_go) {
-                int n = write(sockfd, msg + sent_bytes, bytes_to_go);
-                if (n == -1)
-                        err(EXIT_FAILURE, "write() failed");
-
-                sent_bytes += n;
-                bytes_to_go -= n;
-        }
-}
-
-/*
- * Connects to the i3 IPC socket and returns the file descriptor for the
- * socket. die()s if anything goes wrong.
- *
- */
-int connect_ipc(char *socket_path) {
-       int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
-        if (sockfd == -1)
-                err(EXIT_FAILURE, "Could not create socket");
-
-        struct sockaddr_un addr;
-        memset(&addr, 0, sizeof(struct sockaddr_un));
-        addr.sun_family = AF_LOCAL;
-        strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
-        if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
-                err(EXIT_FAILURE, "Could not connect to i3");
-
-       return sockfd;
-}
index fb2635a274024e1f41199bd3e71dd8a164fd9bb0..def6848177464d7a86eec2aa8b91ef3ad4b7f2f7 100644 (file)
@@ -1,14 +1,11 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-input/main.c: Utility which lets the user input commands and sends them
- * to i3.
+ *                  to i3.
  *
  */
 #include <ev.h>
 
 #include "i3-input.h"
 
+#include "libi3.h"
+
+/* IPC format string. %s will be replaced with what the user entered, then
+ * the command will be sent to i3 */
+static char *format;
+
 static char *socket_path;
 static int sockfd;
 static xcb_key_symbols_t *symbols;
-static int modeswitchmask;
-static int numlockmask;
 static bool modeswitch_active = false;
 static xcb_window_t win;
 static xcb_pixmap_t pixmap;
@@ -47,48 +48,12 @@ static xcb_gcontext_t pixmap_gc;
 static char *glyphs_ucs[512];
 static char *glyphs_utf8[512];
 static int input_position;
-static int font_height;
-static char *command_prefix;
+static i3Font font;
 static char *prompt;
 static int prompt_len;
 static int limit;
 xcb_window_t root;
-
-/*
- * Try to get the socket path from X11 and return NULL if it doesn’t work.
- * As i3-msg is a short-running tool, we don’t bother with cleaning up the
- * connection and leave it up to the operating system on exit.
- *
- */
-static char *socket_path_from_x11() {
-        xcb_connection_t *conn;
-        int screen;
-        if ((conn = xcb_connect(NULL, &screen)) == NULL ||
-            xcb_connection_has_error(conn))
-                return NULL;
-        xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
-        xcb_window_t root = root_screen->root;
-
-        xcb_intern_atom_cookie_t atom_cookie;
-        xcb_intern_atom_reply_t *atom_reply;
-
-        atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
-        atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
-        if (atom_reply == NULL)
-                return NULL;
-
-        xcb_get_property_cookie_t prop_cookie;
-        xcb_get_property_reply_t *prop_reply;
-        prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
-                                                 XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
-        prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
-        if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
-                return NULL;
-        if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
-                     (char*)xcb_get_property_value(prop_reply)) == -1)
-                return NULL;
-        return socket_path;
-}
+xcb_connection_t *conn;
 
 /*
  * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
@@ -96,21 +61,21 @@ static char *socket_path_from_x11() {
  *
  */
 static uint8_t *concat_strings(char **glyphs, int max) {
-        uint8_t *output = calloc(max+1, 4);
-        uint8_t *walk = output;
-        for (int c = 0; c < max; c++) {
-                printf("at %c\n", glyphs[c][0]);
-                /* if the first byte is 0, this has to be UCS2 */
-                if (glyphs[c][0] == '\0') {
-                        memcpy(walk, glyphs[c], 2);
-                        walk += 2;
-                } else {
-                        strcpy((char*)walk, glyphs[c]);
-                        walk += strlen(glyphs[c]);
-                }
+    uint8_t *output = calloc(max+1, 4);
+    uint8_t *walk = output;
+    for (int c = 0; c < max; c++) {
+        printf("at %c\n", glyphs[c][0]);
+        /* if the first byte is 0, this has to be UCS2 */
+        if (glyphs[c][0] == '\0') {
+            memcpy(walk, glyphs[c], 2);
+            walk += 2;
+        } else {
+            strcpy((char*)walk, glyphs[c]);
+            walk += strlen(glyphs[c]);
         }
-        printf("output = %s\n", output);
-        return output;
+    }
+    printf("output = %s\n", output);
+    return output;
 }
 
 /*
@@ -119,37 +84,37 @@ static uint8_t *concat_strings(char **glyphs, int max) {
  *
  */
 static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) {
-        printf("expose!\n");
-
-        /* re-draw the background */
-        xcb_rectangle_t border = {0, 0, 500, font_height + 8}, inner = {2, 2, 496, font_height + 8 - 4};
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
-        xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
-        xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
-
-        /* restore font color */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
-        uint8_t *con = concat_strings(glyphs_ucs, input_position);
-        char *full_text = (char*)con;
-        if (prompt != NULL) {
-                full_text = malloc((prompt_len + input_position) * 2 + 1);
-                if (full_text == NULL)
-                        err(EXIT_FAILURE, "malloc() failed\n");
-                memcpy(full_text, prompt, prompt_len * 2);
-                memcpy(full_text + (prompt_len * 2), con, input_position * 2);
-        }
-        xcb_image_text_16(conn, input_position + prompt_len, pixmap, pixmap_gc, 4 /* X */,
-                          font_height + 2 /* Y = baseline of font */, (xcb_char2b_t*)full_text);
-
-        /* Copy the contents of the pixmap to the real window */
-        xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font_height + 8);
-        xcb_flush(conn);
-        free(con);
-        if (prompt != NULL)
-                free(full_text);
-
-        return 1;
+    printf("expose!\n");
+
+    /* re-draw the background */
+    xcb_rectangle_t border = {0, 0, 500, font.height + 8}, inner = {2, 2, 496, font.height + 8 - 4};
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
+    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#000000") });
+    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
+
+    /* restore font color */
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
+    uint8_t *con = concat_strings(glyphs_ucs, input_position);
+    char *full_text = (char*)con;
+    if (prompt != NULL) {
+        full_text = malloc((prompt_len + input_position) * 2 + 1);
+        if (full_text == NULL)
+            err(EXIT_FAILURE, "malloc() failed\n");
+        memcpy(full_text, prompt, prompt_len * 2);
+        memcpy(full_text + (prompt_len * 2), con, input_position * 2);
+    }
+    xcb_image_text_16(conn, input_position + prompt_len, pixmap, pixmap_gc, 4 /* X */,
+                      font.height + 2 /* Y = baseline of font */, (xcb_char2b_t*)full_text);
+
+    /* Copy the contents of the pixmap to the real window */
+    xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font.height + 8);
+    xcb_flush(conn);
+    free(con);
+    if (prompt != NULL)
+        free(full_text);
+
+    return 1;
 }
 
 /*
@@ -157,37 +122,69 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
  *
  */
 static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
-        printf("releasing %d, state raw = %d\n", event->detail, event->state);
+    printf("releasing %d, state raw = %d\n", event->detail, event->state);
 
-        /* fix state */
-        event->state &= ~numlockmask;
+    /* See the documentation of xcb_key_symbols_get_keysym for this one.
+     * Basically: We get either col 0 or col 1, depending on whether shift is
+     * pressed. */
+    int col = (event->state & XCB_MOD_MASK_SHIFT);
 
-        xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
-        if (sym == XK_Mode_switch) {
-                printf("Mode switch disabled\n");
-                modeswitch_active = false;
-        }
+    /* If modeswitch is currently active, we need to look in group 2 or 3,
+     * respectively. */
+    if (modeswitch_active)
+        col += 2;
 
-        return 1;
+    xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
+    if (sym == XK_Mode_switch) {
+        printf("Mode switch disabled\n");
+        modeswitch_active = false;
+    }
+
+    return 1;
 }
 
 static void finish_input() {
-        uint8_t *command = concat_strings(glyphs_utf8, input_position);
-        char *full_command = (char*)command;
-        /* prefix the command if a prefix was specified on commandline */
-        if (command_prefix != NULL) {
-                if (asprintf(&full_command, "%s%s", command_prefix, command) == -1)
-                        err(EXIT_FAILURE, "asprintf() failed\n");
+    char *command = (char*)concat_strings(glyphs_utf8, input_position);
+
+    /* count the occurences of %s in the string */
+    int c;
+    int len = strlen(format);
+    int cnt = 0;
+    for (c = 0; c < (len-1); c++)
+        if (format[c] == '%' && format[c+1] == 's')
+            cnt++;
+    printf("occurences = %d\n", cnt);
+
+    /* allocate space for the output */
+    int inputlen = strlen(command);
+    char *full = calloc(1,
+                        strlen(format) - (2 * cnt) /* format without all %s */
+                        + (inputlen * cnt)         /* replaced %s */
+                        + 1);                      /* trailing NUL */
+    char *dest = full;
+    for (c = 0; c < len; c++) {
+        /* if this is not % or it is % but without a following 's',
+         * just copy the character */
+        if (format[c] != '%' || (c == (len-1)) || format[c+1] != 's')
+            *(dest++) = format[c];
+        else {
+            strncat(dest, command, inputlen);
+            dest += inputlen;
+            /* skip the following 's' of '%s' */
+            c++;
         }
-        printf("command = %s\n", full_command);
+    }
+
+    /* prefix the command if a prefix was specified on commandline */
+    printf("command = %s\n", full);
 
-        ipc_send_message(sockfd, strlen(full_command), 0, (uint8_t*)full_command);
+    ipc_send_message(sockfd, strlen(full), 0, (uint8_t*)full);
 
 #if 0
-        free(command);
-        return 1;
+    free(command);
+    return 1;
 #endif
-        exit(0);
+    exit(0);
 }
 
 /*
@@ -200,224 +197,254 @@ static void finish_input() {
  *
  */
 static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
-        printf("Keypress %d, state raw = %d\n", event->detail, event->state);
-
-        /* fix state */
-        if (modeswitch_active)
-                event->state |= modeswitchmask;
-
-        /* Apparantly, after activating numlock once, the numlock modifier
-         * stays turned on (use xev(1) to verify). So, to resolve useful
-         * keysyms, we remove the numlock flag from the event state */
-        event->state &= ~numlockmask;
-
-        xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
-        if (sym == XK_Mode_switch) {
-                printf("Mode switch enabled\n");
-                modeswitch_active = true;
-                return 1;
-        }
-
-        if (sym == XK_Return)
-                finish_input();
-
-        if (sym == XK_BackSpace) {
-                if (input_position == 0)
-                        return 1;
-
-                input_position--;
-                free(glyphs_ucs[input_position]);
-                free(glyphs_utf8[input_position]);
-
-                handle_expose(NULL, conn, NULL);
-                return 1;
-        }
-        if (sym == XK_Escape) {
-                exit(0);
-        }
-
-        /* TODO: handle all of these? */
-        printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym));
-        printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym));
-        printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym));
-        printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym));
-        printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym));
-        printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym));
-        printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
-
-        if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym))
-                return 1;
-
-        printf("sym = %c (%d)\n", sym, sym);
-
-        /* convert the keysym to UCS */
-        uint16_t ucs = keysym2ucs(sym);
-        if ((int16_t)ucs == -1) {
-                fprintf(stderr, "Keysym could not be converted to UCS, skipping\n");
-                return 1;
-        }
-
-        /* store the UCS into a string */
-        uint8_t inp[3] = {(ucs & 0xFF00) >> 8, (ucs & 0xFF), 0};
+    printf("Keypress %d, state raw = %d\n", event->detail, event->state);
+
+    /* See the documentation of xcb_key_symbols_get_keysym for this one.
+     * Basically: We get either col 0 or col 1, depending on whether shift is
+     * pressed. */
+    int col = (event->state & XCB_MOD_MASK_SHIFT);
+
+    /* If modeswitch is currently active, we need to look in group 2 or 3,
+     * respectively. */
+    if (modeswitch_active)
+        col += 2;
+
+    xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, col);
+    if (sym == XK_Mode_switch) {
+        printf("Mode switch enabled\n");
+        modeswitch_active = true;
+        return 1;
+    }
 
-        printf("inp[0] = %02x, inp[1] = %02x, inp[2] = %02x\n", inp[0], inp[1], inp[2]);
-        /* convert it to UTF-8 */
-        char *out = convert_ucs_to_utf8((char*)inp);
-        printf("converted to %s\n", out);
+    if (sym == XK_Return)
+        finish_input();
 
-        glyphs_ucs[input_position] = malloc(3 * sizeof(uint8_t));
-        if (glyphs_ucs[input_position] == NULL)
-                err(EXIT_FAILURE, "malloc() failed\n");
-        memcpy(glyphs_ucs[input_position], inp, 3);
-        glyphs_utf8[input_position] = strdup(out);
-        input_position++;
+    if (sym == XK_BackSpace) {
+        if (input_position == 0)
+            return 1;
 
-        if (input_position == limit)
-                finish_input();
+        input_position--;
+        free(glyphs_ucs[input_position]);
+        free(glyphs_utf8[input_position]);
 
         handle_expose(NULL, conn, NULL);
         return 1;
-}
-
-int main(int argc, char *argv[]) {
-        socket_path = getenv("I3SOCK");
-        char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
-        int o, option_index = 0;
-
-        static struct option long_options[] = {
-                {"socket", required_argument, 0, 's'},
-                {"version", no_argument, 0, 'v'},
-                {"limit", required_argument, 0, 'l'},
-                {"prompt", required_argument, 0, 'P'},
-                {"prefix", required_argument, 0, 'p'},
-                {"font", required_argument, 0, 'f'},
-                {"help", no_argument, 0, 'h'},
-                {0, 0, 0, 0}
-        };
-
-        char *options_string = "s:p:P:f:l:vh";
-
-        while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
-                switch (o) {
-                        case 's':
-                                FREE(socket_path);
-                                socket_path = strdup(optarg);
-                                break;
-                        case 'v':
-                                printf("i3-input " I3_VERSION);
-                                return 0;
-                        case 'p':
-                                FREE(command_prefix);
-                                command_prefix = strdup(optarg);
-                                break;
-                        case 'l':
-                                limit = atoi(optarg);
-                                break;
-                        case 'P':
-                                FREE(prompt);
-                                prompt = strdup(optarg);
-                                break;
-                        case 'f':
-                                FREE(pattern);
-                                pattern = strdup(optarg);
-                                break;
-                        case 'h':
-                                printf("i3-input " I3_VERSION);
-                                printf("i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]\n");
-                                return 0;
-                }
-        }
-
-        if (socket_path == NULL)
-                socket_path = socket_path_from_x11();
-
-        if (socket_path == NULL)
-                socket_path = "/tmp/i3-ipc.sock";
-
-        sockfd = connect_ipc(socket_path);
-
-        if (prompt != NULL)
-                prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
-
-        int screens;
-        xcb_connection_t *conn = xcb_connect(NULL, &screens);
-        if (xcb_connection_has_error(conn))
-                die("Cannot open display\n");
-
-        xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
-        root = root_screen->root;
-
-        modeswitchmask = get_mod_mask(conn, XK_Mode_switch);
-        numlockmask = get_mod_mask(conn, XK_Num_Lock);
-       symbols = xcb_key_symbols_alloc(conn);
+    }
+    if (sym == XK_Escape) {
+        exit(0);
+    }
+
+    /* TODO: handle all of these? */
+    printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym));
+    printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym));
+    printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym));
+    printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym));
+    printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym));
+    printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym));
+    printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
+
+    if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym))
+        return 1;
 
-        uint32_t font_id = get_font_id(conn, pattern, &font_height);
+    printf("sym = %c (%d)\n", sym, sym);
 
-        /* Open an input window */
-        win = open_input_window(conn, 500, font_height + 8);
+    /* convert the keysym to UCS */
+    uint16_t ucs = keysym2ucs(sym);
+    if ((int16_t)ucs == -1) {
+        fprintf(stderr, "Keysym could not be converted to UCS, skipping\n");
+        return 1;
+    }
 
-        /* Create pixmap */
-        pixmap = xcb_generate_id(conn);
-        pixmap_gc = xcb_generate_id(conn);
-        xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
-        xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
+    /* store the UCS into a string */
+    uint8_t inp[3] = {(ucs & 0xFF00) >> 8, (ucs & 0xFF), 0};
 
-        /* Set input focus (we have override_redirect=1, so the wm will not do
-         * this for us) */
-        xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
+    printf("inp[0] = %02x, inp[1] = %02x, inp[2] = %02x\n", inp[0], inp[1], inp[2]);
+    /* convert it to UTF-8 */
+    char *out = convert_ucs_to_utf8((char*)inp);
+    printf("converted to %s\n", out);
 
-        /* Create graphics context */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+    glyphs_ucs[input_position] = malloc(3 * sizeof(uint8_t));
+    if (glyphs_ucs[input_position] == NULL)
+        err(EXIT_FAILURE, "malloc() failed\n");
+    memcpy(glyphs_ucs[input_position], inp, 3);
+    glyphs_utf8[input_position] = strdup(out);
+    input_position++;
 
-        /* Grab the keyboard to get all input */
-        xcb_flush(conn);
+    if (input_position == limit)
+        finish_input();
 
-        /* Try (repeatedly, if necessary) to grab the keyboard. We might not
-         * get the keyboard at the first attempt because of the keybinding
-         * still being active when started via a wm’s keybinding. */
-        xcb_grab_keyboard_cookie_t cookie;
-        xcb_grab_keyboard_reply_t *reply = NULL;
+    handle_expose(NULL, conn, NULL);
+    return 1;
+}
 
-        int count = 0;
-        while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) {
-                cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
-                reply = xcb_grab_keyboard_reply(conn, cookie, NULL);
-                usleep(1000);
+int main(int argc, char *argv[]) {
+    format = strdup("%s");
+    socket_path = getenv("I3SOCK");
+    char *pattern = sstrdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1");
+    int o, option_index = 0;
+
+    static struct option long_options[] = {
+        {"socket", required_argument, 0, 's'},
+        {"version", no_argument, 0, 'v'},
+        {"limit", required_argument, 0, 'l'},
+        {"prompt", required_argument, 0, 'P'},
+        {"prefix", required_argument, 0, 'p'},
+        {"format", required_argument, 0, 'F'},
+        {"font", required_argument, 0, 'f'},
+        {"help", no_argument, 0, 'h'},
+        {0, 0, 0, 0}
+    };
+
+    char *options_string = "s:p:P:f:l:F:vh";
+
+    while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
+        switch (o) {
+            case 's':
+                FREE(socket_path);
+                socket_path = strdup(optarg);
+                break;
+            case 'v':
+                printf("i3-input " I3_VERSION);
+                return 0;
+            case 'p':
+                /* This option is deprecated, but will still work in i3 v4.1, 4.2 and 4.3 */
+                fprintf(stderr, "i3-input: WARNING: the -p option is DEPRECATED in favor of the -F (format) option\n");
+                FREE(format);
+                sasprintf(&format, "%s%%s", optarg);
+                break;
+            case 'l':
+                limit = atoi(optarg);
+                break;
+            case 'P':
+                FREE(prompt);
+                prompt = strdup(optarg);
+                break;
+            case 'f':
+                FREE(pattern);
+                pattern = strdup(optarg);
+                break;
+            case 'F':
+                FREE(format);
+                format = strdup(optarg);
+                break;
+            case 'h':
+                printf("i3-input " I3_VERSION "\n");
+                printf("i3-input [-s <socket>] [-F <format>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]\n");
+                printf("\n");
+                printf("Example:\n");
+                printf("    i3-input -F 'workspace \"%%s\"' -P 'Switch to workspace: '\n");
+                return 0;
         }
-
-        if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
-                fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
-                exit(-1);
+    }
+
+    printf("using format \"%s\"\n", format);
+
+    if (socket_path == NULL)
+        socket_path = socket_path_from_x11();
+
+    if (socket_path == NULL)
+        socket_path = "/tmp/i3-ipc.sock";
+
+    sockfd = ipc_connect(socket_path);
+
+    if (prompt != NULL)
+        prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
+
+    int screens;
+    conn = xcb_connect(NULL, &screens);
+    if (!conn || xcb_connection_has_error(conn))
+        die("Cannot open display\n");
+
+    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
+    root = root_screen->root;
+
+    symbols = xcb_key_symbols_alloc(conn);
+
+    font = load_font(pattern, true);
+
+    /* Open an input window */
+    win = xcb_generate_id(conn);
+    xcb_create_window(
+        conn,
+        XCB_COPY_FROM_PARENT,
+        win, /* the window id */
+        root, /* parent == root */
+        50, 50, 500, font.height + 8, /* dimensions */
+        0, /* X11 border = 0, we draw our own */
+        XCB_WINDOW_CLASS_INPUT_OUTPUT,
+        XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+        XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
+        (uint32_t[]){
+            0, /* back pixel: black */
+            1, /* override redirect: don’t manage this window */
+            XCB_EVENT_MASK_EXPOSURE
+        });
+
+    /* Map the window (make it visible) */
+    xcb_map_window(conn, win);
+
+    /* Create pixmap */
+    pixmap = xcb_generate_id(conn);
+    pixmap_gc = xcb_generate_id(conn);
+    xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + 8);
+    xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
+
+    /* Set input focus (we have override_redirect=1, so the wm will not do
+     * this for us) */
+    xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
+
+    /* Create graphics context */
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
+
+    /* Grab the keyboard to get all input */
+    xcb_flush(conn);
+
+    /* Try (repeatedly, if necessary) to grab the keyboard. We might not
+     * get the keyboard at the first attempt because of the keybinding
+     * still being active when started via a wm’s keybinding. */
+    xcb_grab_keyboard_cookie_t cookie;
+    xcb_grab_keyboard_reply_t *reply = NULL;
+
+    int count = 0;
+    while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) {
+        cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+        reply = xcb_grab_keyboard_reply(conn, cookie, NULL);
+        usleep(1000);
+    }
+
+    if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
+        fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
+        exit(-1);
+    }
+
+    xcb_flush(conn);
+
+    xcb_generic_event_t *event;
+    while ((event = xcb_wait_for_event(conn)) != NULL) {
+        if (event->response_type == 0) {
+            fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
+            continue;
         }
 
-        xcb_flush(conn);
-
-        xcb_generic_event_t *event;
-        while ((event = xcb_wait_for_event(conn)) != NULL) {
-                if (event->response_type == 0) {
-                        fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
-                        continue;
-                }
+        /* Strip off the highest bit (set if the event is generated) */
+        int type = (event->response_type & 0x7F);
 
-                /* Strip off the highest bit (set if the event is generated) */
-                int type = (event->response_type & 0x7F);
+        switch (type) {
+            case XCB_KEY_PRESS:
+                handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
+                break;
 
-                switch (type) {
-                        case XCB_KEY_PRESS:
-                                handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
-                                break;
+            case XCB_KEY_RELEASE:
+                handle_key_release(NULL, conn, (xcb_key_release_event_t*)event);
+                break;
 
-                        case XCB_KEY_RELEASE:
-                                handle_key_release(NULL, conn, (xcb_key_release_event_t*)event);
-                                break;
-
-                        case XCB_EXPOSE:
-                                handle_expose(NULL, conn, (xcb_expose_event_t*)event);
-                                break;
-                }
-
-                free(event);
+            case XCB_EXPOSE:
+                handle_expose(NULL, conn, (xcb_expose_event_t*)event);
+                break;
         }
 
-        return 0;
+        free(event);
+    }
+
+    return 0;
 }
index 4557c9dab73199b270642a57ba724859ab08bf8b..df112eefe65fe472772e268d19b2dd55ccf86af0 100644 (file)
@@ -1,11 +1,11 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * ucs2_to_utf8.c: Converts between UCS-2 and UTF-8, both of which are used in
+ *                 different contexts in X11.
  *
  */
 #include <stdlib.h>
@@ -14,6 +14,8 @@
 #include <err.h>
 #include <iconv.h>
 
+#include "libi3.h"
+
 static iconv_t conversion_descriptor = 0;
 static iconv_t conversion_descriptor2 = 0;
 
@@ -23,37 +25,34 @@ static iconv_t conversion_descriptor2 = 0;
  *
  */
 char *convert_ucs_to_utf8(char *input) {
-       size_t input_size = 2;
-       /* UTF-8 may consume up to 4 byte */
-       int buffer_size = 8;
-
-       char *buffer = calloc(buffer_size, 1);
-        if (buffer == NULL)
-                err(EXIT_FAILURE, "malloc() failed\n");
-       size_t output_size = buffer_size;
-       /* We need to use an additional pointer, because iconv() modifies it */
-       char *output = buffer;
-
-       /* We convert the input into UCS-2 big endian */
-        if (conversion_descriptor == 0) {
-                conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
-                if (conversion_descriptor == 0) {
-                        fprintf(stderr, "error opening the conversion context\n");
-                        exit(1);
-                }
-        }
-
-       /* Get the conversion descriptor back to original state */
-       iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
-
-       /* Convert our text */
-       int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
-        if (rc == (size_t)-1) {
-                perror("Converting to UCS-2 failed");
-                return NULL;
-       }
-
-       return buffer;
+    size_t input_size = 2;
+    /* UTF-8 may consume up to 4 byte */
+    int buffer_size = 8;
+
+    char *buffer = scalloc(buffer_size);
+    size_t output_size = buffer_size;
+    /* We need to use an additional pointer, because iconv() modifies it */
+    char *output = buffer;
+
+    /* We convert the input into UCS-2 big endian */
+    if (conversion_descriptor == 0) {
+        conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
+        if (conversion_descriptor == 0)
+            errx(EXIT_FAILURE, "Error opening the conversion context");
+    }
+
+    /* Get the conversion descriptor back to original state */
+    iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
+
+    /* Convert our text */
+    int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
+    if (rc == (size_t)-1) {
+        free(buffer);
+        perror("Converting to UCS-2 failed");
+        return NULL;
+    }
+
+    return buffer;
 }
 
 /*
@@ -64,41 +63,38 @@ char *convert_ucs_to_utf8(char *input) {
  *
  */
 char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
-       size_t input_size = strlen(input) + 1;
-       /* UCS-2 consumes exactly two bytes for each glyph */
-       int buffer_size = input_size * 2;
-
-       char *buffer = malloc(buffer_size);
-        if (buffer == NULL)
-                err(EXIT_FAILURE, "malloc() failed\n");
-       size_t output_size = buffer_size;
-       /* We need to use an additional pointer, because iconv() modifies it */
-       char *output = buffer;
-
-       /* We convert the input into UCS-2 big endian */
-        if (conversion_descriptor2 == 0) {
-                conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
-                if (conversion_descriptor2 == 0) {
-                        fprintf(stderr, "error opening the conversion context\n");
-                        exit(1);
-                }
-        }
-
-       /* Get the conversion descriptor back to original state */
-       iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
-
-       /* Convert our text */
-       int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
-        if (rc == (size_t)-1) {
-                perror("Converting to UCS-2 failed");
-                if (real_strlen != NULL)
-                       *real_strlen = 0;
-                return NULL;
-       }
+    size_t input_size = strlen(input) + 1;
+    /* UCS-2 consumes exactly two bytes for each glyph */
+    int buffer_size = input_size * 2;
+
+    char *buffer = smalloc(buffer_size);
+    size_t output_size = buffer_size;
+    /* We need to use an additional pointer, because iconv() modifies it */
+    char *output = buffer;
+
+    /* We convert the input into UCS-2 big endian */
+    if (conversion_descriptor2 == 0) {
+        conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
+        if (conversion_descriptor2 == 0)
+            errx(EXIT_FAILURE, "Error opening the conversion context");
+    }
 
+    /* Get the conversion descriptor back to original state */
+    iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
+
+    /* Convert our text */
+    int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
+    if (rc == (size_t)-1) {
+        perror("Converting to UCS-2 failed");
+        free(buffer);
         if (real_strlen != NULL)
-               *real_strlen = ((buffer_size - output_size) / 2) - 1;
+            *real_strlen = 0;
+        return NULL;
+    }
+
+    if (real_strlen != NULL)
+        *real_strlen = ((buffer_size - output_size) / 2) - 1;
 
-       return buffer;
+    return buffer;
 }
 
diff --git a/i3-input/xcb.c b/i3-input/xcb.c
deleted file mode 100644 (file)
index 3c1d99e..0000000
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#include <xcb/xcb.h>
-#include <xcb/xcb_keysyms.h>
-
-#include <X11/keysym.h>
-
-#include "i3-input.h"
-
-/*
- * Convenience-wrapper around xcb_change_gc which saves us declaring a variable
- *
- */
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
-        xcb_change_gc(conn, gc, mask, &value);
-}
-
-/*
- * Returns the colorpixel to use for the given hex color (think of HTML).
- *
- * The hex_color has to start with #, for example #FF00FF.
- *
- * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
- * This has to be done by the caller.
- *
- */
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
-        char strgroups[3][3] = {{hex[1], hex[2], '\0'},
-                                {hex[3], hex[4], '\0'},
-                                {hex[5], hex[6], '\0'}};
-        uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
-                             (strtol(strgroups[1], NULL, 16)),
-                             (strtol(strgroups[2], NULL, 16))};
-
-        return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
-}
-
-/*
- * Returns the mask for Mode_switch (to be used for looking up keysymbols by
- * keycode).
- *
- */
-uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) {
-       xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(conn);
-
-       xcb_get_modifier_mapping_reply_t *modmap_r;
-       xcb_keycode_t *modmap, kc;
-       xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, keycode);
-       if (modeswitchcodes == NULL)
-               return 0;
-
-       modmap_r = xcb_get_modifier_mapping_reply(conn, xcb_get_modifier_mapping(conn), NULL);
-       modmap = xcb_get_modifier_mapping_keycodes(modmap_r);
-
-       for (int i = 0; i < 8; i++)
-               for (int j = 0; j < modmap_r->keycodes_per_modifier; j++) {
-                       kc = modmap[i * modmap_r->keycodes_per_modifier + j];
-                       for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) {
-                               if (*ktest != kc)
-                                       continue;
-
-                               free(modeswitchcodes);
-                               free(modmap_r);
-                               return (1 << i);
-                       }
-               }
-
-       return 0;
-}
-
-/*
- * Opens the window we use for input/output and maps it
- *
- */
-xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
-        xcb_window_t win = xcb_generate_id(conn);
-        //xcb_cursor_t cursor_id = xcb_generate_id(conn);
-
-#if 0
-        /* Use the default cursor (left pointer) */
-        if (cursor > -1) {
-                i3Font *cursor_font = load_font(conn, "cursor");
-                xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
-                                XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
-                                0, 0, 0, 65535, 65535, 65535);
-        }
-#endif
-
-        uint32_t mask = 0;
-        uint32_t values[3];
-
-        mask |= XCB_CW_BACK_PIXEL;
-        values[0] = 0;
-
-        mask |= XCB_CW_OVERRIDE_REDIRECT;
-        values[1] = 1;
-
-       mask |= XCB_CW_EVENT_MASK;
-       values[2] = XCB_EVENT_MASK_EXPOSURE;
-
-        xcb_create_window(conn,
-                          XCB_COPY_FROM_PARENT,
-                          win, /* the window id */
-                          root, /* parent == root */
-                          50, 50, width, height, /* dimensions */
-                          0, /* border = 0, we draw our own */
-                          XCB_WINDOW_CLASS_INPUT_OUTPUT,
-                          XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
-                          mask,
-                          values);
-
-#if 0
-        if (cursor > -1)
-                xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
-#endif
-
-        /* Map the window (= make it visible) */
-        xcb_map_window(conn, win);
-
-       return win;
-}
-
-/*
- * Returns the ID of the font matching the given pattern and stores the height
- * of the font (in pixels) in *font_height. die()s if no font matches.
- *
- */
-int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
-        xcb_void_cookie_t font_cookie;
-        xcb_list_fonts_with_info_cookie_t info_cookie;
-
-        /* Send all our requests first */
-        int result;
-        result = xcb_generate_id(conn);
-        font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern);
-        info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
-        xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
-        if (error != NULL) {
-                fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code);
-                exit(1);
-        }
-
-        /* Get information (height/name) for this font */
-        xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
-        if (reply == NULL)
-                die("Could not load font \"%s\"\n", pattern);
-
-        *font_height = reply->font_ascent + reply->font_descent;
-
-        return result;
-}
index 4dd4418f70862cfaf2c74f5a437f2b7c5a53725d..4f4d0134cd15715d47f2ab8c0ba0bdfc3e09f035 100755 (executable)
@@ -358,6 +358,8 @@ sub convert_command {
 # add an i3bar invocation automatically if no 'workspace_bar no' was found
 if ($workspace_bar) {
     print "\n";
-    print "# XXX: Automatically added a call to i3bar to provide a workspace bar\n";
-    print "exec i3status | i3bar -d\n";
+    print "# XXX: Automatically added a bar configuration\n";
+    print "bar {\n";
+    print "    status_command i3status\n";
+    print "}\n";
 }
index 1b7c1c04a9bb092660f958af71ab52f2eb306603..617df93279b774de32383538528183353cf391d0 100644 (file)
@@ -11,17 +11,17 @@ HEADERS=$(wildcard *.h)
 
 # Depend on the specific file (.c for each .o) and on all headers
 %.o: %.c ${HEADERS}
-       echo "CC $<"
+       echo "[i3-msg] CC $<"
        $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
 
 all: i3-msg
 
 i3-msg: ${FILES}
-       echo "LINK i3-msg"
+       echo "[i3-msg] LINK i3-msg"
        $(CC) $(LDFLAGS) -o i3-msg ${FILES} $(LIBS)
 
 install: all
-       echo "INSTALL"
+       echo "[i3-msg] INSTALL"
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
        $(INSTALL) -m 0755 i3-msg $(DESTDIR)$(PREFIX)/bin/
 
index 630a345d63c6ce57dc611b19464d8a5b6da0f8cf..6a3b29d3eb77bad86150d88fd8a0431055423396 100644 (file)
@@ -2,15 +2,15 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-msg/main.c: Utility which sends messages to a running i3-instance using
  * IPC via UNIX domain sockets.
  *
- * This serves as an example for how to send your own messages to i3.
+ * This (in combination with libi3/ipc_send_message.c and
+ * libi3/ipc_recv_message.c) serves as an example for how to send your own
+ * messages to i3.
+ *
  * Additionally, it’s even useful sometimes :-).
  *
  */
 #include <xcb/xcb.h>
 #include <xcb/xcb_aux.h>
 
+#include "libi3.h"
 #include <i3/ipc.h>
 
 static char *socket_path;
 
-/*
- * Try to get the socket path from X11 and return NULL if it doesn’t work.
- * As i3-msg is a short-running tool, we don’t bother with cleaning up the
- * connection and leave it up to the operating system on exit.
- *
- */
-static char *socket_path_from_x11() {
-    xcb_connection_t *conn;
-    int screen;
-    if ((conn = xcb_connect(NULL, &screen)) == NULL ||
-        xcb_connection_has_error(conn))
-        return NULL;
-    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
-    xcb_window_t root = root_screen->root;
-
-    xcb_intern_atom_cookie_t atom_cookie;
-    xcb_intern_atom_reply_t *atom_reply;
-
-    atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
-    atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
-    if (atom_reply == NULL)
-        return NULL;
-
-    xcb_get_property_cookie_t prop_cookie;
-    xcb_get_property_reply_t *prop_reply;
-    prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
-                                             XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
-    prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
-    if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
-        return NULL;
-    if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
-                 (char*)xcb_get_property_value(prop_reply)) == -1)
-        return NULL;
-    return socket_path;
-}
-
-/*
- * Formats a message (payload) of the given size and type and sends it to i3 via
- * the given socket file descriptor.
- *
- */
-static void ipc_send_message(int sockfd, uint32_t message_size,
-                             uint32_t message_type, uint8_t *payload) {
-    int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
-    char msg[buffer_size];
-    char *walk = msg;
-
-    strcpy(walk, I3_IPC_MAGIC);
-    walk += strlen(I3_IPC_MAGIC);
-    memcpy(walk, &message_size, sizeof(uint32_t));
-    walk += sizeof(uint32_t);
-    memcpy(walk, &message_type, sizeof(uint32_t));
-    walk += sizeof(uint32_t);
-    memcpy(walk, payload, message_size);
-
-    int sent_bytes = 0;
-    int bytes_to_go = buffer_size;
-    while (sent_bytes < bytes_to_go) {
-        int n = write(sockfd, msg + sent_bytes, bytes_to_go);
-        if (n == -1)
-            err(EXIT_FAILURE, "write() failed");
-
-        sent_bytes += n;
-        bytes_to_go -= n;
-    }
-}
-
-static void ipc_recv_message(int sockfd, uint32_t message_type,
-                             uint32_t *reply_length, uint8_t **reply) {
-    /* Read the message header first */
-    uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
-    char msg[to_read];
-    char *walk = msg;
-
-    uint32_t read_bytes = 0;
-    while (read_bytes < to_read) {
-        int n = read(sockfd, msg + read_bytes, to_read);
-        if (n == -1)
-            err(EXIT_FAILURE, "read() failed");
-        if (n == 0)
-            errx(EXIT_FAILURE, "received EOF instead of reply");
-
-        read_bytes += n;
-        to_read -= n;
-    }
-
-    if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0)
-        errx(EXIT_FAILURE, "invalid magic in reply");
-
-    walk += strlen(I3_IPC_MAGIC);
-    *reply_length = *((uint32_t*)walk);
-    walk += sizeof(uint32_t);
-    if (*((uint32_t*)walk) != message_type)
-        errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type);
-    walk += sizeof(uint32_t);
-
-    *reply = malloc(*reply_length);
-    if ((*reply) == NULL)
-        err(EXIT_FAILURE, "malloc() failed");
-
-    to_read = *reply_length;
-    read_bytes = 0;
-    while (read_bytes < to_read) {
-        int n = read(sockfd, *reply + read_bytes, to_read);
-        if (n == -1)
-            err(EXIT_FAILURE, "read() failed");
-
-        read_bytes += n;
-        to_read -= n;
-    }
-}
-
 int main(int argc, char *argv[]) {
     socket_path = getenv("I3SOCK");
     int o, option_index = 0;
@@ -170,7 +59,7 @@ int main(int argc, char *argv[]) {
         if (o == 's') {
             if (socket_path != NULL)
                 free(socket_path);
-            socket_path = strdup(optarg);
+            socket_path = sstrdup(optarg);
         } else if (o == 't') {
             if (strcasecmp(optarg, "command") == 0)
                 message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
@@ -180,9 +69,13 @@ int main(int argc, char *argv[]) {
                 message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS;
             else if (strcasecmp(optarg, "get_tree") == 0)
                 message_type = I3_IPC_MESSAGE_TYPE_GET_TREE;
+            else if (strcasecmp(optarg, "get_marks") == 0)
+                message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS;
+            else if (strcasecmp(optarg, "get_bar_config") == 0)
+                message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG;
             else {
                 printf("Unknown message type\n");
-                printf("Known types: command, get_workspaces, get_outputs, get_tree\n");
+                printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config\n");
                 exit(EXIT_FAILURE);
             }
         } else if (o == 'q') {
@@ -202,15 +95,14 @@ int main(int argc, char *argv[]) {
 
     /* Fall back to the default socket path */
     if (socket_path == NULL)
-        socket_path = strdup("/tmp/i3-ipc.sock");
+        socket_path = sstrdup("/tmp/i3-ipc.sock");
 
     /* Use all arguments, separated by whitespace, as payload.
      * This way, you don’t have to do i3-msg 'mark foo', you can use
      * i3-msg mark foo */
     while (optind < argc) {
         if (!payload) {
-            if (!(payload = strdup(argv[optind])))
-                err(EXIT_FAILURE, "strdup(argv[optind])");
+            payload = sstrdup(argv[optind]);
         } else {
             char *both;
             if (asprintf(&both, "%s %s", payload, argv[optind]) == -1)
@@ -235,15 +127,21 @@ int main(int argc, char *argv[]) {
     if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
         err(EXIT_FAILURE, "Could not connect to i3");
 
-    ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload);
+    if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload) == -1)
+        err(EXIT_FAILURE, "IPC: write()");
 
     if (quiet)
         return 0;
 
     uint32_t reply_length;
     uint8_t *reply;
-    ipc_recv_message(sockfd, message_type, &reply_length, &reply);
-    printf("%.*s", reply_length, reply);
+    int ret;
+    if ((ret = ipc_recv_message(sockfd, message_type, &reply_length, &reply)) != 0) {
+        if (ret == -1)
+            err(EXIT_FAILURE, "IPC: read()");
+        exit(1);
+    }
+    printf("%.*s\n", reply_length, reply);
     free(reply);
 
     close(sockfd);
index 933ae76cd89fa0a458f766dd11b0bb4fc2dc7db5..05a5b911c3f40224d13c33f61012b6f8fa0638ee 100644 (file)
@@ -3,23 +3,28 @@ TOPDIR=..
 
 include $(TOPDIR)/common.mk
 
+CPPFLAGS += -I$(TOPDIR)/include
+
 # Depend on the object files of all source-files in src/*.c and on all header files
 FILES=$(patsubst %.c,%.o,$(wildcard *.c))
 HEADERS=$(wildcard *.h)
 
 # Depend on the specific file (.c for each .o) and on all headers
 %.o: %.c ${HEADERS}
-       echo "CC $<"
+       echo "[i3-nagbar] CC $<"
        $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
 
 all: i3-nagbar
 
-i3-nagbar: ${FILES}
-       echo "LINK i3-nagbar"
-       $(CC) $(LDFLAGS) -o $@ ${FILES} $(LIBS)
+i3-nagbar: $(TOPDIR)/libi3/libi3.a ${FILES}
+       echo "[i3-nagbar] LINK i3-nagbar"
+       $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
+
+$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
+       $(MAKE) -C $(TOPDIR)/libi3
 
 install: all
-       echo "INSTALL"
+       echo "[i3-nagbar] INSTALL"
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
        $(INSTALL) -m 0755 i3-nagbar $(DESTDIR)$(PREFIX)/bin/
 
index 2fbe3cbb4cf68bed1784ba6e4173f8f8c2388121..5a21226bb532a8f9681bc328a7fd2342163a8c94 100644 (file)
@@ -18,9 +18,4 @@ while (0)
 
 extern xcb_window_t root;
 
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
-xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
-int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
-
 #endif
index d0d7e77a248281ed2699babeb66099f692b5ba84..4d4e253a496a0cdabdd1a18a7cb3077bdfe98ccf 100644 (file)
@@ -2,12 +2,10 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * i3-nagbar is a utility which displays a nag message.
+ * i3-nagbar is a utility which displays a nag message, for example in the case
+ * when the user has an error in his configuration file.
  *
  */
 #include <ev.h>
@@ -28,6 +26,7 @@
 #include <xcb/xcb_aux.h>
 #include <xcb/xcb_event.h>
 
+#include "libi3.h"
 #include "i3-nagbar.h"
 
 typedef struct {
@@ -41,11 +40,20 @@ static xcb_window_t win;
 static xcb_pixmap_t pixmap;
 static xcb_gcontext_t pixmap_gc;
 static xcb_rectangle_t rect = { 0, 0, 600, 20 };
-static int font_height;
-static char *prompt = "Please do not run this program.";
+static i3Font font;
+static char *prompt;
 static button_t *buttons;
 static int buttoncnt;
+
+/* Result of get_colorpixel() for the various colors. */
+static uint32_t color_background;        /* background of the bar */
+static uint32_t color_button_background; /* background for buttons */
+static uint32_t color_border;            /* color of the button border */
+static uint32_t color_border_bottom;     /* color of the bottom border */
+static uint32_t color_text;              /* color of the text */
+
 xcb_window_t root;
+xcb_connection_t *conn;
 
 /*
  * Starts the given application by passing it through a shell. We use double fork
@@ -118,32 +126,30 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
  *
  */
 static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
-    printf("expose!\n");
-
     /* re-draw the background */
-    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#900000"));
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ color_background });
     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
 
     /* restore font color */
     uint32_t values[3];
-    values[0] = get_colorpixel(conn, "#FFFFFF");
-    values[1] = get_colorpixel(conn, "#900000");
+    values[0] = color_text;
+    values[1] = color_background;
     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
     xcb_image_text_8(conn, strlen(prompt), pixmap, pixmap_gc, 4 + 4/* X */,
-                      font_height + 2 + 4 /* Y = baseline of font */, prompt);
+                      font.height + 2 + 4 /* Y = baseline of font */, prompt);
 
     /* render close button */
     int line_width = 4;
     int w = 20;
     int y = rect.width;
-    values[0] = get_colorpixel(conn, "#680a0a");
+    values[0] = color_button_background;
     values[1] = line_width;
     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
 
     xcb_rectangle_t close = { y - w - (2 * line_width), 0, w + (2 * line_width), rect.height };
     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
 
-    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424"));
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ color_border });
     xcb_point_t points[] = {
         { y - w - (2 * line_width), line_width / 2 },
         { y - (line_width / 2), line_width / 2 },
@@ -153,12 +159,12 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
     };
     xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points);
 
-    values[0] = get_colorpixel(conn, "#ffffff");
-    values[1] = get_colorpixel(conn, "#680a0a");
+    values[0] = color_text;
+    values[1] = color_button_background;
     values[2] = 1;
     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH, values);
     xcb_image_text_8(conn, strlen("x"), pixmap, pixmap_gc, y - w - line_width + (w / 2) - 4/* X */,
-                      font_height + 2 + 4 - 1/* Y = baseline of font */, "X");
+                      font.height + 2 + 4 - 1/* Y = baseline of font */, "X");
     y -= w;
 
     y -= 20;
@@ -167,13 +173,13 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
     line_width = 1;
     for (int c = 0; c < buttoncnt; c++) {
         /* TODO: make w = text extents of the label */
-        w = 90;
+        w = 100;
         y -= 30;
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#680a0a"));
+        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ color_button_background });
         close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 };
         xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
 
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424"));
+        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ color_border });
         buttons[c].x = y - w - (2 * line_width);
         buttons[c].width = w;
         xcb_point_t points2[] = {
@@ -185,18 +191,18 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
         };
         xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2);
 
-        values[0] = get_colorpixel(conn, "#ffffff");
-        values[1] = get_colorpixel(conn, "#680a0a");
+        values[0] = color_text;
+        values[1] = color_button_background;
         xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
         xcb_image_text_8(conn, strlen(buttons[c].label), pixmap, pixmap_gc, y - w - line_width + 6/* X */,
-                          font_height + 2 + 3/* Y = baseline of font */, buttons[c].label);
+                          font.height + 2 + 3/* Y = baseline of font */, buttons[c].label);
 
         y -= w;
     }
 
     /* border line at the bottom */
     line_width = 2;
-    values[0] = get_colorpixel(conn, "#470909");
+    values[0] = color_border_bottom;
     values[1] = line_width;
     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
     xcb_point_t bottom[] = {
@@ -214,8 +220,9 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
 }
 
 int main(int argc, char *argv[]) {
-    char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+    char *pattern = strdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1");
     int o, option_index = 0;
+    enum { TYPE_ERROR = 0, TYPE_WARNING = 1 } bar_type = TYPE_ERROR;
 
     static struct option long_options[] = {
         {"version", no_argument, 0, 'v'},
@@ -223,10 +230,13 @@ int main(int argc, char *argv[]) {
         {"button", required_argument, 0, 'b'},
         {"help", no_argument, 0, 'h'},
         {"message", no_argument, 0, 'm'},
+        {"type", required_argument, 0, 't'},
         {0, 0, 0, 0}
     };
 
-    char *options_string = "b:f:m:vh";
+    char *options_string = "b:f:m:t:vh";
+
+    prompt = strdup("Please do not run this program.");
 
     while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
         switch (o) {
@@ -238,8 +248,12 @@ int main(int argc, char *argv[]) {
                 pattern = strdup(optarg);
                 break;
             case 'm':
+                FREE(prompt);
                 prompt = strdup(optarg);
                 break;
+            case 't':
+                bar_type = (strcasecmp(optarg, "warning") == 0 ? TYPE_WARNING : TYPE_ERROR);
+                break;
             case 'h':
                 printf("i3-nagbar " I3_VERSION "\n");
                 printf("i3-nagbar [-m <message>] [-b <button> <action>] [-f <font>] [-v]\n");
@@ -260,7 +274,6 @@ int main(int argc, char *argv[]) {
     }
 
     int screens;
-    xcb_connection_t *conn;
     if ((conn = xcb_connect(NULL, &screens)) == NULL ||
         xcb_connection_has_error(conn))
         die("Cannot open display\n");
@@ -274,10 +287,47 @@ int main(int argc, char *argv[]) {
     xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
     root = root_screen->root;
 
-    uint32_t font_id = get_font_id(conn, pattern, &font_height);
+    if (bar_type == TYPE_ERROR) {
+        /* Red theme for error messages */
+        color_button_background = get_colorpixel("#680a0a");
+        color_background = get_colorpixel("#900000");
+        color_text = get_colorpixel("#ffffff");
+        color_border = get_colorpixel("#d92424");
+        color_border_bottom = get_colorpixel("#470909");
+    } else {
+        /* Yellowish theme for warnings */
+        color_button_background = get_colorpixel("#ffc100");
+        color_background = get_colorpixel("#ffa8000");
+        color_text = get_colorpixel("#000000");
+        color_border = get_colorpixel("#ab7100");
+        color_border_bottom = get_colorpixel("#ab7100");
+    }
+
+    font = load_font(pattern, true);
 
     /* Open an input window */
-    win = open_input_window(conn, 500, font_height + 8 + 8 /* 8px padding */);
+    win = xcb_generate_id(conn);
+
+    xcb_create_window(
+        conn,
+        XCB_COPY_FROM_PARENT,
+        win, /* the window id */
+        root, /* parent == root */
+        50, 50, 500, font.height + 8 + 8 /* 8 px padding */, /* dimensions */
+        0, /* x11 border = 0, we draw our own */
+        XCB_WINDOW_CLASS_INPUT_OUTPUT,
+        XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+        XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
+        (uint32_t[]){
+            0, /* back pixel: black */
+            XCB_EVENT_MASK_EXPOSURE |
+            XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+            XCB_EVENT_MASK_BUTTON_PRESS |
+            XCB_EVENT_MASK_BUTTON_RELEASE
+        });
+
+    /* Map the window (make it visible) */
+    xcb_map_window(conn, win);
 
     /* Setup NetWM atoms */
     #define xmacro(name) \
@@ -318,7 +368,7 @@ int main(int argc, char *argv[]) {
         uint32_t bottom_end_x;
     } __attribute__((__packed__)) strut_partial = {0,};
 
-    strut_partial.top = font_height + 6;
+    strut_partial.top = font.height + 6;
     strut_partial.top_start_x = 0;
     strut_partial.top_end_x = 800;
 
@@ -334,11 +384,11 @@ int main(int argc, char *argv[]) {
     /* Create pixmap */
     pixmap = xcb_generate_id(conn);
     pixmap_gc = xcb_generate_id(conn);
-    xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
+    xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + 8);
     xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
 
     /* Create graphics context */
-    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
 
     /* Grab the keyboard to get all input */
     xcb_flush(conn);
@@ -383,7 +433,7 @@ int main(int argc, char *argv[]) {
                 xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
 
                 /* Create graphics context */
-                xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+                xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
                 break;
             }
         }
diff --git a/i3-nagbar/xcb.c b/i3-nagbar/xcb.c
deleted file mode 100644 (file)
index ed1bfd8..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#include <xcb/xcb.h>
-#include <xcb/xcb_keysyms.h>
-
-#include <X11/keysym.h>
-
-#include "i3-nagbar.h"
-
-/*
- * Convenience-wrapper around xcb_change_gc which saves us declaring a variable
- *
- */
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
-        xcb_change_gc(conn, gc, mask, &value);
-}
-
-/*
- * Returns the colorpixel to use for the given hex color (think of HTML).
- *
- * The hex_color has to start with #, for example #FF00FF.
- *
- * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
- * This has to be done by the caller.
- *
- */
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
-        char strgroups[3][3] = {{hex[1], hex[2], '\0'},
-                                {hex[3], hex[4], '\0'},
-                                {hex[5], hex[6], '\0'}};
-        uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
-                             (strtol(strgroups[1], NULL, 16)),
-                             (strtol(strgroups[2], NULL, 16))};
-
-        return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
-}
-
-/*
- * Opens the window we use for input/output and maps it
- *
- */
-xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
-        xcb_window_t win = xcb_generate_id(conn);
-        //xcb_cursor_t cursor_id = xcb_generate_id(conn);
-
-#if 0
-        /* Use the default cursor (left pointer) */
-        if (cursor > -1) {
-                i3Font *cursor_font = load_font(conn, "cursor");
-                xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
-                                XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
-                                0, 0, 0, 65535, 65535, 65535);
-        }
-#endif
-
-        uint32_t mask = 0;
-        uint32_t values[3];
-
-        mask |= XCB_CW_BACK_PIXEL;
-        values[0] = 0;
-
-       mask |= XCB_CW_EVENT_MASK;
-       values[1] = XCB_EVENT_MASK_EXPOSURE |
-                XCB_EVENT_MASK_STRUCTURE_NOTIFY |
-                XCB_EVENT_MASK_BUTTON_PRESS |
-                XCB_EVENT_MASK_BUTTON_RELEASE;
-
-        xcb_create_window(conn,
-                          XCB_COPY_FROM_PARENT,
-                          win, /* the window id */
-                          root, /* parent == root */
-                          50, 50, width, height, /* dimensions */
-                          0, /* border = 0, we draw our own */
-                          XCB_WINDOW_CLASS_INPUT_OUTPUT,
-                          XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
-                          mask,
-                          values);
-
-#if 0
-        if (cursor > -1)
-                xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
-#endif
-
-        /* Map the window (= make it visible) */
-        xcb_map_window(conn, win);
-
-       return win;
-}
-
-/*
- * Returns the ID of the font matching the given pattern and stores the height
- * of the font (in pixels) in *font_height. die()s if no font matches.
- *
- */
-int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
-        xcb_void_cookie_t font_cookie;
-        xcb_list_fonts_with_info_cookie_t info_cookie;
-
-        /* Send all our requests first */
-        int result;
-        result = xcb_generate_id(conn);
-        font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern);
-        info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
-        xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
-        if (error != NULL) {
-                fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code);
-                exit(1);
-        }
-
-        /* Get information (height/name) for this font */
-        xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
-        if (reply == NULL)
-                die("Could not load font \"%s\"\n", pattern);
-
-        *font_height = reply->font_ascent + reply->font_descent;
-
-        return result;
-}
diff --git a/i3-sensible-editor b/i3-sensible-editor
new file mode 100755 (executable)
index 0000000..dffe00d
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+# This script tries to exec an editor by trying some known editors if $EDITOR is
+# not set.
+#
+# Distributions/packagers can enhance this script with a
+# distribution-specific mechanism to find the preferred pager.
+which $VISUAL >/dev/null && exec $VISUAL "$@"
+which $EDITOR >/dev/null && exec $EDITOR "$@"
+
+# Hopefully one of these is installed (no flamewars about preference please!):
+which nano >/dev/null && exec nano "$@"
+which vim >/dev/null && exec vim "$@"
+which vi >/dev/null && exec vi "$@"
+which emacs >/dev/null && exec emacs "$@"
diff --git a/i3-sensible-pager b/i3-sensible-pager
new file mode 100755 (executable)
index 0000000..5af8d6b
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+# This script tries to exec a pager by trying some known pagers if $PAGER is
+# not set.
+#
+# Distributions/packagers can enhance this script with a
+# distribution-specific mechanism to find the preferred pager.
+which $PAGER >/dev/null && exec $PAGER "$@"
+
+# Hopefully one of these is installed:
+which most >/dev/null && exec most "$@"
+which less >/dev/null && exec less "$@"
+# we don't use 'more' because it will exit if the file is 'too short'
+
+# If no pager is installed, try an editor
+exec i3-sensible-editor "$@"
diff --git a/i3-sensible-terminal b/i3-sensible-terminal
new file mode 100755 (executable)
index 0000000..28e6062
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+# This script tries to exec a terminal emulator by trying some known terminal
+# emulators.
+#
+# 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.
+# Please don't touch the first line, though:
+which $TERMINAL >/dev/null && exec $TERMINAL "$@"
+
+# Hopefully one of these is installed:
+which xterm >/dev/null && exec xterm "$@"
+which urxvt >/dev/null && exec urxvt "$@"
+which rxvt >/dev/null && exec rxvt "$@"
+which roxterm >/dev/null && exec roxterm "$@"
index 52c4a7e16044a3c267614715e83246b85e089a76..c5208c9b02b7d2a7ef3d54a2e2c42b40588332e6 100644 (file)
--- a/i3.config
+++ b/i3.config
@@ -16,7 +16,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 floating_modifier Mod1
 
 # start a terminal
-bindsym Mod1+Return exec urxvt
+bindsym Mod1+Return exec i3-sensible-terminal
 
 # kill focused window
 bindsym Mod1+Shift+q kill
@@ -147,7 +147,9 @@ bindsym Mod1+r mode "resize"
 
 # Start i3bar to display a workspace bar (plus the system information i3status
 # finds out, if available)
-exec i3status | i3bar -d
+bar {
+       status_command i3status
+}
 
 #######################################################################
 # automatically start i3-config-wizard to offer the user to create a
index 17e7483bb0abd78543f450ac300ce3e37435f861..185e9f041955e877cf3085ab6359593911499195 100644 (file)
@@ -17,7 +17,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 floating_modifier $mod
 
 # start a terminal
-bindcode $mod+36 exec urxvt
+bindcode $mod+36 exec i3-sensible-terminal
 
 # kill focused window
 bindcode $mod+Shift+24 kill
@@ -148,4 +148,6 @@ bindcode $mod+27 mode "resize"
 
 # Start i3bar to display a workspace bar (plus the system information i3status
 # finds out, if available)
-exec i3status | i3bar -d
+bar {
+       status_command i3status
+}
index 643065dfd76c09ffa6b666a3c9144593847c53c7..79d0e7cd4e0e58f43d97bb1db4064503a5785ad9 100644 (file)
@@ -10,30 +10,33 @@ CPPFLAGS += -I$(TOPDIR)/include
 
 all: i3bar doc
 
-i3bar: ${FILES}
-       echo "LINK"
-       $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+i3bar: $(TOPDIR)/libi3/libi3.a ${FILES}
+       echo "[i3bar] LINK"
+       $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
+
+$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
+       $(MAKE) -C $(TOPDIR)/libi3
 
 doc:
        echo ""
-       echo "SUBDIR doc"
+       echo "[i3bar] SUBDIR doc"
        $(MAKE) -C doc
 
 src/%.o: src/%.c ${HEADERS}
-       echo "CC $<"
+       echo "[i3bar] CC $<"
        $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
 
 install: all
-       echo "INSTALL"
+       echo "[i3bar] INSTALL"
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
        $(INSTALL) -m 0755 i3bar $(DESTDIR)$(PREFIX)/bin
 
 clean:
        rm -f src/*.o
-       make -C doc clean
+       $(MAKE) -C doc clean
 
 distclean: clean
        rm -f i3bar
-       make -C doc distclean
+       $(MAKE) -C doc distclean
 
 .PHONY: install clean distclean doc
index d28f6249d914f4b9ad1e28857cba1f38a9f01481..dcf3022bdd4eed7733502a006a4c95bb7d6ec863 100644 (file)
@@ -1,7 +1,7 @@
 i3bar(1)
 ========
 Axel Wagner <mail+i3bar@merovius.de>
-v0.7, July 2011
+v4.1, October 2011
 
 == NAME
 
@@ -9,81 +9,59 @@ i3bar - xcb-based status- and workspace-bar
 
 == SYNOPSIS
 
-*i3bar* [*-s* 'sock_path'] [*-c* 'command'] [*-m*|*-d*['pos']] [*-f* 'font'] [*-V*] [*-h*]
+*i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*]
 
-== OPTIONS
+== WARNING
 
-*-s, --socket* 'sock_path'::
-Specifies the 'socketpath', via which *i3bar* connects to *i3*(1). If *i3bar* can not connect to *i3*, it will exit. Defaults to '/tmp/i3-ipc.sock'
+i3bar will automatically be invoked by i3 for every 'bar' configuration block.
 
-*-c, --command* 'command'::
-Execute '<command>' to get 'stdin'. You can also simply pipe into 'stdin', but starting the coomand for itself, *i3bar* is able to send 'SIGCONT' and 'SIGSTOP', when combined with *-m*
+Starting it manually is usually not what you want to do.
 
-*-m, --hide*::
-Hide the bar, when 'mod4' is not pressed. With this, dockmode will not be set, and the bar is out of the way most of the time so you have more room.
-If *-c* is specified, the childprocess is sent a 'SIGSTOP' on hiding and a 'SIGCONT' on unhiding of the bars.
-This is the default behavior of i3bar.
+You have been warned!
 
-*-d*['pos']*, --dock*[*=*'pos']::
-Put i3bar in dockmode. This will reserve some space for it, so it does not overlap other clients.
-You can specify either *bottom* (default) or *top* as 'pos'.
+== OPTIONS
+
+*-s, --socket* 'sock_path'::
+Overwrites the path to the i3 IPC socket.
 
-*-f, --font* 'font'::
-Specifies a 'X-core-font' to use. You can choose one with *xfontsel*(1). Defaults to '+++-misc-fixed-medium-r-semicondensed--12-110-75-75-c-60-iso10646-1+++'.
+*-b, --bar_id* 'bar_id'::
+Specifies the bar ID for which to get the configuration from i3.
 
-*-V, --verbose*::
-Be (very) verbose with the debug-output. If not set, only errors are reported to 'stderr'
+*-v, --version*::
+Display version number and exit.
 
 *-h, --help*::
 Display a short help-message and exit
 
 == DESCRIPTION
 
-*i3bar* is an xcb- and libev-based status- and ws-bar. It is best thought of as an replacement for the *i3-wsbar*(1) + *dzen2*(1)-combination. It creates a workspace-bar for every active output ("screen") and displays a piped in statusline rightaligned on every bar.
-
-It does not sample any status-information itself, so you still need a program like *i3status*(1) or *conky*(1) for that.
-
-i3bar does not support any color or other markups, so stdin should be plain utf8, one line at a time. If you use *i3status*(1), you therefore should specify 'output_format = none' in the general section of its config file.
-
-Also, you should disable the internal workspace bar of *i3*(1), when using *i3bar* by specifying 'workspace_bar no' in your *i3*-configfile.
-
-== COLORS
+*i3bar* displays a bar at the bottom (or top) of your monitor(s) containing
+workspace switching buttons and a statusline generated by i3status(1) or
+similar. It is automatically invoked (and configured through) i3.
 
-*i3bar* does not yet support formatting in the displayed statusline. However it does support setting colors for the bar, the workspace-buttons and the statusline.
-
-For now this happens with the following command-line-options:
-
-*--color-bar-fg, --color-bar-bg, --color-active-ws-fg, --color-active-ws-bg, --color-inactive-ws-fg,  --color-inactive-ws-bg, --color-urgent-ws-bg, --color-urgent-ws-fg, --color-focus-ws-fg, --color-focus-ws-bg*
-
-For each specified option you need to give a HEX-colorcode.
-
-Be advised that this command-line-options are only temporary and are very likely to be removed, when we finally have a config-file.
+i3bar does not support any color or other markups, so stdin should be plain
+utf8, one line at a time. If you use *i3status*(1), you therefore should
+specify 'output_format = none' in the general section of its config file.
 
 == ENVIRONMENT
 
 === I3SOCK
 
-If no ipc-socket is specified on the commandline, this variable is used
-to determine the path, at wich the unix domain socket is expected, on which
-to connect to i3.
+Used as a fallback for the i3 IPC socket path if neither the commandline
+contains an argument nor the I3_SOCKET_PATH property is set on the X11 root
+window.
 
 == EXAMPLES
 
-To get a docked bar with some statusinformation, you use
-
-*i3status | i3bar --dock*
+Nothing to see here, move along. As stated above, you should not run i3bar manually.
 
-If you rather have it displayed at the top of the screen, you use
-
-*i3status | i3bar --dock=top*
-
-If you want it to hide when not needed, you should instead simply use
-
-*i3bar -c i3status*
+Instead, see the i3 documentation, especially the User’s Guide.
 
 == SEE ALSO
 
-+i3(1)+, +i3-wsbar(1)+, +dzen2(1)+, +i3status(1)+
++i3status(1)+ or +conky(1)+ for programs generating a statusline.
+
++dzen2(1)+ or +xmobar(1)+ for similar programs to i3bar.
 
 == AUTHORS
 
index 2622b3b0ed647ea9c7bc04b19f0f719221cc8175..ceb183362bc0c37d93b24523661f1e2f2e3c0b2b 100644 (file)
@@ -1,9 +1,10 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
+ * vim:ts=4:sw=4:expandtab
  *
- * © 2010-2011 Axel Wagner and contributors
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
- * See file LICNSE for license information
+ * child.c: Getting Input for the statusline
  *
  */
 #ifndef CHILD_H_
index 22e3ca4337d292aaa120650101e6becaa10f11b4..3b6967faf347cdace0c353c9b30680ac032eb82f 100644 (file)
@@ -1,26 +1,26 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
  *
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
  */
 #ifndef COMMON_H_
 #define COMMON_H_
 
+#include <stdbool.h>
+
 typedef struct rect_t rect;
-typedef int bool;
 
 struct ev_loop* main_loop;
 char            *statusline;
 char            *statusline_buffer;
 
 struct rect_t {
-       int     x;
-       int     y;
-       int     w;
-       int     h;
+    int x;
+    int y;
+    int w;
+    int h;
 };
 
 #include "queue.h"
@@ -29,8 +29,10 @@ struct rect_t {
 #include "outputs.h"
 #include "util.h"
 #include "workspaces.h"
+#include "trayclients.h"
 #include "xcb.h"
 #include "ucs2_to_utf8.h"
 #include "config.h"
+#include "libi3.h"
 
 #endif
index b3473917dc0fc625f03de43785a38d9b6b69661c..1015cc5f2c0fc66d81e0331bc7252bc7fa5282fe 100644 (file)
@@ -1,22 +1,49 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ *
+ * config.c: Parses the configuration (received from i3).
+ *
+ */
 #ifndef CONFIG_H_
 #define CONFIG_H_
 
 #include "common.h"
 
 typedef enum {
-    DOCKPOS_NONE = 0,
-    DOCKPOS_TOP,
-    DOCKPOS_BOT
-} dockpos_t;
+    POS_NONE = 0,
+    POS_TOP,
+    POS_BOT
+} position_t;
 
 typedef struct config_t {
     int          hide_on_modifier;
-    dockpos_t    dockpos;
+    position_t   position;
     int          verbose;
-    xcb_colors_t *colors;
+    struct xcb_color_strings_t colors;
     int          disable_ws;
+    char         *bar_id;
+    char         *command;
+    char         *fontname;
+    char         *tray_output;
+    int          num_outputs;
+    char         **outputs;
 } config_t;
 
 config_t config;
 
+/**
+ * Start parsing the received bar configuration json-string
+ *
+ */
+void parse_config_json(char *json);
+
+/**
+ * free()s the color strings as soon as they are not needed anymore.
+ *
+ */
+void free_colors(struct xcb_color_strings_t *colors);
+
 #endif
index 35f2d0f7cb1f8dff6098c281e9da7e15f4ab2757..a0c4970420a768ca4633689b7768d6b939219ecb 100644 (file)
@@ -1,9 +1,10 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
+ * vim:ts=4:sw=4:expandtab
  *
- * © 2010-2011 Axel Wagner and contributors
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
- * See file LICNSE for license information
+ * ipc.c: Communicating with i3
  *
  */
 #ifndef IPC_H_
index f74048da5d5f2604d09419e1ea75d7b4cfeb63a9..6501c318a534b8c407fb90ef4aa742530a314d77 100644 (file)
@@ -1,9 +1,10 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
+ * vim:ts=4:sw=4:expandtab
  *
- * © 2010-2011 Axel Wagner and contributors
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
- * See file LICNSE for license information
+ * outputs.c: Maintaining the output-list
  *
  */
 #ifndef OUTPUTS_H_
@@ -22,33 +23,34 @@ struct outputs_head *outputs;
  * Start parsing the received json-string
  *
  */
-void        parse_outputs_json(char* json);
+void parse_outputs_json(char* json);
 
 /*
  * Initiate the output-list
  *
  */
-void        init_outputs();
+void init_outputs();
 
 /*
  * Returns the output with the given name
  *
  */
-i3_output*  get_output_by_name(char* name);
+i3_output* get_output_by_name(char* name);
 
 struct i3_output {
-       char*           name;         /* Name of the output */
-       bool            active;       /* If the output is active */
-       int             ws;           /* The number of the currently visible ws */
-       rect            rect;         /* The rect (relative to the root-win) */
+    char*          name;          /* Name of the output */
+    bool           active;        /* If the output is active */
+    int            ws;            /* The number of the currently visible ws */
+    rect           rect;          /* The rect (relative to the root-win) */
 
-       xcb_window_t    bar;          /* The id of the bar of the output */
-    xcb_pixmap_t    buffer;       /* An extra pixmap for double-buffering */
-       xcb_gcontext_t  bargc;        /* The graphical context of the bar */
+    xcb_window_t   bar;           /* The id of the bar of the output */
+    xcb_pixmap_t   buffer;        /* An extra pixmap for double-buffering */
+    xcb_gcontext_t bargc;         /* The graphical context of the bar */
 
-       struct ws_head  *workspaces;  /* The workspaces on this output */
+    struct ws_head *workspaces;   /* The workspaces on this output */
+    struct tc_head *trayclients;  /* The tray clients on this output */
 
-       SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
+    SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
 };
 
 #endif
diff --git a/i3bar/include/queue.h b/i3bar/include/queue.h
deleted file mode 100644 (file)
index 75bb957..0000000
+++ /dev/null
@@ -1,527 +0,0 @@
-/*     $OpenBSD: queue.h,v 1.1 2007/10/26 03:14:08 niallo Exp $        */
-/*     $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $       */
-
-/*
- * Copyright (c) 1991, 1993
- *     The Regents of the University of California.  All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- *     @(#)queue.h     8.5 (Berkeley) 8/20/94
- */
-
-#ifndef        _SYS_QUEUE_H_
-#define        _SYS_QUEUE_H_
-
-/*
- * This file defines five types of data structures: singly-linked lists,
- * lists, simple queues, tail queues, and circular queues.
- *
- *
- * A singly-linked list is headed by a single forward pointer. The elements
- * are singly linked for minimum space and pointer manipulation overhead at
- * the expense of O(n) removal for arbitrary elements. New elements can be
- * added to the list after an existing element or at the head of the list.
- * Elements being removed from the head of the list should use the explicit
- * macro for this purpose for optimum efficiency. A singly-linked list may
- * only be traversed in the forward direction.  Singly-linked lists are ideal
- * for applications with large datasets and few or no removals or for
- * implementing a LIFO queue.
- *
- * A list is headed by a single forward pointer (or an array of forward
- * pointers for a hash table header). The elements are doubly linked
- * so that an arbitrary element can be removed without a need to
- * traverse the list. New elements can be added to the list before
- * or after an existing element or at the head of the list. A list
- * may only be traversed in the forward direction.
- *
- * A simple queue is headed by a pair of pointers, one the head of the
- * list and the other to the tail of the list. The elements are singly
- * linked to save space, so elements can only be removed from the
- * head of the list. New elements can be added to the list before or after
- * an existing element, at the head of the list, or at the end of the
- * list. A simple queue may only be traversed in the forward direction.
- *
- * A tail queue is headed by a pair of pointers, one to the head of the
- * list and the other to the tail of the list. The elements are doubly
- * linked so that an arbitrary element can be removed without a need to
- * traverse the list. New elements can be added to the list before or
- * after an existing element, at the head of the list, or at the end of
- * the list. A tail queue may be traversed in either direction.
- *
- * A circle queue is headed by a pair of pointers, one to the head of the
- * list and the other to the tail of the list. The elements are doubly
- * linked so that an arbitrary element can be removed without a need to
- * traverse the list. New elements can be added to the list before or after
- * an existing element, at the head of the list, or at the end of the list.
- * A circle queue may be traversed in either direction, but has a more
- * complex end of list detection.
- *
- * For details on the use of these macros, see the queue(3) manual page.
- */
-
-#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
-#define _Q_INVALIDATE(a) (a) = ((void *)-1)
-#else
-#define _Q_INVALIDATE(a)
-#endif
-
-/*
- * Singly-linked List definitions.
- */
-#define SLIST_HEAD(name, type)                                         \
-struct name {                                                          \
-       struct type *slh_first; /* first element */                     \
-}
-
-#define        SLIST_HEAD_INITIALIZER(head)                                    \
-       { NULL }
-
-#define SLIST_ENTRY(type)                                              \
-struct {                                                               \
-       struct type *sle_next;  /* next element */                      \
-}
-
-/*
- * Singly-linked List access methods.
- */
-#define        SLIST_FIRST(head)       ((head)->slh_first)
-#define        SLIST_END(head)         NULL
-#define        SLIST_EMPTY(head)       (SLIST_FIRST(head) == SLIST_END(head))
-#define        SLIST_NEXT(elm, field)  ((elm)->field.sle_next)
-
-#define        SLIST_FOREACH(var, head, field)                                 \
-       for((var) = SLIST_FIRST(head);                                  \
-           (var) != SLIST_END(head);                                   \
-           (var) = SLIST_NEXT(var, field))
-
-#define        SLIST_FOREACH_PREVPTR(var, varp, head, field)                   \
-       for ((varp) = &SLIST_FIRST((head));                             \
-           ((var) = *(varp)) != SLIST_END(head);                       \
-           (varp) = &SLIST_NEXT((var), field))
-
-/*
- * Singly-linked List functions.
- */
-#define        SLIST_INIT(head) {                                              \
-       SLIST_FIRST(head) = SLIST_END(head);                            \
-}
-
-#define        SLIST_INSERT_AFTER(slistelm, elm, field) do {                   \
-       (elm)->field.sle_next = (slistelm)->field.sle_next;             \
-       (slistelm)->field.sle_next = (elm);                             \
-} while (0)
-
-#define        SLIST_INSERT_HEAD(head, elm, field) do {                        \
-       (elm)->field.sle_next = (head)->slh_first;                      \
-       (head)->slh_first = (elm);                                      \
-} while (0)
-
-#define        SLIST_REMOVE_NEXT(head, elm, field) do {                        \
-       (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next;  \
-} while (0)
-
-#define        SLIST_REMOVE_HEAD(head, field) do {                             \
-       (head)->slh_first = (head)->slh_first->field.sle_next;          \
-} while (0)
-
-#define SLIST_REMOVE(head, elm, type, field) do {                      \
-       if ((head)->slh_first == (elm)) {                               \
-               SLIST_REMOVE_HEAD((head), field);                       \
-       } else {                                                        \
-               struct type *curelm = (head)->slh_first;                \
-                                                                       \
-               while (curelm->field.sle_next != (elm))                 \
-                       curelm = curelm->field.sle_next;                \
-               curelm->field.sle_next =                                \
-                   curelm->field.sle_next->field.sle_next;             \
-               _Q_INVALIDATE((elm)->field.sle_next);                   \
-       }                                                               \
-} while (0)
-
-/*
- * List definitions.
- */
-#define LIST_HEAD(name, type)                                          \
-struct name {                                                          \
-       struct type *lh_first;  /* first element */                     \
-}
-
-#define LIST_HEAD_INITIALIZER(head)                                    \
-       { NULL }
-
-#define LIST_ENTRY(type)                                               \
-struct {                                                               \
-       struct type *le_next;   /* next element */                      \
-       struct type **le_prev;  /* address of previous next element */  \
-}
-
-/*
- * List access methods
- */
-#define        LIST_FIRST(head)                ((head)->lh_first)
-#define        LIST_END(head)                  NULL
-#define        LIST_EMPTY(head)                (LIST_FIRST(head) == LIST_END(head))
-#define        LIST_NEXT(elm, field)           ((elm)->field.le_next)
-
-#define LIST_FOREACH(var, head, field)                                 \
-       for((var) = LIST_FIRST(head);                                   \
-           (var)!= LIST_END(head);                                     \
-           (var) = LIST_NEXT(var, field))
-
-/*
- * List functions.
- */
-#define        LIST_INIT(head) do {                                            \
-       LIST_FIRST(head) = LIST_END(head);                              \
-} while (0)
-
-#define LIST_INSERT_AFTER(listelm, elm, field) do {                    \
-       if (((elm)->field.le_next = (listelm)->field.le_next) != NULL)  \
-               (listelm)->field.le_next->field.le_prev =               \
-                   &(elm)->field.le_next;                              \
-       (listelm)->field.le_next = (elm);                               \
-       (elm)->field.le_prev = &(listelm)->field.le_next;               \
-} while (0)
-
-#define        LIST_INSERT_BEFORE(listelm, elm, field) do {                    \
-       (elm)->field.le_prev = (listelm)->field.le_prev;                \
-       (elm)->field.le_next = (listelm);                               \
-       *(listelm)->field.le_prev = (elm);                              \
-       (listelm)->field.le_prev = &(elm)->field.le_next;               \
-} while (0)
-
-#define LIST_INSERT_HEAD(head, elm, field) do {                                \
-       if (((elm)->field.le_next = (head)->lh_first) != NULL)          \
-               (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
-       (head)->lh_first = (elm);                                       \
-       (elm)->field.le_prev = &(head)->lh_first;                       \
-} while (0)
-
-#define LIST_REMOVE(elm, field) do {                                   \
-       if ((elm)->field.le_next != NULL)                               \
-               (elm)->field.le_next->field.le_prev =                   \
-                   (elm)->field.le_prev;                               \
-       *(elm)->field.le_prev = (elm)->field.le_next;                   \
-       _Q_INVALIDATE((elm)->field.le_prev);                            \
-       _Q_INVALIDATE((elm)->field.le_next);                            \
-} while (0)
-
-#define LIST_REPLACE(elm, elm2, field) do {                            \
-       if (((elm2)->field.le_next = (elm)->field.le_next) != NULL)     \
-               (elm2)->field.le_next->field.le_prev =                  \
-                   &(elm2)->field.le_next;                             \
-       (elm2)->field.le_prev = (elm)->field.le_prev;                   \
-       *(elm2)->field.le_prev = (elm2);                                \
-       _Q_INVALIDATE((elm)->field.le_prev);                            \
-       _Q_INVALIDATE((elm)->field.le_next);                            \
-} while (0)
-
-/*
- * Simple queue definitions.
- */
-#define SIMPLEQ_HEAD(name, type)                                       \
-struct name {                                                          \
-       struct type *sqh_first; /* first element */                     \
-       struct type **sqh_last; /* addr of last next element */         \
-}
-
-#define SIMPLEQ_HEAD_INITIALIZER(head)                                 \
-       { NULL, &(head).sqh_first }
-
-#define SIMPLEQ_ENTRY(type)                                            \
-struct {                                                               \
-       struct type *sqe_next;  /* next element */                      \
-}
-
-/*
- * Simple queue access methods.
- */
-#define        SIMPLEQ_FIRST(head)         ((head)->sqh_first)
-#define        SIMPLEQ_END(head)           NULL
-#define        SIMPLEQ_EMPTY(head)         (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
-#define        SIMPLEQ_NEXT(elm, field)    ((elm)->field.sqe_next)
-
-#define SIMPLEQ_FOREACH(var, head, field)                              \
-       for((var) = SIMPLEQ_FIRST(head);                                \
-           (var) != SIMPLEQ_END(head);                                 \
-           (var) = SIMPLEQ_NEXT(var, field))
-
-/*
- * Simple queue functions.
- */
-#define        SIMPLEQ_INIT(head) do {                                         \
-       (head)->sqh_first = NULL;                                       \
-       (head)->sqh_last = &(head)->sqh_first;                          \
-} while (0)
-
-#define SIMPLEQ_INSERT_HEAD(head, elm, field) do {                     \
-       if (((elm)->field.sqe_next = (head)->sqh_first) == NULL)        \
-               (head)->sqh_last = &(elm)->field.sqe_next;              \
-       (head)->sqh_first = (elm);                                      \
-} while (0)
-
-#define SIMPLEQ_INSERT_TAIL(head, elm, field) do {                     \
-       (elm)->field.sqe_next = NULL;                                   \
-       *(head)->sqh_last = (elm);                                      \
-       (head)->sqh_last = &(elm)->field.sqe_next;                      \
-} while (0)
-
-#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {           \
-       if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
-               (head)->sqh_last = &(elm)->field.sqe_next;              \
-       (listelm)->field.sqe_next = (elm);                              \
-} while (0)
-
-#define SIMPLEQ_REMOVE_HEAD(head, field) do {                  \
-       if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
-               (head)->sqh_last = &(head)->sqh_first;                  \
-} while (0)
-
-/*
- * Tail queue definitions.
- */
-#define TAILQ_HEAD(name, type)                                         \
-struct name {                                                          \
-       struct type *tqh_first; /* first element */                     \
-       struct type **tqh_last; /* addr of last next element */         \
-}
-
-#define TAILQ_HEAD_INITIALIZER(head)                                   \
-       { NULL, &(head).tqh_first }
-
-#define TAILQ_ENTRY(type)                                              \
-struct {                                                               \
-       struct type *tqe_next;  /* next element */                      \
-       struct type **tqe_prev; /* address of previous next element */  \
-}
-
-/*
- * tail queue access methods
- */
-#define        TAILQ_FIRST(head)               ((head)->tqh_first)
-#define        TAILQ_END(head)                 NULL
-#define        TAILQ_NEXT(elm, field)          ((elm)->field.tqe_next)
-#define TAILQ_LAST(head, headname)                                     \
-       (*(((struct headname *)((head)->tqh_last))->tqh_last))
-/* XXX */
-#define TAILQ_PREV(elm, headname, field)                               \
-       (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
-#define        TAILQ_EMPTY(head)                                               \
-       (TAILQ_FIRST(head) == TAILQ_END(head))
-
-#define TAILQ_FOREACH(var, head, field)                                        \
-       for((var) = TAILQ_FIRST(head);                                  \
-           (var) != TAILQ_END(head);                                   \
-           (var) = TAILQ_NEXT(var, field))
-
-#define TAILQ_FOREACH_REVERSE(var, head, headname, field)              \
-       for((var) = TAILQ_LAST(head, headname);                         \
-           (var) != TAILQ_END(head);                                   \
-           (var) = TAILQ_PREV(var, headname, field))
-
-/*
- * Tail queue functions.
- */
-#define        TAILQ_INIT(head) do {                                           \
-       (head)->tqh_first = NULL;                                       \
-       (head)->tqh_last = &(head)->tqh_first;                          \
-} while (0)
-
-#define TAILQ_INSERT_HEAD(head, elm, field) do {                       \
-       if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)        \
-               (head)->tqh_first->field.tqe_prev =                     \
-                   &(elm)->field.tqe_next;                             \
-       else                                                            \
-               (head)->tqh_last = &(elm)->field.tqe_next;              \
-       (head)->tqh_first = (elm);                                      \
-       (elm)->field.tqe_prev = &(head)->tqh_first;                     \
-} while (0)
-
-#define TAILQ_INSERT_TAIL(head, elm, field) do {                       \
-       (elm)->field.tqe_next = NULL;                                   \
-       (elm)->field.tqe_prev = (head)->tqh_last;                       \
-       *(head)->tqh_last = (elm);                                      \
-       (head)->tqh_last = &(elm)->field.tqe_next;                      \
-} while (0)
-
-#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {             \
-       if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
-               (elm)->field.tqe_next->field.tqe_prev =                 \
-                   &(elm)->field.tqe_next;                             \
-       else                                                            \
-               (head)->tqh_last = &(elm)->field.tqe_next;              \
-       (listelm)->field.tqe_next = (elm);                              \
-       (elm)->field.tqe_prev = &(listelm)->field.tqe_next;             \
-} while (0)
-
-#define        TAILQ_INSERT_BEFORE(listelm, elm, field) do {                   \
-       (elm)->field.tqe_prev = (listelm)->field.tqe_prev;              \
-       (elm)->field.tqe_next = (listelm);                              \
-       *(listelm)->field.tqe_prev = (elm);                             \
-       (listelm)->field.tqe_prev = &(elm)->field.tqe_next;             \
-} while (0)
-
-#define TAILQ_REMOVE(head, elm, field) do {                            \
-       if (((elm)->field.tqe_next) != NULL)                            \
-               (elm)->field.tqe_next->field.tqe_prev =                 \
-                   (elm)->field.tqe_prev;                              \
-       else                                                            \
-               (head)->tqh_last = (elm)->field.tqe_prev;               \
-       *(elm)->field.tqe_prev = (elm)->field.tqe_next;                 \
-       _Q_INVALIDATE((elm)->field.tqe_prev);                           \
-       _Q_INVALIDATE((elm)->field.tqe_next);                           \
-} while (0)
-
-#define TAILQ_REPLACE(head, elm, elm2, field) do {                     \
-       if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)   \
-               (elm2)->field.tqe_next->field.tqe_prev =                \
-                   &(elm2)->field.tqe_next;                            \
-       else                                                            \
-               (head)->tqh_last = &(elm2)->field.tqe_next;             \
-       (elm2)->field.tqe_prev = (elm)->field.tqe_prev;                 \
-       *(elm2)->field.tqe_prev = (elm2);                               \
-       _Q_INVALIDATE((elm)->field.tqe_prev);                           \
-       _Q_INVALIDATE((elm)->field.tqe_next);                           \
-} while (0)
-
-/*
- * Circular queue definitions.
- */
-#define CIRCLEQ_HEAD(name, type)                                       \
-struct name {                                                          \
-       struct type *cqh_first;         /* first element */             \
-       struct type *cqh_last;          /* last element */              \
-}
-
-#define CIRCLEQ_HEAD_INITIALIZER(head)                                 \
-       { CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
-
-#define CIRCLEQ_ENTRY(type)                                            \
-struct {                                                               \
-       struct type *cqe_next;          /* next element */              \
-       struct type *cqe_prev;          /* previous element */          \
-}
-
-/*
- * Circular queue access methods
- */
-#define        CIRCLEQ_FIRST(head)             ((head)->cqh_first)
-#define        CIRCLEQ_LAST(head)              ((head)->cqh_last)
-#define        CIRCLEQ_END(head)               ((void *)(head))
-#define        CIRCLEQ_NEXT(elm, field)        ((elm)->field.cqe_next)
-#define        CIRCLEQ_PREV(elm, field)        ((elm)->field.cqe_prev)
-#define        CIRCLEQ_EMPTY(head)                                             \
-       (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head))
-
-#define CIRCLEQ_FOREACH(var, head, field)                              \
-       for((var) = CIRCLEQ_FIRST(head);                                \
-           (var) != CIRCLEQ_END(head);                                 \
-           (var) = CIRCLEQ_NEXT(var, field))
-
-#define CIRCLEQ_FOREACH_REVERSE(var, head, field)                      \
-       for((var) = CIRCLEQ_LAST(head);                                 \
-           (var) != CIRCLEQ_END(head);                                 \
-           (var) = CIRCLEQ_PREV(var, field))
-
-/*
- * Circular queue functions.
- */
-#define        CIRCLEQ_INIT(head) do {                                         \
-       (head)->cqh_first = CIRCLEQ_END(head);                          \
-       (head)->cqh_last = CIRCLEQ_END(head);                           \
-} while (0)
-
-#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do {           \
-       (elm)->field.cqe_next = (listelm)->field.cqe_next;              \
-       (elm)->field.cqe_prev = (listelm);                              \
-       if ((listelm)->field.cqe_next == CIRCLEQ_END(head))             \
-               (head)->cqh_last = (elm);                               \
-       else                                                            \
-               (listelm)->field.cqe_next->field.cqe_prev = (elm);      \
-       (listelm)->field.cqe_next = (elm);                              \
-} while (0)
-
-#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do {          \
-       (elm)->field.cqe_next = (listelm);                              \
-       (elm)->field.cqe_prev = (listelm)->field.cqe_prev;              \
-       if ((listelm)->field.cqe_prev == CIRCLEQ_END(head))             \
-               (head)->cqh_first = (elm);                              \
-       else                                                            \
-               (listelm)->field.cqe_prev->field.cqe_next = (elm);      \
-       (listelm)->field.cqe_prev = (elm);                              \
-} while (0)
-
-#define CIRCLEQ_INSERT_HEAD(head, elm, field) do {                     \
-       (elm)->field.cqe_next = (head)->cqh_first;                      \
-       (elm)->field.cqe_prev = CIRCLEQ_END(head);                      \
-       if ((head)->cqh_last == CIRCLEQ_END(head))                      \
-               (head)->cqh_last = (elm);                               \
-       else                                                            \
-               (head)->cqh_first->field.cqe_prev = (elm);              \
-       (head)->cqh_first = (elm);                                      \
-} while (0)
-
-#define CIRCLEQ_INSERT_TAIL(head, elm, field) do {                     \
-       (elm)->field.cqe_next = CIRCLEQ_END(head);                      \
-       (elm)->field.cqe_prev = (head)->cqh_last;                       \
-       if ((head)->cqh_first == CIRCLEQ_END(head))                     \
-               (head)->cqh_first = (elm);                              \
-       else                                                            \
-               (head)->cqh_last->field.cqe_next = (elm);               \
-       (head)->cqh_last = (elm);                                       \
-} while (0)
-
-#define        CIRCLEQ_REMOVE(head, elm, field) do {                           \
-       if ((elm)->field.cqe_next == CIRCLEQ_END(head))                 \
-               (head)->cqh_last = (elm)->field.cqe_prev;               \
-       else                                                            \
-               (elm)->field.cqe_next->field.cqe_prev =                 \
-                   (elm)->field.cqe_prev;                              \
-       if ((elm)->field.cqe_prev == CIRCLEQ_END(head))                 \
-               (head)->cqh_first = (elm)->field.cqe_next;              \
-       else                                                            \
-               (elm)->field.cqe_prev->field.cqe_next =                 \
-                   (elm)->field.cqe_next;                              \
-       _Q_INVALIDATE((elm)->field.cqe_prev);                           \
-       _Q_INVALIDATE((elm)->field.cqe_next);                           \
-} while (0)
-
-#define CIRCLEQ_REPLACE(head, elm, elm2, field) do {                   \
-       if (((elm2)->field.cqe_next = (elm)->field.cqe_next) ==         \
-           CIRCLEQ_END(head))                                          \
-               (head)->cqh_last = (elm2);                              \
-       else                                                            \
-               (elm2)->field.cqe_next->field.cqe_prev = (elm2);        \
-       if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) ==         \
-           CIRCLEQ_END(head))                                          \
-               (head)->cqh_first = (elm2);                             \
-       else                                                            \
-               (elm2)->field.cqe_prev->field.cqe_next = (elm2);        \
-       _Q_INVALIDATE((elm)->field.cqe_prev);                           \
-       _Q_INVALIDATE((elm)->field.cqe_next);                           \
-} while (0)
-
-#endif /* !_SYS_QUEUE_H_ */
diff --git a/i3bar/include/trayclients.h b/i3bar/include/trayclients.h
new file mode 100644 (file)
index 0000000..e1e795f
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ *
+ */
+#ifndef TRAYCLIENT_H_
+#define TRAYCLIENT_H_
+
+#include "common.h"
+
+typedef struct trayclient trayclient;
+
+TAILQ_HEAD(tc_head, trayclient);
+
+struct trayclient {
+    xcb_window_t       win;         /* The window ID of the tray client */
+    bool               mapped;      /* Whether this window is mapped */
+    int                xe_version;  /* The XEMBED version supported by the client */
+
+    TAILQ_ENTRY(trayclient) tailq;  /* Pointer for the TAILQ-Macro */
+};
+
+#endif
index 2fb3862a16de8891fb5761fca978d0b9f8e9c228..a77ed20eaf056f0670c38e3745a839b1e2068bab 100644 (file)
@@ -1 +1,15 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * ucs2_to_utf8.c: Converts between UCS-2 and UTF-8, both of which are used in
+ *                 different contexts in X11.
+ */
+#ifndef _UCS2_TO_UTF8
+#define _UCS2_TO_UTF8
+
 char *convert_utf8_to_ucs2(char *input, int *real_strlen);
+
+#endif
index 6094932071ec299aed1af14f9fe29d8ec468e44a..eb05ed0828ed7dc236da3e022d364c6d43a03d86 100644 (file)
@@ -1,9 +1,8 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
+ * vim:ts=4:sw=4:expandtab
  *
- * © 2010-2011 Axel Wagner and contributors
- *
- * See file LICNSE for license information
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #ifndef UTIL_H_
index 6f5ca72b4cdd9b1bd230b2b9a0c76f84fe3be671..dfd93d9b2c18dfe14d197aeebe2a4c829d83a8af 100644 (file)
@@ -1,9 +1,10 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
+ * vim:ts=4:sw=4:expandtab
  *
- * © 2010-2011 Axel Wagner and contributors
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
- * See file LICNSE for license information
+ * workspaces.c: Maintaining the workspace-lists
  *
  */
 #ifndef WORKSPACES_H_
index 531fdfe942c76265e0e5b559528b5ccb5e733604..8067a19382919a24805a0018c5ac2ec35d741ec9 100644 (file)
@@ -1,9 +1,10 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
+ * vim:ts=4:sw=4:expandtab
  *
- * © 2010-2011 Axel Wagner and contributors
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
- * See file LICNSE for license information
+ * xcb.c: Communicating with X
  *
  */
 #ifndef XCB_H_
 #include <stdint.h>
 //#include "outputs.h"
 
+#ifdef XCB_COMPAT
+#define XCB_ATOM_CARDINAL CARDINAL
+#endif
+
+#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1
+#define SYSTEM_TRAY_REQUEST_DOCK    0
+#define SYSTEM_TRAY_BEGIN_MESSAGE   1
+#define SYSTEM_TRAY_CANCEL_MESSAGE  2
+#define XEMBED_MAPPED                   (1 << 0)
+#define XEMBED_EMBEDDED_NOTIFY         0
+
 struct xcb_color_strings_t {
     char *bar_fg;
     char *bar_bg;
@@ -28,10 +41,18 @@ struct xcb_color_strings_t {
 typedef struct xcb_colors_t xcb_colors_t;
 
 /*
- * Initialize xcb and use the specified fontname for text-rendering
+ * Early initialization of the connection to X11: Everything which does not
+ * depend on 'config'.
+ *
+ */
+char *init_xcb_early();
+
+/**
+ * Initialization which depends on 'config' being usable. Called after the
+ * configuration has arrived.
  *
  */
-char *init_xcb(char *fontname);
+void init_xcb_late(char *fontname);
 
 /*
  * Initialize the colors
index 5d1688731659a38240ed84b40075e0dd10197e25..b75ceabd538afa3a96aba1fb87fd1a23f06a6403 100644 (file)
@@ -2,4 +2,10 @@ ATOM_DO(_NET_WM_WINDOW_TYPE)
 ATOM_DO(_NET_WM_WINDOW_TYPE_DOCK)
 ATOM_DO(_NET_WM_STRUT_PARTIAL)
 ATOM_DO(I3_SOCKET_PATH)
+ATOM_DO(MANAGER)
+ATOM_DO(_NET_SYSTEM_TRAY_ORIENTATION)
+ATOM_DO(_NET_SYSTEM_TRAY_VISUAL)
+ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
+ATOM_DO(_XEMBED_INFO)
+ATOM_DO(_XEMBED)
 #undef ATOM_DO
index faab9142ee04d6985f120dc2bd469b8c6f9a2a9d..e294fb9abe6886424a3efb029ed8a7226e4575f9 100644 (file)
@@ -1,11 +1,10 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
  *
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
- * src/child.c: Getting Input for the statusline
+ * child.c: Getting Input for the statusline
  *
  */
 #include <stdlib.h>
@@ -17,6 +16,7 @@
 #include <fcntl.h>
 #include <string.h>
 #include <errno.h>
+#include <err.h>
 #include <ev.h>
 
 #include "common.h"
@@ -40,8 +40,8 @@ void cleanup() {
         ev_io_stop(main_loop, stdin_io);
         FREE(stdin_io);
         FREE(statusline_buffer);
-       /* statusline pointed to memory within statusline_buffer */
-       statusline = NULL;
+        /* statusline pointed to memory within statusline_buffer */
+        statusline = NULL;
     }
 
     if (child_sig != NULL) {
@@ -60,7 +60,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
     int n = 0;
     int rec = 0;
     int buffer_len = STDIN_CHUNK_SIZE;
-    char *buffer = malloc(buffer_len);
+    char *buffer = smalloc(buffer_len);
     buffer[0] = '\0';
     while(1) {
         n = read(fd, buffer + rec, buffer_len - rec);
@@ -89,8 +89,8 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
 
         if (rec == buffer_len) {
             buffer_len += STDIN_CHUNK_SIZE;
-            buffer = realloc(buffer, buffer_len);
-           }
+            buffer = srealloc(buffer, buffer_len);
+        }
     }
     if (*buffer == '\0') {
         FREE(buffer);
@@ -129,7 +129,9 @@ void start_child(char *command) {
     child_pid = 0;
     if (command != NULL) {
         int fd[2];
-        pipe(fd);
+        if (pipe(fd) == -1)
+            err(EXIT_FAILURE, "pipe(fd)");
+
         child_pid = fork();
         switch (child_pid) {
             case -1:
@@ -167,12 +169,12 @@ void start_child(char *command) {
     /* We set O_NONBLOCK because blocking is evil in event-driven software */
     fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
 
-    stdin_io = malloc(sizeof(ev_io));
+    stdin_io = smalloc(sizeof(ev_io));
     ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ);
     ev_io_start(main_loop, stdin_io);
 
     /* We must cleanup, if the child unexpectedly terminates */
-    child_sig = malloc(sizeof(ev_child));
+    child_sig = smalloc(sizeof(ev_child));
     ev_child_init(child_sig, &child_sig_cb, child_pid, 0);
     ev_child_start(main_loop, child_sig);
 
diff --git a/i3bar/src/config.c b/i3bar/src/config.c
new file mode 100644 (file)
index 0000000..5f3338a
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ *
+ * config.c: Parses the configuration (received from i3).
+ *
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <i3/ipc.h>
+#include <yajl/yajl_parse.h>
+#include <yajl/yajl_version.h>
+
+#include "common.h"
+
+static char *cur_key;
+
+/*
+ * Parse a key.
+ *
+ * Essentially we just save it in cur_key.
+ *
+ */
+#if YAJL_MAJOR >= 2
+static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
+#else
+static int config_map_key_cb(void *params_, const unsigned char *keyVal, unsigned keyLen) {
+#endif
+    FREE(cur_key);
+
+    cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1));
+    strncpy(cur_key, (const char*) keyVal, keyLen);
+    cur_key[keyLen] = '\0';
+
+    return 1;
+}
+
+/*
+ * Parse a null-value (current_workspace)
+ *
+ */
+static int config_null_cb(void *params_) {
+    if (!strcmp(cur_key, "id")) {
+        /* If 'id' is NULL, the bar config was not found. Error out. */
+        ELOG("No such bar config. Use 'i3-msg -t get_bar_config' to get the available configs.\n");
+        ELOG("Are you starting i3bar by hand? You should not:\n");
+        ELOG("Configure a 'bar' block in your i3 config and i3 will launch i3bar automatically.\n");
+        exit(EXIT_FAILURE);
+    }
+
+    return 1;
+}
+
+/*
+ * Parse a string
+ *
+ */
+#if YAJL_MAJOR >= 2
+static int config_string_cb(void *params_, const unsigned char *val, size_t _len) {
+#else
+static int config_string_cb(void *params_, const unsigned char *val, unsigned int _len) {
+#endif
+    int len = (int)_len;
+    /* The id is ignored, we already have it in config.bar_id */
+    if (!strcmp(cur_key, "id"))
+        return 1;
+
+    if (!strcmp(cur_key, "mode")) {
+        DLOG("mode = %.*s, len = %d\n", len, val, len);
+        config.hide_on_modifier = (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")));
+        return 1;
+    }
+
+    if (!strcmp(cur_key, "position")) {
+        DLOG("position = %.*s\n", len, val);
+        config.position = (len == 3 && !strncmp((const char*)val, "top", strlen("top")) ? POS_TOP : POS_BOT);
+        return 1;
+    }
+
+    if (!strcmp(cur_key, "status_command")) {
+        /* We cannot directly start the child here, because start_child() also
+         * needs to be run when no command was specified (to setup stdin).
+         * Therefore we save the command in 'config' and access it later in
+         * got_bar_config() */
+        DLOG("command = %.*s\n", len, val);
+        sasprintf(&config.command, "%.*s", len, val);
+        return 1;
+    }
+
+    if (!strcmp(cur_key, "font")) {
+        DLOG("font = %.*s\n", len, val);
+        sasprintf(&config.fontname, "%.*s", len, val);
+        return 1;
+    }
+
+    if (!strcmp(cur_key, "outputs")) {
+        DLOG("+output %.*s\n", len, val);
+        int new_num_outputs = config.num_outputs + 1;
+        config.outputs = srealloc(config.outputs, sizeof(char*) * new_num_outputs);
+        sasprintf(&config.outputs[config.num_outputs], "%.*s", len, val);
+        config.num_outputs = new_num_outputs;
+        return 1;
+    }
+
+    if (!strcmp(cur_key, "tray_output")) {
+        DLOG("tray_output %.*s\n", len, val);
+        FREE(config.tray_output);
+        sasprintf(&config.tray_output, "%.*s", len, val);
+        return 1;
+    }
+
+#define COLOR(json_name, struct_name) \
+    do { \
+        if (!strcmp(cur_key, #json_name)) { \
+            DLOG(#json_name " = " #struct_name " = %.*s\n", len, val); \
+            sasprintf(&(config.colors.struct_name), "%.*s", len, val); \
+            return 1; \
+        } \
+    } while (0)
+
+    COLOR(statusline, bar_fg);
+    COLOR(background, bar_bg);
+    COLOR(focused_workspace_text, focus_ws_fg);
+    COLOR(focused_workspace_bg, focus_ws_bg);
+    COLOR(active_workspace_text, active_ws_fg);
+    COLOR(active_workspace_bg, active_ws_bg);
+    COLOR(inactive_workspace_text, inactive_ws_fg);
+    COLOR(inactive_workspace_bg, inactive_ws_bg);
+    COLOR(urgent_workspace_text, urgent_ws_fg);
+    COLOR(urgent_workspace_bg, urgent_ws_bg);
+
+    printf("got unexpected string %.*s for cur_key = %s\n", len, val, cur_key);
+
+    return 0;
+}
+
+/*
+ * Parse a boolean value
+ *
+ */
+static int config_boolean_cb(void *params_, int val) {
+    if (!strcmp(cur_key, "workspace_buttons")) {
+        DLOG("workspace_buttons = %d\n", val);
+        config.disable_ws = !val;
+        return 1;
+    }
+
+    if (!strcmp(cur_key, "verbose")) {
+        DLOG("verbose = %d\n", val);
+        config.verbose = val;
+        return 1;
+    }
+
+    return 0;
+}
+
+/* A datastructure to pass all these callbacks to yajl */
+static yajl_callbacks outputs_callbacks = {
+    &config_null_cb,
+    &config_boolean_cb,
+    NULL,
+    NULL,
+    NULL,
+    &config_string_cb,
+    NULL,
+    &config_map_key_cb,
+    NULL,
+    NULL,
+    NULL
+};
+
+/*
+ * Start parsing the received bar configuration json-string
+ *
+ */
+void parse_config_json(char *json) {
+    yajl_handle handle;
+    yajl_status state;
+#if YAJL_MAJOR < 2
+    yajl_parser_config parse_conf = { 0, 0 };
+
+    handle = yajl_alloc(&outputs_callbacks, &parse_conf, NULL, NULL);
+#else
+    handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
+#endif
+
+    state = yajl_parse(handle, (const unsigned char*) json, strlen(json));
+
+    /* FIXME: Proper errorhandling for JSON-parsing */
+    switch (state) {
+        case yajl_status_ok:
+            break;
+        case yajl_status_client_canceled:
+#if YAJL_MAJOR < 2
+        case yajl_status_insufficient_data:
+#endif
+        case yajl_status_error:
+            ELOG("Could not parse config-reply!\n");
+            exit(EXIT_FAILURE);
+            break;
+    }
+
+    yajl_free(handle);
+}
+
+/*
+ * free()s the color strings as soon as they are not needed anymore.
+ *
+ */
+void free_colors(struct xcb_color_strings_t *colors) {
+#define FREE_COLOR(x) \
+    do { \
+        if (colors->x) \
+            free(colors->x); \
+    } while (0)
+    FREE_COLOR(bar_fg);
+    FREE_COLOR(bar_bg);
+    FREE_COLOR(active_ws_fg);
+    FREE_COLOR(active_ws_bg);
+    FREE_COLOR(inactive_ws_fg);
+    FREE_COLOR(inactive_ws_bg);
+    FREE_COLOR(urgent_ws_fg);
+    FREE_COLOR(urgent_ws_bg);
+    FREE_COLOR(focus_ws_fg);
+    FREE_COLOR(focus_ws_bg);
+#undef FREE_COLOR
+}
+
index 7769fdb18812d88667175156043794900b3afb5b..8f8174e569f4f26c210ac5873d4f6421f8ee04e2 100644 (file)
@@ -1,11 +1,10 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
  *
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
- * src/ipc.c: Communicating with i3
+ * ipc.c: Communicating with i3
  *
  */
 #include <stdlib.h>
 #include "common.h"
 
 ev_io      *i3_connection;
-ev_timer   *reconn = NULL;
 
 const char *sock_path;
 
 typedef void(*handler_t)(char*);
 
-/*
- * Retry to connect.
- *
- */
-void retry_connection(struct ev_loop *loop, ev_timer *w, int events) {
-    static int retries = 8;
-    if (init_connection(sock_path) == 0) {
-        if (retries == 0) {
-            ELOG("Retried 8 times - connection failed!\n");
-            exit(EXIT_FAILURE);
-        }
-        retries--;
-        return;
-    }
-    retries = 8;
-    ev_timer_stop(loop, w);
-    subscribe_events();
-
-    /* We get the current outputs and workspaces, to
-     * reconfigure all bars with the current configuration */
-    i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
-    if (!config.disable_ws) {
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
-    }
-}
-
-/*
- * Schedule a reconnect
- *
- */
-void reconnect() {
-    if (reconn == NULL) {
-        if ((reconn = malloc(sizeof(ev_timer))) == NULL) {
-            ELOG("malloc() failed: %s\n", strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-    } else {
-        ev_timer_stop(main_loop, reconn);
-    }
-    ev_timer_init(reconn, retry_connection, 0.25, 0.25);
-    ev_timer_start(main_loop, reconn);
-}
-
 /*
  * Called, when we get a reply to a command from i3.
  * Since i3 does not give us much feedback on commands, we do not much
@@ -112,12 +67,47 @@ void got_output_reply(char *reply) {
     reconfig_windows();
 }
 
+/*
+ * Called when we get the configuration for our bar instance
+ *
+ */
+void got_bar_config(char *reply) {
+    DLOG("Received bar config \"%s\"\n", reply);
+    /* We initiate the main-function by requesting infos about the outputs and
+     * workspaces. Everything else (creating the bars, showing the right workspace-
+     * buttons and more) is taken care of by the event-drivenness of the code */
+    i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
+    parse_config_json(reply);
+
+    /* Now we can actually use 'config', so let's subscribe to the appropriate
+     * events and request the workspaces if necessary. */
+    subscribe_events();
+    if (!config.disable_ws)
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
+
+    /* Initialize the rest of XCB */
+    init_xcb_late(config.fontname);
+
+    /* Resolve color strings to colorpixels and save them, then free the strings. */
+    init_colors(&(config.colors));
+    free_colors(&(config.colors));
+
+    /* The name of this function is actually misleading. Even if no command is
+     * specified, this function initiates the watchers to listen on stdin and
+     * react accordingly */
+    start_child(config.command);
+    FREE(config.command);
+}
+
 /* Data-structure to easily call the reply-handlers later */
 handler_t reply_handlers[] = {
     &got_command_reply,
     &got_workspace_reply,
     &got_subscribe_reply,
     &got_output_reply,
+    NULL,
+    NULL,
+    &got_bar_config,
 };
 
 /*
@@ -157,11 +147,7 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
 
     /* First we only read the header, because we know its length */
     uint32_t header_len = strlen(I3_IPC_MAGIC) + sizeof(uint32_t)*2;
-    char *header = malloc(header_len);
-    if (header == NULL) {
-        ELOG("Could not allocate memory: %s\n", strerror(errno));
-        exit(EXIT_FAILURE);
-    }
+    char *header = smalloc(header_len);
 
     /* We first parse the fixed-length IPC-header, to know, how much data
      * we have to expect */
@@ -173,12 +159,11 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
             exit(EXIT_FAILURE);
         }
         if (n == 0) {
-            /* EOF received. We try to recover a few times, because most likely
-             * i3 just restarted */
-            ELOG("EOF received, try to recover...\n");
-            destroy_connection();
-            reconnect();
-            return;
+            /* EOF received. Since i3 will restart i3bar instances as appropriate,
+             * we exit here. */
+            DLOG("EOF received, exiting...\n");
+            clean_xcb();
+            exit(EXIT_SUCCESS);
         }
         rec += n;
     }
@@ -200,15 +185,7 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
 
     /* Now that we know, what to expect, we can start read()ing the rest
      * of the message */
-    char *buffer = malloc(size + 1);
-    if (buffer == NULL) {
-        /* EOF received. We try to recover a few times, because most likely
-         * i3 just restarted */
-        ELOG("EOF received, try to recover...\n");
-        destroy_connection();
-        reconnect();
-        return;
-    }
+    char *buffer = smalloc(size + 1);
     rec = 0;
 
     while (rec < size) {
@@ -230,7 +207,8 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
         type ^= 1 << 31;
         event_handlers[type](buffer);
     } else {
-        reply_handlers[type](buffer);
+        if (reply_handlers[type])
+            reply_handlers[type](buffer);
     }
 
     FREE(header);
@@ -253,12 +231,7 @@ int i3_send_msg(uint32_t type, const char *payload) {
     /* TODO: I'm not entirely sure if this buffer really has to contain more
      * than the pure header (why not just write() the payload from *payload?),
      * but we leave it for now */
-    char *buffer = malloc(to_write);
-    if (buffer == NULL) {
-        ELOG("Could not allocate memory: %s\n", strerror(errno));
-        exit(EXIT_FAILURE);
-    }
-
+    char *buffer = smalloc(to_write);
     char *walk = buffer;
 
     strncpy(buffer, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC));
@@ -296,27 +269,8 @@ int i3_send_msg(uint32_t type, const char *payload) {
  */
 int init_connection(const char *socket_path) {
     sock_path = socket_path;
-    int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
-    if (sockfd == -1) {
-        ELOG("Could not create Socket: %s\n", strerror(errno));
-        exit(EXIT_FAILURE);
-    }
-
-    struct sockaddr_un addr;
-    memset(&addr, 0, sizeof(struct sockaddr_un));
-    addr.sun_family = AF_LOCAL;
-    strcpy(addr.sun_path, sock_path);
-    if (connect(sockfd, (const struct sockaddr*) &addr, sizeof(struct sockaddr_un)) < 0) {
-        ELOG("Could not connect to i3! %s: %s\n", sock_path, strerror(errno));
-        reconnect();
-        return 0;
-    }
-
-    i3_connection = malloc(sizeof(ev_io));
-    if (i3_connection == NULL) {
-        ELOG("malloc() failed: %s\n", strerror(errno));
-        exit(EXIT_FAILURE);
-    }
+    int sockfd = ipc_connect(socket_path);
+    i3_connection = smalloc(sizeof(ev_io));
     ev_io_init(i3_connection, &got_data, sockfd, EV_READ);
     ev_io_start(main_loop, i3_connection);
     return 1;
index 0def305754c767806ee80986b36197d8a3659cce..e648e00eca044535645dce78d1328777d1a1c42e 100644 (file)
@@ -1,9 +1,8 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
  *
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
  */
 #include <stdio.h>
@@ -28,70 +27,23 @@ char *expand_path(char *path) {
         ELOG("glob() failed\n");
         exit(EXIT_FAILURE);
     }
-    char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
-    if (result == NULL) {
-        ELOG("malloc() failed: %s\n", strerror(errno));
-        exit(EXIT_FAILURE);
-    }
+    char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
     globfree(&globbuf);
     return result;
 }
 
-static void read_color(char **color) {
-    int len = strlen(optarg);
-    if (len == 6 || (len == 7 && optarg[0] == '#')) {
-        int offset = len - 6;
-        int good = 1, i;
-        for (i = offset; good && i < 6 + offset; ++i) {
-            char c = optarg[i];
-            if (!(c >= 'a' && c <= 'f')
-                    && !(c >= 'A' && c <= 'F')
-                    && !(c >= '0' && c <= '9')) {
-                good = 0;
-                break;
-            }
-        }
-        if (good) {
-            *color = strdup(optarg + offset);
-            return;
-        }
-    }
-
-    fprintf(stderr, "Bad color value \"%s\"\n", optarg);
-    exit(EXIT_FAILURE);
-}
-
-static void free_colors(struct xcb_color_strings_t *colors) {
-#define FREE_COLOR(x) \
-    do { \
-        if (colors->x) \
-            free(colors->x); \
-    } while (0)
-    FREE_COLOR(bar_fg);
-    FREE_COLOR(bar_bg);
-    FREE_COLOR(active_ws_fg);
-    FREE_COLOR(active_ws_bg);
-    FREE_COLOR(inactive_ws_fg);
-    FREE_COLOR(inactive_ws_bg);
-    FREE_COLOR(urgent_ws_fg);
-    FREE_COLOR(urgent_ws_bg);
-    FREE_COLOR(focus_ws_fg);
-    FREE_COLOR(focus_ws_bg);
-#undef FREE_COLOR
-}
-
 void print_usage(char *elf_name) {
-    printf("Usage: %s [-s sock_path] [-c command] [-m|-d[pos]] [-f font] [-V] [-h]\n", elf_name);
+    printf("Usage: %s [-b bar_id] [-s sock_path] [-h] [-v]\n", elf_name);
+    printf("\n");
+    printf("--bar_id <bar_id>\tBar ID for which to get the configuration\n");
     printf("-s <sock_path>\tConnect to i3 via <sock_path>\n");
-    printf("-c <command>\tExecute <command> to get stdin\n");
-    printf("-m\t\tHide the bars, when mod4 is not pressed.\n");
-    printf("-d[<pos>]\tEnable dockmode. <pos> is \"top\" or \"bottom\". Default is bottom\n");
-    printf("\t\tIf -c is specified, the childprocess is sent a SIGSTOP on hiding,\n");
-    printf("\t\tand a SIGCONT on unhiding of the bars\n");
-    printf("-f <font>\tUse X-Core-Font <font> for display\n");
-    printf("-w\t\tDisable workspace-buttons\n");
-    printf("-V\t\tBe (very) verbose with the debug-output\n");
     printf("-h\t\tDisplay this help-message and exit\n");
+    printf("-v\t\tDisplay version number and exit\n");
+    printf("\n");
+    printf(" PLEASE NOTE that i3bar will be automatically started by i3\n"
+           " as soon as there is a 'bar' configuration block in your\n"
+           " config file. You should never need to start it manually.\n");
+    printf("\n");
 }
 
 /*
@@ -118,107 +70,33 @@ int main(int argc, char **argv) {
     int opt;
     int option_index = 0;
     char *socket_path = getenv("I3SOCK");
-    char *command = NULL;
-    char *fontname = NULL;
     char *i3_default_sock_path = "/tmp/i3-ipc.sock";
-    struct xcb_color_strings_t colors = { NULL, };
 
-    /* Definition of the standard-config */
-    config.hide_on_modifier = 0;
-    config.dockpos = DOCKPOS_NONE;
-    config.disable_ws = 0;
+    /* Initialize the standard config to use 0 as default */
+    memset(&config, '\0', sizeof(config_t));
 
     static struct option long_opt[] = {
         { "socket",               required_argument, 0, 's' },
-        { "command",              required_argument, 0, 'c' },
-        { "hide",                 no_argument,       0, 'm' },
-        { "dock",                 optional_argument, 0, 'd' },
-        { "font",                 required_argument, 0, 'f' },
-        { "nows",                 no_argument,       0, 'w' },
+        { "bar_id",               required_argument, 0, 0 },
         { "help",                 no_argument,       0, 'h' },
         { "version",              no_argument,       0, 'v' },
-        { "verbose",              no_argument,       0, 'V' },
-        { "color-bar-fg",         required_argument, 0, 'A' },
-        { "color-bar-bg",         required_argument, 0, 'B' },
-        { "color-active-ws-fg",   required_argument, 0, 'C' },
-        { "color-active-ws-bg",   required_argument, 0, 'D' },
-        { "color-inactive-ws-fg", required_argument, 0, 'E' },
-        { "color-inactive-ws-bg", required_argument, 0, 'F' },
-        { "color-urgent-ws-bg",   required_argument, 0, 'G' },
-        { "color-urgent-ws-fg",   required_argument, 0, 'H' },
-        { "color-focus-ws-bg",    required_argument, 0, 'I' },
-        { "color-focus-ws-fg",    required_argument, 0, 'J' },
         { NULL,                   0,                 0, 0}
     };
 
-    while ((opt = getopt_long(argc, argv, "s:c:d::mf:whvVA:B:C:D:E:F:G:H:I:J:", long_opt, &option_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, "s:hv", long_opt, &option_index)) != -1) {
         switch (opt) {
             case 's':
                 socket_path = expand_path(optarg);
                 break;
-            case 'c':
-                command = strdup(optarg);
-                break;
-            case 'm':
-                config.hide_on_modifier = 1;
-                break;
-            case 'd':
-                config.hide_on_modifier = 0;
-                if (optarg == NULL) {
-                    config.dockpos = DOCKPOS_BOT;
-                    break;
-                }
-                if (!strcmp(optarg, "top")) {
-                    config.dockpos = DOCKPOS_TOP;
-                } else if (!strcmp(optarg, "bottom")) {
-                    config.dockpos = DOCKPOS_BOT;
-                } else {
-                    print_usage(argv[0]);
-                    exit(EXIT_FAILURE);
-                }
-                break;
-            case 'f':
-                fontname = strdup(optarg);
-                break;
-            case 'w':
-                config.disable_ws = 1;
-                break;
             case 'v':
                 printf("i3bar version " I3_VERSION " © 2010-2011 Axel Wagner and contributors\n");
                 exit(EXIT_SUCCESS);
                 break;
-            case 'V':
-                config.verbose = 1;
-                break;
-            case 'A':
-                read_color(&colors.bar_fg);
-                break;
-            case 'B':
-                read_color(&colors.bar_bg);
-                break;
-            case 'C':
-                read_color(&colors.active_ws_fg);
-                break;
-            case 'D':
-                read_color(&colors.active_ws_bg);
-                break;
-            case 'E':
-                read_color(&colors.inactive_ws_fg);
-                break;
-            case 'F':
-                read_color(&colors.inactive_ws_bg);
-                break;
-            case 'G':
-                read_color(&colors.urgent_ws_bg);
-                break;
-            case 'H':
-                read_color(&colors.urgent_ws_fg);
-                break;
-            case 'I':
-                read_color(&colors.focus_ws_bg);
-                break;
-            case 'J':
-                read_color(&colors.focus_ws_fg);
+            case 0:
+                if (!strcmp(long_opt[option_index].name, "bar_id")) {
+                    FREE(config.bar_id);
+                    config.bar_id = sstrdup(optarg);
+                }
                 break;
             default:
                 print_usage(argv[0]);
@@ -227,26 +105,16 @@ int main(int argc, char **argv) {
         }
     }
 
-    if (fontname == NULL) {
-        /* This is a very restrictive default. More sensefull would be something like
-         * "-misc-*-*-*-*--*-*-*-*-*-*-*-*". But since that produces very ugly results
-         * on my machine, let's stick with this until we have a configfile */
-        fontname = "-misc-fixed-medium-r-semicondensed--12-110-75-75-c-60-iso10646-1";
-    }
-
-    if (config.dockpos != DOCKPOS_NONE) {
-        if (config.hide_on_modifier) {
-            ELOG("--dock and --hide are mutually exclusive!\n");
-            exit(EXIT_FAILURE);
-        }
-    } else {
-        config.hide_on_modifier = 1;
+    if (!config.bar_id) {
+        /* TODO: maybe we want -f which will automatically ask i3 for the first
+         * configured bar (and error out if there are too many)? */
+        ELOG("No bar_id passed. Please let i3 start i3bar or specify --bar_id\n");
+        exit(EXIT_FAILURE);
     }
 
     main_loop = ev_default_loop(0);
 
-    init_colors(&colors);
-    char *atom_sock_path = init_xcb(fontname);
+    char *atom_sock_path = init_xcb_early();
 
     if (socket_path == NULL) {
         socket_path = atom_sock_path;
@@ -257,38 +125,18 @@ int main(int argc, char **argv) {
         socket_path = expand_path(i3_default_sock_path);
     }
 
-    free_colors(&colors);
-
     init_outputs();
     if (init_connection(socket_path)) {
-        /* We subscribe to the i3-events we need */
-        subscribe_events();
-
-        /* We initiate the main-function by requesting infos about the outputs and
-         * workspaces. Everything else (creating the bars, showing the right workspace-
-         * buttons and more) is taken care of by the event-driveniness of the code */
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
-        if (!config.disable_ws) {
-            i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
-        }
+        /* Request the bar configuration. When it arrives, we fill the config array. */
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
     }
 
-    /* The name of this function is actually misleading. Even if no -c is specified,
-     * this function initiates the watchers to listen on stdin and react accordingly */
-    start_child(command);
-    FREE(command);
-
     /* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main-loop.
      * We only need those watchers on the stack, so putting them on the stack saves us
      * some calls to free() */
-    ev_signal *sig_term = malloc(sizeof(ev_signal));
-    ev_signal *sig_int = malloc(sizeof(ev_signal));
-    ev_signal *sig_hup = malloc(sizeof(ev_signal));
-
-    if (sig_term == NULL || sig_int == NULL || sig_hup == NULL) {
-        ELOG("malloc() failed: %s\n", strerror(errno));
-        exit(EXIT_FAILURE);
-    }
+    ev_signal *sig_term = smalloc(sizeof(ev_signal));
+    ev_signal *sig_int = smalloc(sizeof(ev_signal));
+    ev_signal *sig_hup = smalloc(sizeof(ev_signal));
 
     ev_signal_init(sig_term, &sig_cb, SIGTERM);
     ev_signal_init(sig_int, &sig_cb, SIGINT);
index 9daf328d0454463ff482e7bb94eccfdad08de694..9dc5cab8563ffd200816296b7c4851d155749168 100644 (file)
@@ -1,11 +1,10 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
  *
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
- * src/outputs.c: Maintaining the output-list
+ * outputs.c: Maintaining the output-list
  *
  */
 #include <string.h>
@@ -24,7 +23,7 @@ struct outputs_json_params {
     i3_output           *outputs_walk;
     char                *cur_key;
     char                *json;
-    bool                init;
+    bool                in_rect;
 };
 
 /*
@@ -43,7 +42,7 @@ static int outputs_null_cb(void *params_) {
  * Parse a boolean value (active)
  *
  */
-static int outputs_boolean_cb(void *params_, bool val) {
+static int outputs_boolean_cb(void *params_, int val) {
     struct outputs_json_params *params = (struct outputs_json_params*) params_;
 
     if (strcmp(params->cur_key, "active")) {
@@ -113,7 +112,7 @@ static int outputs_string_cb(void *params_, const unsigned char *val, unsigned i
     struct outputs_json_params *params = (struct outputs_json_params*) params_;
 
     if (!strcmp(params->cur_key, "current_workspace")) {
-        char *copy = malloc(sizeof(const unsigned char) * (len + 1));
+        char *copy = smalloc(sizeof(const unsigned char) * (len + 1));
         strncpy(copy, (const char*) val, len);
         copy[len] = '\0';
 
@@ -132,7 +131,7 @@ static int outputs_string_cb(void *params_, const unsigned char *val, unsigned i
         return 0;
     }
 
-    char *name = malloc(sizeof(const unsigned char) * (len + 1));
+    char *name = smalloc(sizeof(const unsigned char) * (len + 1));
     strncpy(name, (const char*) val, len);
     name[len] = '\0';
 
@@ -152,20 +151,27 @@ static int outputs_start_map_cb(void *params_) {
     i3_output *new_output = NULL;
 
     if (params->cur_key == NULL) {
-        new_output = malloc(sizeof(i3_output));
+        new_output = smalloc(sizeof(i3_output));
         new_output->name = NULL;
         new_output->ws = 0,
         memset(&new_output->rect, 0, sizeof(rect));
         new_output->bar = XCB_NONE;
 
-        new_output->workspaces = malloc(sizeof(struct ws_head));
+        new_output->workspaces = smalloc(sizeof(struct ws_head));
         TAILQ_INIT(new_output->workspaces);
 
+        new_output->trayclients = smalloc(sizeof(struct tc_head));
+        TAILQ_INIT(new_output->trayclients);
+
         params->outputs_walk = new_output;
 
         return 1;
     }
 
+    if (!strcmp(params->cur_key, "rect")) {
+        params->in_rect = true;
+    }
+
     return 1;
 }
 
@@ -175,7 +181,33 @@ static int outputs_start_map_cb(void *params_) {
  */
 static int outputs_end_map_cb(void *params_) {
     struct outputs_json_params *params = (struct outputs_json_params*) params_;
-    /* FIXME: What is at the end of a rect? */
+    if (params->in_rect) {
+        params->in_rect = false;
+        /* Ignore the end of a rect */
+        return 1;
+    }
+
+    /* See if we actually handle that output */
+    if (config.num_outputs > 0) {
+        bool handle_output = false;
+        for (int c = 0; c < config.num_outputs; c++) {
+            if (strcasecmp(params->outputs_walk->name, config.outputs[c]) != 0)
+                continue;
+
+            handle_output = true;
+            break;
+        }
+        if (!handle_output) {
+            DLOG("Ignoring output \"%s\", not configured to handle it.\n",
+                 params->outputs_walk->name);
+            FREE(params->outputs_walk->name);
+            FREE(params->outputs_walk->workspaces);
+            FREE(params->outputs_walk->trayclients);
+            FREE(params->outputs_walk);
+            FREE(params->cur_key);
+            return 1;
+        }
+    }
 
     i3_output *target = get_output_by_name(params->outputs_walk->name);
 
@@ -203,7 +235,7 @@ static int outputs_map_key_cb(void *params_, const unsigned char *keyVal, unsign
     struct outputs_json_params *params = (struct outputs_json_params*) params_;
     FREE(params->cur_key);
 
-    params->cur_key = malloc(sizeof(unsigned char) * (keyLen + 1));
+    params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1));
     strncpy(params->cur_key, (const char*) keyVal, keyLen);
     params->cur_key[keyLen] = '\0';
 
@@ -230,7 +262,7 @@ yajl_callbacks outputs_callbacks = {
  *
  */
 void init_outputs() {
-    outputs = malloc(sizeof(struct outputs_head));
+    outputs = smalloc(sizeof(struct outputs_head));
     SLIST_INIT(outputs);
 }
 
@@ -244,6 +276,7 @@ void parse_outputs_json(char *json) {
     params.outputs_walk = NULL;
     params.cur_key = NULL;
     params.json = json;
+    params.in_rect = false;
 
     yajl_handle handle;
     yajl_status state;
index 8c79c3f9659b5e23be249b0fd5e3c184e1a139e7..642a72fb3d838812ba94fe27bc7ed94b26954493 100644 (file)
@@ -1,12 +1,11 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
+ * ucs2_to_utf8.c: Converts between UCS-2 and UTF-8, both of which are used in
+ *                 different contexts in X11.
  */
 #include <stdlib.h>
 #include <stdio.h>
@@ -14,6 +13,8 @@
 #include <err.h>
 #include <iconv.h>
 
+#include "libi3.h"
+
 static iconv_t conversion_descriptor = 0;
 static iconv_t conversion_descriptor2 = 0;
 
@@ -23,37 +24,36 @@ static iconv_t conversion_descriptor2 = 0;
  *
  */
 char *convert_ucs_to_utf8(char *input) {
-       size_t input_size = 2;
-       /* UTF-8 may consume up to 4 byte */
-       int buffer_size = 8;
+    size_t input_size = 2;
+    /* UTF-8 may consume up to 4 byte */
+    int buffer_size = 8;
 
-       char *buffer = calloc(buffer_size, 1);
-        if (buffer == NULL)
-                err(EXIT_FAILURE, "malloc() failed\n");
-       size_t output_size = buffer_size;
-       /* We need to use an additional pointer, because iconv() modifies it */
-       char *output = buffer;
+    char *buffer = scalloc(buffer_size);
+    size_t output_size = buffer_size;
+    /* We need to use an additional pointer, because iconv() modifies it */
+    char *output = buffer;
 
-       /* We convert the input into UCS-2 big endian */
+    /* We convert the input into UCS-2 big endian */
+    if (conversion_descriptor == 0) {
+        conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
         if (conversion_descriptor == 0) {
-                conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
-                if (conversion_descriptor == 0) {
-                        fprintf(stderr, "error opening the conversion context\n");
-                        exit(1);
-                }
+            fprintf(stderr, "error opening the conversion context\n");
+            exit(1);
         }
+    }
 
-       /* Get the conversion descriptor back to original state */
-       iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
+    /* Get the conversion descriptor back to original state */
+    iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
 
-       /* Convert our text */
-       int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
-        if (rc == (size_t)-1) {
-                perror("Converting to UCS-2 failed");
-                return NULL;
-       }
+    /* Convert our text */
+    int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
+    if (rc == (size_t)-1) {
+        perror("Converting to UCS-2 failed");
+        free(buffer);
+        return NULL;
+    }
 
-       return buffer;
+    return buffer;
 }
 
 /*
@@ -64,40 +64,39 @@ char *convert_ucs_to_utf8(char *input) {
  *
  */
 char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
-       size_t input_size = strlen(input) + 1;
-       /* UCS-2 consumes exactly two bytes for each glyph */
-       int buffer_size = input_size * 2;
+    size_t input_size = strlen(input) + 1;
+    /* UCS-2 consumes exactly two bytes for each glyph */
+    int buffer_size = input_size * 2;
 
-       char *buffer = malloc(buffer_size);
-        if (buffer == NULL)
-                err(EXIT_FAILURE, "malloc() failed\n");
-       size_t output_size = buffer_size;
-       /* We need to use an additional pointer, because iconv() modifies it */
-       char *output = buffer;
+    char *buffer = smalloc(buffer_size);
+    size_t output_size = buffer_size;
+    /* We need to use an additional pointer, because iconv() modifies it */
+    char *output = buffer;
 
-       /* We convert the input into UCS-2 big endian */
+    /* We convert the input into UCS-2 big endian */
+    if (conversion_descriptor2 == 0) {
+        conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
         if (conversion_descriptor2 == 0) {
-                conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
-                if (conversion_descriptor2 == 0) {
-                        fprintf(stderr, "error opening the conversion context\n");
-                        exit(1);
-                }
+            fprintf(stderr, "error opening the conversion context\n");
+            exit(1);
         }
+    }
 
-       /* Get the conversion descriptor back to original state */
-       iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
-
-       /* Convert our text */
-       int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
-        if (rc == (size_t)-1) {
-                perror("Converting to UCS-2 failed");
-                if (real_strlen != NULL)
-                       *real_strlen = 0;
-                return NULL;
-       }
+    /* Get the conversion descriptor back to original state */
+    iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
 
+    /* Convert our text */
+    int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
+    if (rc == (size_t)-1) {
+        perror("Converting to UCS-2 failed");
+        free(buffer);
         if (real_strlen != NULL)
-               *real_strlen = ((buffer_size - output_size) / 2) - 1;
+            *real_strlen = 0;
+        return NULL;
+    }
+
+    if (real_strlen != NULL)
+        *real_strlen = ((buffer_size - output_size) / 2) - 1;
 
-       return buffer;
+    return buffer;
 }
index eeb9ca349f459d47a7b0ad9c5eacfa39a5b4c831..7cfbeffd4e3e4ba912250611392bc7b5260021af 100644 (file)
@@ -1,11 +1,10 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
  *
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
- * src/workspaces.c: Maintaining the workspace-lists
+ * workspaces.c: Maintaining the workspace-lists
  *
  */
 #include <string.h>
@@ -29,7 +28,7 @@ struct workspaces_json_params {
  * Parse a boolean value (visible, focused, urgent)
  *
  */
-static int workspaces_boolean_cb(void *params_, bool val) {
+static int workspaces_boolean_cb(void *params_, int val) {
     struct workspaces_json_params *params = (struct workspaces_json_params*) params_;
 
     if (!strcmp(params->cur_key, "visible")) {
@@ -115,7 +114,7 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, unsigne
 
         if (!strcmp(params->cur_key, "name")) {
             /* Save the name */
-            params->workspaces_walk->name = malloc(sizeof(const unsigned char) * (len + 1));
+            params->workspaces_walk->name = smalloc(sizeof(const unsigned char) * (len + 1));
             strncpy(params->workspaces_walk->name, (const char*) val, len);
             params->workspaces_walk->name[len] = '\0';
 
@@ -139,14 +138,17 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, unsigne
 
         if (!strcmp(params->cur_key, "output")) {
             /* We add the ws to the TAILQ of the output, it belongs to */
-            output_name = malloc(sizeof(const unsigned char) * (len + 1));
+            output_name = smalloc(sizeof(const unsigned char) * (len + 1));
             strncpy(output_name, (const char*) val, len);
             output_name[len] = '\0';
-            params->workspaces_walk->output = get_output_by_name(output_name);
+            i3_output *target = get_output_by_name(output_name);
+            if (target) {
+                params->workspaces_walk->output = target;
 
-            TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces,
-                              params->workspaces_walk,
-                              tailq);
+                TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces,
+                                  params->workspaces_walk,
+                                  tailq);
+            }
 
             FREE(output_name);
             return 1;
@@ -165,7 +167,7 @@ static int workspaces_start_map_cb(void *params_) {
     i3_ws *new_workspace = NULL;
 
     if (params->cur_key == NULL) {
-        new_workspace = malloc(sizeof(i3_ws));
+        new_workspace = smalloc(sizeof(i3_ws));
         new_workspace->num = -1;
         new_workspace->name = NULL;
         new_workspace->visible = 0;
@@ -195,11 +197,7 @@ static int workspaces_map_key_cb(void *params_, const unsigned char *keyVal, uns
     struct workspaces_json_params *params = (struct workspaces_json_params*) params_;
     FREE(params->cur_key);
 
-    params->cur_key = malloc(sizeof(unsigned char) * (keyLen + 1));
-    if (params->cur_key == NULL) {
-        ELOG("Could not allocate memory: %s\n", strerror(errno));
-        exit(EXIT_FAILURE);
-    }
+    params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1));
     strncpy(params->cur_key, (const char*) keyVal, keyLen);
     params->cur_key[keyLen] = '\0';
 
index ac48ea5a2e871858e43145b3c1cc489da26baaca..29ffe1c41cfe96e0d9fa7a28ebee2f81f00ac00c 100644 (file)
@@ -1,16 +1,20 @@
 /*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
  *
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
  *
- * src/xcb.c: Communicating with X
+ * xcb.c: Communicating with X
  *
  */
 #include <xcb/xcb.h>
 #include <xcb/xproto.h>
 #include <xcb/xcb_atom.h>
+
+#ifdef XCB_COMPAT
+#include "xcb_compat.h"
+#endif
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <ev.h>
 #include <errno.h>
 #include <limits.h>
+#include <err.h>
 
 #include <X11/Xlib.h>
 #include <X11/XKBlib.h>
 #include <X11/extensions/XKB.h>
 
 #include "common.h"
-
-#if defined(__APPLE__)
-
-/*
- * Taken from FreeBSD
- * Returns a pointer to a new string which is a duplicate of the
- * string, but only copies at most n characters.
- *
- */
-char *strndup(const char *str, size_t n) {
-    size_t len;
-    char *copy;
-
-    for (len = 0; len < n && str[len]; len++)
-        continue;
-
-    if ((copy = malloc(len + 1)) == NULL)
-        return (NULL);
-    memcpy(copy, str, len);
-    copy[len] = '\0';
-    return (copy);
-}
-
-#endif
+#include "libi3.h"
 
 /* We save the Atoms in an easy to access array, indexed by an enum */
 enum {
@@ -63,6 +45,7 @@ xcb_atom_t               atoms[NUM_ATOMS];
 
 /* Variables, that are the same for all functions at all times */
 xcb_connection_t *xcb_connection;
+int              screen;
 xcb_screen_t     *xcb_screen;
 xcb_window_t     xcb_root;
 xcb_font_t       xcb_font;
@@ -185,21 +168,6 @@ void draw_text(xcb_drawable_t drawable, xcb_gcontext_t ctx, int16_t x, int16_t y
     }
 }
 
-/*
- * Converts a colorstring to a colorpixel as expected from xcb_change_gc.
- * s is assumed to be in the format "rrggbb"
- *
- */
-uint32_t get_colorpixel(const char *s) {
-    char strings[3][3] = { { s[0], s[1], '\0'} ,
-                           { s[2], s[3], '\0'} ,
-                           { s[4], s[5], '\0'} };
-    uint8_t r = strtol(strings[0], NULL, 16);
-    uint8_t g = strtol(strings[1], NULL, 16);
-    uint8_t b = strtol(strings[2], NULL, 16);
-    return (r << 16 | g << 8 | b);
-}
-
 /*
  * Redraws the statusline to the buffer
  *
@@ -272,7 +240,9 @@ void unhide_bars() {
                XCB_CONFIG_WINDOW_HEIGHT |
                XCB_CONFIG_WINDOW_STACK_MODE;
         values[0] = walk->rect.x;
-        values[1] = walk->rect.y + walk->rect.h - font_height - 6;
+        if (config.position == POS_TOP)
+            values[1] = walk->rect.y;
+        else values[1] = walk->rect.y + walk->rect.h - font_height - 6;
         values[2] = walk->rect.w;
         values[3] = font_height + 6;
         values[4] = XCB_STACK_MODE_ABOVE;
@@ -298,16 +268,16 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
     do { \
         colors.name = get_colorpixel(new_colors->name ? new_colors->name : def); \
     } while  (0)
-    PARSE_COLOR(bar_fg, "FFFFFF");
-    PARSE_COLOR(bar_bg, "000000");
-    PARSE_COLOR(active_ws_fg, "FFFFFF");
-    PARSE_COLOR(active_ws_bg, "480000");
-    PARSE_COLOR(inactive_ws_fg, "FFFFFF");
-    PARSE_COLOR(inactive_ws_bg, "240000");
-    PARSE_COLOR(urgent_ws_fg, "FFFFFF");
-    PARSE_COLOR(urgent_ws_bg, "002400");
-    PARSE_COLOR(focus_ws_fg, "FFFFFF");
-    PARSE_COLOR(focus_ws_bg, "480000");
+    PARSE_COLOR(bar_fg, "#FFFFFF");
+    PARSE_COLOR(bar_bg, "#000000");
+    PARSE_COLOR(active_ws_fg, "#FFFFFF");
+    PARSE_COLOR(active_ws_bg, "#333333");
+    PARSE_COLOR(inactive_ws_fg, "#888888");
+    PARSE_COLOR(inactive_ws_bg, "#222222");
+    PARSE_COLOR(urgent_ws_fg, "#FFFFFF");
+    PARSE_COLOR(urgent_ws_bg, "#900000");
+    PARSE_COLOR(focus_ws_fg, "#FFFFFF");
+    PARSE_COLOR(focus_ws_bg, "#285577");
 #undef PARSE_COLOR
 }
 
@@ -389,6 +359,310 @@ void handle_button(xcb_button_press_event_t *event) {
     i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer);
 }
 
+/*
+ * Configures the x coordinate of all trayclients. To be called after adding a
+ * new tray client or removing an old one.
+ *
+ */
+static void configure_trayclients() {
+    trayclient *trayclient;
+    i3_output *output;
+    SLIST_FOREACH(output, outputs, slist) {
+        if (!output->active)
+            continue;
+
+        int clients = 0;
+        TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
+            if (!trayclient->mapped)
+                continue;
+            clients++;
+
+            DLOG("Configuring tray window %08x to x=%d\n",
+                 trayclient->win, output->rect.w - (clients * (font_height + 2)));
+            uint32_t x = output->rect.w - (clients * (font_height + 2));
+            xcb_configure_window(xcb_connection,
+                                 trayclient->win,
+                                 XCB_CONFIG_WINDOW_X,
+                                 &x);
+        }
+    }
+}
+
+/*
+ * Handles ClientMessages (messages sent from another client directly to us).
+ *
+ * At the moment, only the tray window will receive client messages. All
+ * supported client messages currently are _NET_SYSTEM_TRAY_OPCODE.
+ *
+ */
+static void handle_client_message(xcb_client_message_event_t* event) {
+    if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
+        event->format == 32) {
+        DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
+        /* event->data.data32[0] is the timestamp */
+        uint32_t op = event->data.data32[1];
+        uint32_t mask;
+        uint32_t values[2];
+        if (op == SYSTEM_TRAY_REQUEST_DOCK) {
+            xcb_window_t client = event->data.data32[2];
+
+            /* Listen for PropertyNotify events to get the most recent value of
+             * the XEMBED_MAPPED atom, also listen for UnmapNotify events */
+            mask = XCB_CW_EVENT_MASK;
+            values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
+                        XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+            xcb_change_window_attributes(xcb_connection,
+                                         client,
+                                         mask,
+                                         values);
+
+            /* Request the _XEMBED_INFO property. The XEMBED specification
+             * (which is referred by the tray specification) says this *has* to
+             * be set, but VLC does not set it… */
+            bool map_it = true;
+            int xe_version = 1;
+            xcb_get_property_cookie_t xembedc;
+            xembedc = xcb_get_property_unchecked(xcb_connection,
+                                                 0,
+                                                 client,
+                                                 atoms[_XEMBED_INFO],
+                                                 XCB_GET_PROPERTY_TYPE_ANY,
+                                                 0,
+                                                 2 * 32);
+
+            xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection,
+                                                                       xembedc,
+                                                                       NULL);
+            if (xembedr != NULL && xembedr->length != 0) {
+                DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length);
+                uint32_t *xembed = xcb_get_property_value(xembedr);
+                DLOG("xembed version = %d\n", xembed[0]);
+                DLOG("xembed flags = %d\n", xembed[1]);
+                map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
+                xe_version = xembed[0];
+                if (xe_version > 1)
+                    xe_version = 1;
+                free(xembedr);
+            } else {
+                ELOG("Window %08x violates the XEMBED protocol, _XEMBED_INFO not set\n", client);
+            }
+
+            DLOG("X window %08x requested docking\n", client);
+            i3_output *walk, *output = NULL;
+            SLIST_FOREACH(walk, outputs, slist) {
+                if (!walk->active)
+                    continue;
+                if (config.tray_output &&
+                    strcasecmp(walk->name, config.tray_output) != 0)
+                    continue;
+                DLOG("using output %s\n", walk->name);
+                output = walk;
+            }
+            if (output == NULL) {
+                ELOG("No output found\n");
+                return;
+            }
+            xcb_reparent_window(xcb_connection,
+                                client,
+                                output->bar,
+                                output->rect.w - font_height - 2,
+                                2);
+            /* We reconfigure the window to use a reasonable size. The systray
+             * specification explicitly says:
+             *   Tray icons may be assigned any size by the system tray, and
+             *   should do their best to cope with any size effectively
+             */
+            mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
+            values[0] = font_height;
+            values[1] = font_height;
+            xcb_configure_window(xcb_connection,
+                                 client,
+                                 mask,
+                                 values);
+
+            /* send the XEMBED_EMBEDDED_NOTIFY message */
+            void *event = scalloc(32);
+            xcb_client_message_event_t *ev = event;
+            ev->response_type = XCB_CLIENT_MESSAGE;
+            ev->window = client;
+            ev->type = atoms[_XEMBED];
+            ev->format = 32;
+            ev->data.data32[0] = XCB_CURRENT_TIME;
+            ev->data.data32[1] = atoms[XEMBED_EMBEDDED_NOTIFY];
+            ev->data.data32[2] = output->bar;
+            ev->data.data32[3] = xe_version;
+            xcb_send_event(xcb_connection,
+                           0,
+                           client,
+                           XCB_EVENT_MASK_NO_EVENT,
+                           (char*)ev);
+            free(event);
+
+            /* Put the client inside the save set. Upon termination (whether
+             * killed or normal exit does not matter) of i3bar, these clients
+             * will be correctly reparented to their most closest living
+             * ancestor. Without this, tray icons might die when i3bar
+             * exits/crashes. */
+            xcb_change_save_set(xcb_connection, XCB_SET_MODE_INSERT, client);
+
+            if (map_it) {
+                DLOG("Mapping dock client\n");
+                xcb_map_window(xcb_connection, client);
+            } else {
+                DLOG("Not mapping dock client yet\n");
+            }
+            trayclient *tc = smalloc(sizeof(trayclient));
+            tc->win = client;
+            tc->mapped = map_it;
+            tc->xe_version = xe_version;
+            TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
+
+            /* Trigger an update to copy the statusline text to the appropriate
+             * position */
+            configure_trayclients();
+            draw_bars();
+        }
+    }
+}
+
+/*
+ * Handles UnmapNotify events. These events happen when a tray window unmaps
+ * itself. We then update our data structure
+ *
+ */
+static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
+    DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event);
+
+    i3_output *walk;
+    SLIST_FOREACH(walk, outputs, slist) {
+        if (!walk->active)
+            continue;
+        DLOG("checking output %s\n", walk->name);
+        trayclient *trayclient;
+        TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
+            if (trayclient->win != event->window)
+                continue;
+
+            DLOG("Removing tray client with window ID %08x\n", event->window);
+            TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
+
+            /* Trigger an update, we now have more space for the statusline */
+            configure_trayclients();
+            draw_bars();
+            return;
+        }
+    }
+}
+
+/*
+ * Handle PropertyNotify messages. Currently only the _XEMBED_INFO property is
+ * handled, which tells us whether a dock client should be mapped or unmapped.
+ *
+ */
+static void handle_property_notify(xcb_property_notify_event_t *event) {
+    DLOG("PropertyNotify\n");
+    if (event->atom == atoms[_XEMBED_INFO] &&
+        event->state == XCB_PROPERTY_NEW_VALUE) {
+        DLOG("xembed_info updated\n");
+        trayclient *trayclient = NULL, *walk;
+        i3_output *o_walk;
+        SLIST_FOREACH(o_walk, outputs, slist) {
+            if (!o_walk->active)
+                continue;
+
+            TAILQ_FOREACH(walk, o_walk->trayclients, tailq) {
+                if (walk->win != event->window)
+                    continue;
+                trayclient = walk;
+                break;
+            }
+
+            if (trayclient)
+                break;
+        }
+        if (!trayclient) {
+            ELOG("PropertyNotify received for unknown window %08x\n",
+                 event->window);
+            return;
+        }
+        xcb_get_property_cookie_t xembedc;
+        xembedc = xcb_get_property_unchecked(xcb_connection,
+                                             0,
+                                             trayclient->win,
+                                             atoms[_XEMBED_INFO],
+                                             XCB_GET_PROPERTY_TYPE_ANY,
+                                             0,
+                                             2 * 32);
+
+        xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection,
+                                                                   xembedc,
+                                                                   NULL);
+        if (xembedr == NULL || xembedr->length == 0) {
+            DLOG("xembed_info unset\n");
+            return;
+        }
+
+        DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length);
+        uint32_t *xembed = xcb_get_property_value(xembedr);
+        DLOG("xembed version = %d\n", xembed[0]);
+        DLOG("xembed flags = %d\n", xembed[1]);
+        bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
+        DLOG("map-state now %d\n", map_it);
+        if (trayclient->mapped && !map_it) {
+            /* need to unmap the window */
+            xcb_unmap_window(xcb_connection, trayclient->win);
+            trayclient->mapped = map_it;
+            configure_trayclients();
+            draw_bars();
+        } 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();
+        }
+        free(xembedr);
+    }
+}
+
+/*
+ * Handle ConfigureRequests by denying them and sending the client a
+ * ConfigureNotify with its actual size.
+ *
+ */
+static void handle_configure_request(xcb_configure_request_event_t *event) {
+    DLOG("ConfigureRequest for window = %08x\n", event->window);
+
+    trayclient *trayclient;
+    i3_output *output;
+    SLIST_FOREACH(output, outputs, slist) {
+        if (!output->active)
+            continue;
+
+        int clients = 0;
+        TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
+            if (!trayclient->mapped)
+                continue;
+            clients++;
+
+            if (trayclient->win != event->window)
+                continue;
+
+            xcb_rectangle_t rect;
+            rect.x = output->rect.w - (clients * (font_height + 2));
+            rect.y = 2;
+            rect.width = font_height;
+            rect.height = font_height;
+
+            DLOG("This is a tray window. x = %d\n", rect.x);
+            fake_configure_notify(xcb_connection, rect, event->window, 0);
+            return;
+        }
+    }
+
+    DLOG("WARNING: Could not find corresponding tray window.\n");
+}
+
 /*
  * This function is called immediately before the main loop locks. We flush xcb
  * then (and only then)
@@ -412,21 +686,37 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
         exit(1);
     }
 
-    while ((event = xcb_poll_for_event(xcb_connection)) == NULL) {
-        return;
-    }
-
-    switch (event->response_type & ~0x80) {
-        case XCB_EXPOSE:
-            /* Expose-events happen, when the window needs to be redrawn */
-            redraw_bars();
-            break;
-        case XCB_BUTTON_PRESS:
-            /* Button-press-events are mouse-buttons clicked on one of our bars */
-            handle_button((xcb_button_press_event_t*) event);
-            break;
+    while ((event = xcb_poll_for_event(xcb_connection)) != NULL) {
+        switch (event->response_type & ~0x80) {
+            case XCB_EXPOSE:
+                /* Expose-events happen, when the window needs to be redrawn */
+                redraw_bars();
+                break;
+            case XCB_BUTTON_PRESS:
+                /* Button-press-events are mouse-buttons clicked on one of our bars */
+                handle_button((xcb_button_press_event_t*) event);
+                break;
+            case XCB_CLIENT_MESSAGE:
+                /* Client messages are used for client-to-client communication, for
+                 * example system tray widgets talk to us directly via client messages. */
+                handle_client_message((xcb_client_message_event_t*) event);
+                break;
+            case XCB_UNMAP_NOTIFY:
+            case XCB_DESTROY_NOTIFY:
+                /* UnmapNotifies are received when a tray window unmaps itself */
+                handle_unmap_notify((xcb_unmap_notify_event_t*) event);
+                break;
+            case XCB_PROPERTY_NOTIFY:
+                /* PropertyNotify */
+                handle_property_notify((xcb_property_notify_event_t*) event);
+                break;
+            case XCB_CONFIGURE_REQUEST:
+                /* ConfigureRequest, sent by a tray child */
+                handle_configure_request((xcb_configure_request_event_t*) event);
+                break;
+        }
+        free(event);
     }
-    FREE(event);
 }
 
 /*
@@ -477,12 +767,13 @@ void xkb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
 }
 
 /*
- * Initialize xcb and use the specified fontname for text-rendering
+ * Early initialization of the connection to X11: Everything which does not
+ * depend on 'config'.
  *
  */
-char *init_xcb(char *fontname) {
+char *init_xcb_early() {
     /* FIXME: xcb_connect leaks Memory */
-    xcb_connection = xcb_connect(NULL, NULL);
+    xcb_connection = xcb_connect(NULL, &screen);
     if (xcb_connection_has_error(xcb_connection)) {
         ELOG("Cannot open display\n");
         exit(EXIT_FAILURE);
@@ -496,66 +787,10 @@ char *init_xcb(char *fontname) {
     xcb_screen = xcb_setup_roots_iterator(xcb_get_setup(xcb_connection)).data;
     xcb_root = xcb_screen->root;
 
-    /* We load and allocate the font */
-    xcb_font = xcb_generate_id(xcb_connection);
-    xcb_void_cookie_t open_font_cookie;
-    open_font_cookie = xcb_open_font_checked(xcb_connection,
-                                             xcb_font,
-                                             strlen(fontname),
-                                             fontname);
-
-    /* We need to save info about the font, because we need the font's height and
-     * information about the width of characters */
-    xcb_query_font_cookie_t query_font_cookie;
-    query_font_cookie = xcb_query_font(xcb_connection,
-                                       xcb_font);
-
-    /* To grab modifiers without blocking other applications from receiving key-events
-     * involving that modifier, we sadly have to use xkb which is not yet fully supported
-     * in xcb */
-    if (config.hide_on_modifier) {
-        int xkb_major, xkb_minor, xkb_errbase, xkb_err;
-        xkb_major = XkbMajorVersion;
-        xkb_minor = XkbMinorVersion;
-
-        xkb_dpy = XkbOpenDisplay(NULL,
-                                 &xkb_event_base,
-                                 &xkb_errbase,
-                                 &xkb_major,
-                                 &xkb_minor,
-                                 &xkb_err);
-
-        if (xkb_dpy == NULL) {
-            ELOG("No XKB!\n");
-            exit(EXIT_FAILURE);
-        }
-
-        if (fcntl(ConnectionNumber(xkb_dpy), F_SETFD, FD_CLOEXEC) == -1) {
-            ELOG("Could not set FD_CLOEXEC on xkbdpy: %s\n", strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-
-        int i1;
-        if (!XkbQueryExtension(xkb_dpy, &i1, &xkb_event_base, &xkb_errbase, &xkb_major, &xkb_minor)) {
-            ELOG("XKB not supported by X-server!\n");
-            exit(EXIT_FAILURE);
-        }
-
-        if (!XkbSelectEvents(xkb_dpy, XkbUseCoreKbd, XkbStateNotifyMask, XkbStateNotifyMask)) {
-            ELOG("Could not grab Key!\n");
-            exit(EXIT_FAILURE);
-        }
-
-        xkb_io = malloc(sizeof(ev_io));
-        ev_io_init(xkb_io, &xkb_io_cb, ConnectionNumber(xkb_dpy), EV_READ);
-        ev_io_start(main_loop, xkb_io);
-        XFlush(xkb_dpy);
-    }
-
     /* We draw the statusline to a seperate pixmap, because it looks the same on all bars and
      * this way, we can choose to crop it */
     uint32_t mask = XCB_GC_FOREGROUND;
-    uint32_t vals[3] = { colors.bar_bg, colors.bar_bg, xcb_font };
+    uint32_t vals[] = { colors.bar_bg, colors.bar_bg };
 
     statusline_clear = xcb_generate_id(xcb_connection);
     xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection,
@@ -564,7 +799,7 @@ char *init_xcb(char *fontname) {
                                                                mask,
                                                                vals);
 
-    mask |= XCB_GC_BACKGROUND | XCB_GC_FONT;
+    mask |= XCB_GC_BACKGROUND;
     vals[0] = colors.bar_fg;
     statusline_ctx = xcb_generate_id(xcb_connection);
     xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection,
@@ -583,9 +818,9 @@ char *init_xcb(char *fontname) {
 
 
     /* The various Watchers to communicate with xcb */
-    xcb_io = malloc(sizeof(ev_io));
-    xcb_prep = malloc(sizeof(ev_prepare));
-    xcb_chk = malloc(sizeof(ev_check));
+    xcb_io = smalloc(sizeof(ev_io));
+    xcb_prep = smalloc(sizeof(ev_prepare));
+    xcb_chk = smalloc(sizeof(ev_check));
 
     ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
     ev_prepare_init(xcb_prep, &xcb_prep_cb);
@@ -618,6 +853,90 @@ char *init_xcb(char *fontname) {
         }
     }
 
+
+    if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") ||
+        xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") ||
+        xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline-buffer-context")) {
+        exit(EXIT_FAILURE);
+    }
+
+    return path;
+}
+
+/*
+ * Initialization which depends on 'config' being usable. Called after the
+ * configuration has arrived.
+ *
+ */
+void init_xcb_late(char *fontname) {
+    if (fontname == NULL) {
+        /* XXX: font fallback to 'misc' like i3 does it would be good. */
+        fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+    }
+
+    /* We load and allocate the font */
+    xcb_font = xcb_generate_id(xcb_connection);
+    xcb_void_cookie_t open_font_cookie;
+    open_font_cookie = xcb_open_font_checked(xcb_connection,
+                                             xcb_font,
+                                             strlen(fontname),
+                                             fontname);
+
+    /* We need to save info about the font, because we need the font's height and
+     * information about the width of characters */
+    xcb_query_font_cookie_t query_font_cookie;
+    query_font_cookie = xcb_query_font(xcb_connection,
+                                       xcb_font);
+
+    xcb_change_gc(xcb_connection,
+                  statusline_ctx,
+                  XCB_GC_FONT,
+                  (uint32_t[]){ xcb_font });
+
+    xcb_flush(xcb_connection);
+
+    /* To grab modifiers without blocking other applications from receiving key-events
+     * involving that modifier, we sadly have to use xkb which is not yet fully supported
+     * in xcb */
+    if (config.hide_on_modifier) {
+        int xkb_major, xkb_minor, xkb_errbase, xkb_err;
+        xkb_major = XkbMajorVersion;
+        xkb_minor = XkbMinorVersion;
+
+        xkb_dpy = XkbOpenDisplay(NULL,
+                                 &xkb_event_base,
+                                 &xkb_errbase,
+                                 &xkb_major,
+                                 &xkb_minor,
+                                 &xkb_err);
+
+        if (xkb_dpy == NULL) {
+            ELOG("No XKB!\n");
+            exit(EXIT_FAILURE);
+        }
+
+        if (fcntl(ConnectionNumber(xkb_dpy), F_SETFD, FD_CLOEXEC) == -1) {
+            ELOG("Could not set FD_CLOEXEC on xkbdpy: %s\n", strerror(errno));
+            exit(EXIT_FAILURE);
+        }
+
+        int i1;
+        if (!XkbQueryExtension(xkb_dpy, &i1, &xkb_event_base, &xkb_errbase, &xkb_major, &xkb_minor)) {
+            ELOG("XKB not supported by X-server!\n");
+            exit(EXIT_FAILURE);
+        }
+
+        if (!XkbSelectEvents(xkb_dpy, XkbUseCoreKbd, XkbStateNotifyMask, XkbStateNotifyMask)) {
+            ELOG("Could not grab Key!\n");
+            exit(EXIT_FAILURE);
+        }
+
+        xkb_io = smalloc(sizeof(ev_io));
+        ev_io_init(xkb_io, &xkb_io_cb, ConnectionNumber(xkb_dpy), EV_READ);
+        ev_io_start(main_loop, xkb_io);
+        XFlush(xkb_dpy);
+    }
+
     /* Now we save the font-infos */
     font_info = xcb_query_font_reply(xcb_connection,
                                      query_font_cookie,
@@ -636,14 +955,96 @@ char *init_xcb(char *fontname) {
     }
 
     DLOG("Calculated Font-height: %d\n", font_height);
+}
 
-    if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") ||
-        xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") ||
-        xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline-buffer-context")) {
+/*
+ * Initializes tray support by requesting the appropriate _NET_SYSTEM_TRAY atom
+ * for the X11 display we are running on, then acquiring the selection for this
+ * atom. Afterwards, tray clients will send ClientMessages to our window.
+ *
+ */
+void init_tray() {
+    DLOG("Initializing system tray functionality\n");
+    /* request the tray manager atom for the X11 display we are running on */
+    char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
+    snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen);
+    xcb_intern_atom_cookie_t tray_cookie;
+    xcb_intern_atom_reply_t *tray_reply;
+    tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname);
+
+    /* tray support: we need a window to own the selection */
+    xcb_window_t selwin = xcb_generate_id(xcb_connection);
+    uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT;
+    uint32_t selval[] = { 1 };
+    xcb_create_window(xcb_connection,
+                      xcb_screen->root_depth,
+                      selwin,
+                      xcb_root,
+                      -1, -1,
+                      1, 1,
+                      1,
+                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
+                      xcb_screen->root_visual,
+                      selmask,
+                      selval);
+
+    uint32_t orientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
+    /* set the atoms */
+    xcb_change_property(xcb_connection,
+                        XCB_PROP_MODE_REPLACE,
+                        selwin,
+                        atoms[_NET_SYSTEM_TRAY_ORIENTATION],
+                        XCB_ATOM_CARDINAL,
+                        32,
+                        1,
+                        &orientation);
+
+    if (!(tray_reply = xcb_intern_atom_reply(xcb_connection, tray_cookie, NULL))) {
+        ELOG("Could not get atom %s\n", atomname);
         exit(EXIT_FAILURE);
     }
 
-    return path;
+    xcb_set_selection_owner(xcb_connection,
+                            selwin,
+                            tray_reply->atom,
+                            XCB_CURRENT_TIME);
+
+    /* Verify that we have the selection */
+    xcb_get_selection_owner_cookie_t selcookie;
+    xcb_get_selection_owner_reply_t *selreply;
+
+    selcookie = xcb_get_selection_owner(xcb_connection, tray_reply->atom);
+    if (!(selreply = xcb_get_selection_owner_reply(xcb_connection, selcookie, NULL))) {
+        ELOG("Could not get selection owner for %s\n", atomname);
+        exit(EXIT_FAILURE);
+    }
+
+    if (selreply->owner != selwin) {
+        ELOG("Could not set the %s selection. " \
+             "Maybe another tray is already running?\n", atomname);
+        /* NOTE that this error is not fatal. We just can’t provide tray
+         * functionality */
+        free(selreply);
+        return;
+    }
+
+    /* Inform clients waiting for a new _NET_SYSTEM_TRAY that we are here */
+    void *event = scalloc(32);
+    xcb_client_message_event_t *ev = event;
+    ev->response_type = XCB_CLIENT_MESSAGE;
+    ev->window = xcb_root;
+    ev->type = atoms[MANAGER];
+    ev->format = 32;
+    ev->data.data32[0] = XCB_CURRENT_TIME;
+    ev->data.data32[1] = tray_reply->atom;
+    ev->data.data32[2] = selwin;
+    xcb_send_event(xcb_connection,
+                   0,
+                   xcb_root,
+                   XCB_EVENT_MASK_STRUCTURE_NOTIFY,
+                   (char*)ev);
+    free(event);
+    free(tray_reply);
 }
 
 /*
@@ -653,15 +1054,27 @@ char *init_xcb(char *fontname) {
  */
 void clean_xcb() {
     i3_output *o_walk;
+    trayclient *trayclient;
     free_workspaces();
     SLIST_FOREACH(o_walk, outputs, slist) {
+        TAILQ_FOREACH(trayclient, o_walk->trayclients, tailq) {
+            /* Unmap, then reparent (to root) the tray client windows */
+            xcb_unmap_window(xcb_connection, trayclient->win);
+            xcb_reparent_window(xcb_connection,
+                                trayclient->win,
+                                xcb_root,
+                                0,
+                                0);
+        }
         destroy_window(o_walk);
+        FREE(o_walk->trayclients);
         FREE(o_walk->workspaces);
         FREE(o_walk->name);
     }
     FREE_SLIST(outputs, i3_output);
     FREE(outputs);
 
+    xcb_flush(xcb_connection);
     xcb_disconnect(xcb_connection);
 
     ev_check_stop(main_loop, xcb_chk);
@@ -758,6 +1171,7 @@ void realloc_sl_buffer() {
 void reconfig_windows() {
     uint32_t mask;
     uint32_t values[5];
+    static bool tray_configured = false;
 
     i3_output *walk;
     SLIST_FOREACH(walk, outputs, slist) {
@@ -778,8 +1192,14 @@ void reconfig_windows() {
             values[0] = colors.bar_bg;
             /* If hide_on_modifier is set, i3 is not supposed to manage our bar-windows */
             values[1] = config.hide_on_modifier;
-            /* The events we want to receive */
-            values[2] = XCB_EVENT_MASK_EXPOSURE;
+            /* We enable the following EventMask fields:
+             * EXPOSURE, to get expose events (we have to re-draw then)
+             * SUBSTRUCTURE_REDIRECT, to get ConfigureRequests when the tray
+             *                        child windows use ConfigureWindow
+             * BUTTON_PRESS, to handle clicks on the workspace buttons
+             * */
+            values[2] = XCB_EVENT_MASK_EXPOSURE |
+                        XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
             if (!config.disable_ws) {
                 values[2] |= XCB_EVENT_MASK_BUTTON_PRESS;
             }
@@ -803,6 +1223,31 @@ void reconfig_windows() {
                                                                     walk->rect.w,
                                                                     walk->rect.h);
 
+            /* Set the WM_CLASS and WM_NAME (we don't need UTF-8) atoms */
+            xcb_void_cookie_t class_cookie;
+            class_cookie = xcb_change_property(xcb_connection,
+                                               XCB_PROP_MODE_REPLACE,
+                                               walk->bar,
+                                               XCB_ATOM_WM_CLASS,
+                                               XCB_ATOM_STRING,
+                                               8,
+                                               (strlen("i3bar") + 1) * 2,
+                                               "i3bar\0i3bar\0");
+
+            char *name;
+            if (asprintf(&name, "i3bar for output %s", walk->name) == -1)
+                err(EXIT_FAILURE, "asprintf()");
+            xcb_void_cookie_t name_cookie;
+            name_cookie = xcb_change_property(xcb_connection,
+                                              XCB_PROP_MODE_REPLACE,
+                                              walk->bar,
+                                              XCB_ATOM_WM_NAME,
+                                              XCB_ATOM_STRING,
+                                              8,
+                                              strlen(name),
+                                              name);
+            free(name);
+
             /* We want dock-windows (for now). When override_redirect is set, i3 is ignoring
              * this one */
             xcb_void_cookie_t dock_cookie = xcb_change_property(xcb_connection,
@@ -833,15 +1278,15 @@ void reconfig_windows() {
                 uint32_t bottom_start_x;
                 uint32_t bottom_end_x;
             } __attribute__((__packed__)) strut_partial = {0,};
-            switch (config.dockpos) {
-                case DOCKPOS_NONE:
+            switch (config.position) {
+                case POS_NONE:
                     break;
-                case DOCKPOS_TOP:
+                case POS_TOP:
                     strut_partial.top = font_height + 6;
                     strut_partial.top_start_x = walk->rect.x;
                     strut_partial.top_end_x = walk->rect.x + walk->rect.w;
                     break;
-                case DOCKPOS_BOT:
+                case POS_BOT:
                     strut_partial.bottom = font_height + 6;
                     strut_partial.bottom_start_x = walk->rect.x;
                     strut_partial.bottom_end_x = walk->rect.x + walk->rect.w;
@@ -876,11 +1321,20 @@ void reconfig_windows() {
             if (xcb_request_failed(win_cookie,   "Could not create window") ||
                 xcb_request_failed(pm_cookie,    "Could not create pixmap") ||
                 xcb_request_failed(dock_cookie,  "Could not set dock mode") ||
+                xcb_request_failed(class_cookie, "Could not set WM_CLASS")  ||
+                xcb_request_failed(name_cookie,  "Could not set WM_NAME")   ||
                 xcb_request_failed(strut_cookie, "Could not set strut")     ||
                 xcb_request_failed(gc_cookie,    "Could not create graphical context") ||
                 (!config.hide_on_modifier && xcb_request_failed(map_cookie, "Could not map window"))) {
                 exit(EXIT_FAILURE);
             }
+
+            if (!tray_configured &&
+                (!config.tray_output ||
+                 strcasecmp("none", config.tray_output) != 0)) {
+                init_tray();
+                tray_configured = true;
+            }
         } else {
             /* We already have a bar, so we just reconfigure it */
             mask = XCB_CONFIG_WINDOW_X |
@@ -960,13 +1414,26 @@ void draw_bars() {
             /* Luckily we already prepared a seperate pixmap containing the rendered
              * statusline, we just have to copy the relevant parts to the relevant
              * position */
+            trayclient *trayclient;
+            int traypx = 0;
+            TAILQ_FOREACH(trayclient, outputs_walk->trayclients, tailq) {
+                if (!trayclient->mapped)
+                    continue;
+                /* We assume the tray icons are quadratic (we use the font
+                 * *height* as *width* of the icons) because we configured them
+                 * like this. */
+                traypx += font_height + 2;
+            }
+            /* Add 2px of padding if there are any tray icons */
+            if (traypx > 0)
+                traypx += 2;
             xcb_copy_area(xcb_connection,
                           statusline_pm,
                           outputs_walk->buffer,
                           outputs_walk->bargc,
                           MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0,
-                          MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - 4)), 3,
-                          MIN(outputs_walk->rect.w - 4, statusline_width), font_height);
+                          MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3,
+                          MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font_height);
         }
 
         if (config.disable_ws) {
index b87be518c1b18193a89a97d11d7bc4b35faa59c9..8ae4e0a29ac26f8f24cb65a8d1fc1413e458ce43 100644 (file)
@@ -1,4 +1,9 @@
 /*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
  * This header file includes all relevant files of i3 and the most often used
  * system header files. This reduces boilerplate (the amount of code duplicated
  * at the beginning of each source file) and is not significantly slower at
@@ -64,5 +69,8 @@
 #include "output.h"
 #include "ewmh.h"
 #include "assignments.h"
+#include "regex.h"
+#include "libi3.h"
+#include "startup.h"
 
 #endif
index f72dd2e5e4c4a0db14c499ae35d57622096cc008..f4ef8e88067afdb272fc1952d6101c272a37be2d 100644 (file)
@@ -1,6 +1,11 @@
 /*
  * vim:ts=4:sw=4:expandtab
  *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * assignments.c: Assignments for specific windows (for_window).
+ *
  */
 #ifndef _ASSIGNMENTS_H
 #define _ASSIGNMENTS_H
index 300a8abaea48ae0976b1feaacfd9730717e06742..f08a90d5585abbbf60292b4dcea27d99c1362ba5 100644 (file)
@@ -15,11 +15,14 @@ xmacro(_NET_CLIENT_LIST_STACKING)
 xmacro(_NET_CURRENT_DESKTOP)
 xmacro(_NET_ACTIVE_WINDOW)
 xmacro(_NET_WORKAREA)
+xmacro(_NET_STARTUP_ID)
 xmacro(WM_PROTOCOLS)
 xmacro(WM_DELETE_WINDOW)
 xmacro(UTF8_STRING)
 xmacro(WM_STATE)
 xmacro(WM_CLIENT_LEADER)
 xmacro(WM_TAKE_FOCUS)
+xmacro(WM_WINDOW_ROLE)
 xmacro(I3_SOCKET_PATH)
 xmacro(I3_CONFIG_PATH)
+xmacro(I3_SYNC)
index 2de32d045308059495b0e136aa7d72fcffbb5d84..6261613ba762c19dace78d9073f4f571d96a6f61 100644 (file)
@@ -2,10 +2,9 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * click.c: Button press (mouse click) events.
  *
  */
 #ifndef _CLICK_H
index 09d56bae7e338c5027a983c23b1e64010b1e530f..d619b97c17932fe8966fb0b7f32c111b28df0926 100644 (file)
@@ -1,3 +1,12 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * cmdparse.y: the parser for commands you send to i3 (or bind on keys)
+ *
+ */
 #ifndef _CMDPARSE_H
 #define _CMDPARSE_H
 
index 155bb7d8433989e5bafecf9ff6b85cd3d054dcc4..34c6450cf39f5574867b29daf64cc6c922183af0 100644 (file)
@@ -1,3 +1,14 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * con.c: Functions which deal with containers directly (creating containers,
+ *        searching containers, getting specific properties from containers,
+ *        …).
+ *
+ */
 #ifndef _CON_H
 #define _CON_H
 
index 1021a612e7a98917a211a61d037b688e255c6861..b4128cafc6a1ca4381d9814e9b3e7543ede5114e 100644 (file)
@@ -1,11 +1,8 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * include/config.h: Contains all structs/variables for the configurable
  * part of i3 as well as functions handling the configuration file (calling
  * mode).
  *
  */
-
 #ifndef _CONFIG_H
 #define _CONFIG_H
 
 #include <stdbool.h>
 #include "queue.h"
 #include "i3.h"
+#include "libi3.h"
 
 typedef struct Config Config;
+typedef struct Barconfig Barconfig;
 extern char *current_configpath;
 extern Config config;
 extern SLIST_HEAD(modes_head, Mode) modes;
+extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
 
 /**
  * Used during the config file lexing/parsing to keep the state of the lexer
@@ -32,17 +31,18 @@ extern SLIST_HEAD(modes_head, Mode) modes;
  *
  */
 struct context {
-        bool has_errors;
+    bool has_errors;
+    bool has_warnings;
 
-        int line_number;
-        char *line_copy;
-        const char *filename;
+    int line_number;
+    char *line_copy;
+    const char *filename;
 
-        char *compact_error;
+    char *compact_error;
 
-        /* These are the same as in YYLTYPE */
-        int first_column;
-        int last_column;
+    /* These are the same as in YYLTYPE */
+    int first_column;
+    int last_column;
 };
 
 /**
@@ -51,9 +51,9 @@ struct context {
  *
  */
 struct Colortriple {
-        uint32_t border;
-        uint32_t background;
-        uint32_t text;
+    uint32_t border;
+    uint32_t background;
+    uint32_t text;
 };
 
 /**
@@ -62,11 +62,11 @@ struct Colortriple {
  *
  */
 struct Variable {
-        char *key;
-        char *value;
-        char *next_match;
+    char *key;
+    char *value;
+    char *next_match;
 
-        SLIST_ENTRY(Variable) variables;
+    SLIST_ENTRY(Variable) variables;
 };
 
 /**
@@ -76,10 +76,10 @@ struct Variable {
  *
  */
 struct Mode {
-        char *name;
-        struct bindings_head *bindings;
+    char *name;
+    struct bindings_head *bindings;
 
-        SLIST_ENTRY(Mode) modes;
+    SLIST_ENTRY(Mode) modes;
 };
 
 /**
@@ -88,67 +88,152 @@ struct Mode {
  *
  */
 struct Config {
-        const char *terminal;
-        i3Font font;
-
-        char *ipc_socket_path;
-        const char *restart_state_path;
-
-        int default_layout;
-        int container_stack_limit;
-        int container_stack_limit_value;
-
-        /** Default orientation for new containers */
-        int default_orientation;
-
-        /** By default, focus follows mouse. If the user explicitly wants to
-         * turn this off (and instead rely only on the keyboard for changing
-         * focus), we allow him to do this with this relatively special option.
-         * It is not planned to add any different focus models. */
-        bool disable_focus_follows_mouse;
-
-        /** By default, a workspace bar is drawn at the bottom of the screen.
-         * If you want to have a more fancy bar, it is recommended to replace
-         * the whole bar by dzen2, for example using the i3-wsbar script which
-         * comes with i3. Thus, you can turn it off entirely. */
-        bool disable_workspace_bar;
-
-        /** Think of the following layout: Horizontal workspace with a tabbed
-         * con on the left of the screen and a terminal on the right of the
-         * screen. You are in the second container in the tabbed container and
-         * focus to the right. By default, i3 will set focus to the terminal on
-         * the right. If you are in the first container in the tabbed container
-         * however, focusing to the left will wrap. This option forces i3 to
-         * always wrap, which will result in you having to use "focus parent"
-         * more often. */
-        bool force_focus_wrapping;
-
-        /** The default border style for new windows. */
-        border_style_t default_border;
-
-        /** The modifier which needs to be pressed in combination with your mouse
-         * buttons to do things with floating windows (move, resize) */
-        uint32_t floating_modifier;
-
-        /* Color codes are stored here */
-        struct config_client {
-                uint32_t background;
-                struct Colortriple focused;
-                struct Colortriple focused_inactive;
-                struct Colortriple unfocused;
-                struct Colortriple urgent;
-        } client;
-        struct config_bar {
-                struct Colortriple focused;
-                struct Colortriple unfocused;
-                struct Colortriple urgent;
-        } bar;
-
-        /** What should happen when a new popup is opened during fullscreen mode */
-        enum {
-                PDF_LEAVE_FULLSCREEN = 0,
-                PDF_IGNORE = 1
-        } popup_during_fullscreen;
+    const char *terminal;
+    i3Font font;
+
+    char *ipc_socket_path;
+    const char *restart_state_path;
+
+    int default_layout;
+    int container_stack_limit;
+    int container_stack_limit_value;
+
+    /** Default orientation for new containers */
+    int default_orientation;
+
+    /** By default, focus follows mouse. If the user explicitly wants to
+     * turn this off (and instead rely only on the keyboard for changing
+     * focus), we allow him to do this with this relatively special option.
+     * It is not planned to add any different focus models. */
+    bool disable_focus_follows_mouse;
+
+    /** By default, a workspace bar is drawn at the bottom of the screen.
+     * If you want to have a more fancy bar, it is recommended to replace
+     * the whole bar by dzen2, for example using the i3-wsbar script which
+     * comes with i3. Thus, you can turn it off entirely. */
+    bool disable_workspace_bar;
+
+    /** Think of the following layout: Horizontal workspace with a tabbed
+     * con on the left of the screen and a terminal on the right of the
+     * screen. You are in the second container in the tabbed container and
+     * focus to the right. By default, i3 will set focus to the terminal on
+     * the right. If you are in the first container in the tabbed container
+     * however, focusing to the left will wrap. This option forces i3 to
+     * always wrap, which will result in you having to use "focus parent"
+     * more often. */
+    bool force_focus_wrapping;
+
+    /** By default, use the RandR API for multi-monitor setups.
+     * Unfortunately, the nVidia binary graphics driver doesn't support
+     * this API. Instead, it only support the less powerful Xinerama API,
+     * which can be enabled by this option.
+     *
+     * Note: this option takes only effect on the initial startup (eg.
+     * reconfiguration is not possible). On startup, the list of screens
+     * is fetched once and never updated. */
+    bool force_xinerama;
+
+    /** Automatic workspace back and forth switching. If this is set, a
+     * switch to the currently active workspace will switch to the
+     * previously focused one instead, making it possible to fast toggle
+     * between two workspaces. */
+    bool workspace_auto_back_and_forth;
+
+    /** The default border style for new windows. */
+    border_style_t default_border;
+
+    /** The default border style for new floating windows. */
+    border_style_t default_floating_border;
+
+    /** The modifier which needs to be pressed in combination with your mouse
+     * buttons to do things with floating windows (move, resize) */
+    uint32_t floating_modifier;
+
+    /* Color codes are stored here */
+    struct config_client {
+        uint32_t background;
+        struct Colortriple focused;
+        struct Colortriple focused_inactive;
+        struct Colortriple unfocused;
+        struct Colortriple urgent;
+    } client;
+    struct config_bar {
+        struct Colortriple focused;
+        struct Colortriple unfocused;
+        struct Colortriple urgent;
+    } bar;
+
+    /** What should happen when a new popup is opened during fullscreen mode */
+    enum {
+        PDF_LEAVE_FULLSCREEN = 0,
+        PDF_IGNORE = 1
+    } popup_during_fullscreen;
+};
+
+/**
+ * Holds the status bar configuration (i3bar). One of these structures is
+ * created for each 'bar' block in the config.
+ *
+ */
+struct Barconfig {
+    /** Automatically generated ID for this bar config. Used by the bar process
+     * to request a specific configuration. */
+    char *id;
+
+    /** Number of outputs in the outputs array */
+    int num_outputs;
+    /** Outputs on which this bar should show up on. We use an array for
+     * simplicity (since we store just strings). */
+    char **outputs;
+
+    /** Output on which the tray should be shown. The special value of 'no'
+     * disables the tray (it’s enabled by default). */
+    char *tray_output;
+
+    /** Path to the i3 IPC socket. This option is discouraged since programs
+     * can find out the path by looking for the I3_SOCKET_PATH property on the
+     * root window! */
+    char *socket_path;
+
+    /** Bar display mode (hide unless modifier is pressed or show in dock mode) */
+    enum { M_DOCK = 0, M_HIDE = 1 } mode;
+
+    /** Bar position (bottom by default). */
+    enum { P_BOTTOM = 0, P_TOP = 1 } position;
+
+    /** Command that should be run to get a statusline, for example 'i3status'.
+     * Will be passed to the shell. */
+    char *status_command;
+
+    /** Font specification for all text rendered on the bar. */
+    char *font;
+
+    /** Hide workspace buttons? Configuration option is 'workspace_buttons no'
+     * but we invert the bool to get the correct default when initializing with
+     * zero. */
+    bool hide_workspace_buttons;
+
+    /** Enable verbose mode? Useful for debugging purposes. */
+    bool verbose;
+
+    struct bar_colors {
+        char *background;
+        char *statusline;
+
+        char *focused_workspace_text;
+        char *focused_workspace_bg;
+
+        char *active_workspace_text;
+        char *active_workspace_bg;
+
+        char *inactive_workspace_text;
+        char *inactive_workspace_bg;
+
+        char *urgent_workspace_text;
+        char *urgent_workspace_bg;
+    } colors;
+
+    TAILQ_ENTRY(Barconfig) configs;
 };
 
 /**
index 5797b7d80d1db633dcb52eb94c4272c9ea55a521..740278aecd12eb4bae72c0848d5ec25c474bf28d 100644 (file)
@@ -7,12 +7,17 @@
  * include/data.h: This file defines all data structures used by i3
  *
  */
+#ifndef _DATA_H
+#define _DATA_H
+
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-launcher.h>
+
 #include <xcb/randr.h>
 #include <xcb/xcb_atom.h>
 #include <stdbool.h>
+#include <pcre.h>
 
-#ifndef _DATA_H
-#define _DATA_H
 #include "queue.h"
 
 /*
@@ -27,7 +32,6 @@
  */
 
 /* Forward definitions */
-typedef struct Font i3Font;
 typedef struct Binding Binding;
 typedef struct Rect Rect;
 typedef struct xoutput Output;
@@ -137,6 +141,37 @@ struct Ignore_Event {
     SLIST_ENTRY(Ignore_Event) ignore_events;
 };
 
+/**
+ * Stores internal information about a startup sequence, like the workspace it
+ * was initiated on.
+ *
+ */
+struct Startup_Sequence {
+    /** startup ID for this sequence, generated by libstartup-notification */
+    char *id;
+    /** workspace on which this startup was initiated */
+    char *workspace;
+    /** libstartup-notification context for this launch */
+    SnLauncherContext *context;
+
+    TAILQ_ENTRY(Startup_Sequence) sequences;
+};
+
+/**
+ * Regular expression wrapper. It contains the pattern itself as a string (like
+ * ^foo[0-9]$) as well as a pointer to the compiled PCRE expression and the
+ * pcre_extra data returned by pcre_study().
+ *
+ * This makes it easier to have a useful logfile, including the matching or
+ * non-matching pattern.
+ *
+ */
+struct regex {
+    char *pattern;
+    pcre *regex;
+    pcre_extra *extra;
+};
+
 /******************************************************************************
  * Major types
  *****************************************************************************/
@@ -183,24 +218,13 @@ struct Binding {
 struct Autostart {
     /** Command, like in command mode */
     char *command;
+    /** no_startup_id flag for start_application(). Determines whether a
+     * startup notification context/ID should be created. */
+    bool no_startup_id;
     TAILQ_ENTRY(Autostart) autostarts;
     TAILQ_ENTRY(Autostart) autostarts_always;
 };
 
-/**
- * Data structure for cached font information:
- * - font id in X11 (load it once)
- * - font height (multiple calls needed to get it)
- *
- */
-struct Font {
-    /** The height of the font, built from font_ascent + font_descent */
-    int height;
-    /** The xcb-id for the font */
-    xcb_font_t id;
-};
-
-
 /**
  * An Output is a physical output on your graphics driver. Outputs which
  * are currently in use have (output->active == true). Each output has a
@@ -248,6 +272,11 @@ struct Window {
      * application supports _NET_WM_NAME, in COMPOUND_TEXT otherwise). */
     char *name_x;
 
+    /** The WM_WINDOW_ROLE of this window (for example, the pidgin buddy window
+     * sets "buddy list"). Useful to match specific windows in assignments or
+     * for_window. */
+    char *role;
+
     /** Flag to force re-rendering the decoration upon changes */
     bool name_x_changed;
 
@@ -277,12 +306,12 @@ struct Window {
 };
 
 struct Match {
-    char *title;
-    int title_len;
-    char *application;
-    char *class;
-    char *instance;
-    char *mark;
+    struct regex *title;
+    struct regex *application;
+    struct regex *class;
+    struct regex *instance;
+    struct regex *mark;
+    struct regex *role;
     enum {
         M_DONTCHECK = -1,
         M_NODOCK = 0,
index 3d72212076589b1455089ba5c23c1ebdf50c86d0..abf9c76dfe0b6692032c006312c2f3599bd0aefc 100644 (file)
@@ -1,11 +1,11 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * (c) 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * debug.c: Debugging functions, especially FormatEvent, which prints unhandled
+ *          events.  This code is from xcb-util.
  *
  */
 #ifndef _DEBUG_H
index 54c83f400fa97755e83c0d84fe4a00dbb27eb434..0a0cbc292bbdd6bcaf3c87695a7ea461f57b78f1 100644 (file)
@@ -2,10 +2,9 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * ewmh.c: Get/set certain EWMH properties easily.
  *
  */
 #ifndef _EWMH_C
index 6ab4cf2e1621597e191d57f2a8ecff4091ade4f6..e97029f5bb4e0e1a4198ac3db18e93b5122679cc 100644 (file)
@@ -1,11 +1,10 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * floating.c: Floating windows.
  *
  */
 #ifndef _FLOATING_H
 #include "tree.h"
 
 /** Callback for dragging */
-typedef void(*callback_t)(Con*, Rect*, uint32_t, uint32_t, void*);
+typedef void(*callback_t)(Con*, Rect*, uint32_t, uint32_t, const void*);
 
 /** Macro to create a callback function for dragging */
 #define DRAGGING_CB(name) \
         static void name(Con *con, Rect *old_rect, uint32_t new_x, \
-                         uint32_t new_y, void *extra)
+                         uint32_t new_y, const void *extra)
 
 /** On which border was the dragging initiated? */
 typedef enum { BORDER_LEFT   = (1 << 0),
@@ -90,7 +89,7 @@ int floating_border_click(xcb_connection_t *conn, Client *client,
  * Calls the drag_pointer function with the drag_window callback
  *
  */
-void floating_drag_window(Con *con, xcb_button_press_event_t *event);
+void floating_drag_window(Con *con, const xcb_button_press_event_t *event);
 
 /**
  * Called when the user clicked on a floating window while holding the
@@ -98,7 +97,7 @@ void floating_drag_window(Con *con, xcb_button_press_event_t *event);
  * Calls the drag_pointer function with the resize_window callback
  *
  */
-void floating_resize_window(Con *con, bool proportional, xcb_button_press_event_t *event);
+void floating_resize_window(Con *con, const bool proportional, const xcb_button_press_event_t *event);
 
 #if 0
 /**
@@ -134,8 +133,8 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
  * the event and the new coordinates (x, y).
  *
  */
-void drag_pointer(Con *con, xcb_button_press_event_t *event,
+void drag_pointer(Con *con, const xcb_button_press_event_t *event,
                   xcb_window_t confine_to, border_t border, callback_t callback,
-                  void *extra);
+                  const void *extra);
 
 #endif
index 0aaaf158a67c33730958e5ba3ede0a087ae2dbb9..ebec34cf7e0f68c87b6c847bf84b8e81117dd62e 100644 (file)
@@ -1,11 +1,11 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * handlers.c: Small handlers for various events (keypresses, focus changes,
+ *             …).
  *
  */
 #ifndef _HANDLERS_H
index 22dcd476b9605349f2ef53fbd02039a3659498b1..75b7a9bf1b662d2f11c0c4e613368bb1e1f1ebb0 100644 (file)
@@ -2,24 +2,39 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * i3.h: global variables that are used all over i3.
  *
  */
+#ifndef _I3_H
+#define _I3_H
+
+#include <sys/time.h>
+#include <sys/resource.h>
+
 #include <xcb/xcb_keysyms.h>
 
 #include <X11/XKBlib.h>
 
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-launcher.h>
+
 #include "queue.h"
 #include "data.h"
 #include "xcb.h"
 
-#ifndef _I3_H
-#define _I3_H
-
+/** The original value of RLIMIT_CORE when i3 was started. We need to restore
+ * this before starting any other process, since we set RLIMIT_CORE to
+ * RLIM_INFINITY for i3 debugging versions. */
+extern struct rlimit original_rlimit_core;
 extern xcb_connection_t *conn;
+extern int conn_screen;
+/** The last timestamp we got from X11 (timestamps are included in some events
+ * and are used for some things, like determining a unique ID in startup
+ * notification). */
+extern xcb_timestamp_t last_timestamp;
+extern SnDisplay *sndisplay;
 extern xcb_key_symbols_t *keysyms;
 extern char **start_argv;
 extern Display *xlibdpy, *xkbdpy;
@@ -35,5 +50,6 @@ extern uint8_t root_depth;
 extern bool xcursor_supported, xkb_supported;
 extern xcb_window_t root;
 extern struct ev_loop *main_loop;
+extern bool only_check_config;
 
 #endif
index e81f9a155ad5e7254a631c1fb04d7a6a172e6a48..bfadf4cf87d086571c96584c54b3c34e49cd6362 100644 (file)
@@ -1,17 +1,13 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * This public header defines the different constants and message types to use
  * for the IPC interface to i3 (see docs/ipc for more information).
  *
  */
-
 #ifndef _I3_IPC_H
 #define _I3_IPC_H
 
 /** Requests the tree layout from i3 */
 #define I3_IPC_MESSAGE_TYPE_GET_TREE            4
 
+/** Request the current defined marks from i3 */
+#define I3_IPC_MESSAGE_TYPE_GET_MARKS           5
+
+/** Request the configuration for a specific 'bar' */
+#define I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG      6
 
 /*
  * Messages from i3 to clients
 /** Tree reply type */
 #define I3_IPC_REPLY_TYPE_TREE                  4
 
+/** Marks reply type */
+#define I3_IPC_REPLY_TYPE_MARKS                 5
+
+/** Bar config reply type */
+#define I3_IPC_REPLY_TYPE_BAR_CONFIG            6
 
 /*
  * Events from i3 to clients. Events have the first bit set high.
index a5de487aeeb177def2834824f384417a74f2b414..9b59fb0163d48abd65c90eee0faaf717794c5abb 100644 (file)
@@ -1,14 +1,12 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).
  *
  */
-
 #ifndef _IPC_H
 #define _IPC_H
 
diff --git a/include/libi3.h b/include/libi3.h
new file mode 100644 (file)
index 0000000..71fba76
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * libi3: contains functions which are used by i3 *and* accompanying tools such
+ * as i3-msg, i3-config-wizard, …
+ *
+ */
+#ifndef _LIBI3_H
+#define _LIBI3_H
+
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <xcb/xcb.h>
+#include <xcb/xproto.h>
+#include <xcb/xcb_keysyms.h>
+
+typedef struct Font i3Font;
+
+/**
+ * Data structure for cached font information:
+ * - font id in X11 (load it once)
+ * - font height (multiple calls needed to get it)
+ *
+ */
+struct Font {
+    /** The height of the font, built from font_ascent + font_descent */
+    int height;
+    /** The xcb-id for the font */
+    xcb_font_t id;
+};
+
+/* Since this file also gets included by utilities which don’t use the i3 log
+ * infrastructure, we define a fallback. */
+#if !defined(ELOG)
+#define ELOG(fmt, ...) fprintf(stderr, "ERROR: " fmt, ##__VA_ARGS__)
+#endif
+
+/**
+ * Try to get the socket path from X11 and return NULL if it doesn’t work.
+ *
+ * The memory for the socket path is dynamically allocated and has to be
+ * free()d by the caller.
+ *
+ */
+char *socket_path_from_x11();
+
+/**
+ * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that
+ * there is no more memory available)
+ *
+ */
+void *smalloc(size_t size);
+
+/**
+ * Safe-wrapper around calloc which exits if malloc returns NULL (meaning that
+ * there is no more memory available)
+ *
+ */
+void *scalloc(size_t size);
+
+/**
+ * Safe-wrapper around realloc which exits if realloc returns NULL (meaning
+ * that there is no more memory available).
+ *
+ */
+void *srealloc(void *ptr, size_t size);
+
+/**
+ * Safe-wrapper around strdup which exits if malloc returns NULL (meaning that
+ * there is no more memory available)
+ *
+ */
+char *sstrdup(const char *str);
+
+/**
+ * Safe-wrapper around asprintf which exits if it returns -1 (meaning that
+ * there is no more memory available)
+ *
+ */
+int sasprintf(char **strp, const char *fmt, ...);
+
+/**
+ * Connects to the i3 IPC socket and returns the file descriptor for the
+ * socket. die()s if anything goes wrong.
+ *
+ */
+int ipc_connect(const char *socket_path);
+
+/**
+ * Formats a message (payload) of the given size and type and sends it to i3 via
+ * the given socket file descriptor.
+ *
+ * Returns -1 when write() fails, errno will remain.
+ * Returns 0 on success.
+ *
+ */
+int ipc_send_message(int sockfd, uint32_t message_size,
+                     uint32_t message_type, const uint8_t *payload);
+
+/**
+ * Reads a message from the given socket file descriptor and stores its length
+ * (reply_length) as well as a pointer to its contents (reply).
+ *
+ * Returns -1 when read() fails, errno will remain.
+ * Returns -2 when the IPC protocol is violated (invalid magic, unexpected
+ * message type, EOF instead of a message). Additionally, the error will be
+ * printed to stderr.
+ * Returns 0 on success.
+ *
+ */
+int ipc_recv_message(int sockfd, uint32_t message_type,
+                     uint32_t *reply_length, uint8_t **reply);
+
+/**
+ * Generates a configure_notify event and sends it to the given window
+ * Applications need this to think they’ve configured themselves correctly.
+ * The truth is, however, that we will manage them.
+ *
+ */
+void fake_configure_notify(xcb_connection_t *conn, xcb_rectangle_t r, xcb_window_t window, int border_width);
+
+/**
+ * Returns the colorpixel to use for the given hex color (think of HTML). Only
+ * works for true-color (vast majority of cases) at the moment, avoiding a
+ * roundtrip to X11.
+ *
+ * The hex_color has to start with #, for example #FF00FF.
+ *
+ * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
+ * This has to be done by the caller.
+ *
+ * NOTE that this function may in the future rely on a global xcb_connection_t
+ * variable called 'conn' to be present.
+ *
+ */
+uint32_t get_colorpixel(const char *hex) __attribute__((const));
+
+#if defined(__APPLE__)
+
+/*
+ * Taken from FreeBSD
+ * Returns a pointer to a new string which is a duplicate of the
+ * string, but only copies at most n characters.
+ *
+ */
+char *strndup(const char *str, size_t n);
+
+#endif
+
+/**
+ * All-in-one function which returns the modifier mask (XCB_MOD_MASK_*) for the
+ * given keysymbol, for example for XCB_NUM_LOCK (usually configured to mod2).
+ *
+ * This function initiates one round-trip. Use get_mod_mask_for() directly if
+ * you already have the modifier mapping and key symbols.
+ *
+ */
+uint32_t aio_get_mod_mask_for(uint32_t keysym, xcb_key_symbols_t *symbols);
+
+/**
+ * Returns the modifier mask (XCB_MOD_MASK_*) for the given keysymbol, for
+ * example for XCB_NUM_LOCK (usually configured to mod2).
+ *
+ * This function does not initiate any round-trips.
+ *
+ */
+uint32_t get_mod_mask_for(uint32_t keysym,
+                           xcb_key_symbols_t *symbols,
+                           xcb_get_modifier_mapping_reply_t *modmap_reply);
+
+/**
+ * Loads a font for usage, also getting its height. If fallback is true,
+ * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
+ *
+ */
+i3Font load_font(const char *pattern, bool fallback);
+
+#endif
index f3a60a097a8cdf4dd0a99bbf4d41c286633bb856..a2cd6d149d53f717ecdeb16ebbe3ee23171661f8 100644 (file)
@@ -1,3 +1,13 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * load_layout.c: Restore (parts of) the layout, for example after an inplace
+ *                restart.
+ *
+ */
 #ifndef _LOAD_LAYOUT_H
 #define _LOAD_LAYOUT_H
 
index c1e10b0639b321727d95efaf09783423ba8d1884..ef6deb20fe5a993e1e0224f37e70ad18caf5f861 100644 (file)
@@ -2,10 +2,9 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * log.c: Setting of loglevels, logging functions.
  *
  */
 #ifndef _LOG_H
index e23eccf36a9960f19712061e4471911754ff9f7b..833d614fa063867b1a1673fa430b986a5e7c253a 100644 (file)
@@ -1,19 +1,17 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * manage.c: Initially managing new windows (or existing ones on restart).
  *
  */
-
-#include "data.h"
-
 #ifndef _MANAGE_H
 #define _MANAGE_H
 
+#include "data.h"
+
 /**
  * Go through all existing windows (if the window manager is restarted) and
  * manage them
index 2786c66a8be7792cd25485f48ca7ea971b8d0667..6d9cb91539f4219cbfd5fc8ed9081dca2e5591db 100644 (file)
@@ -1,3 +1,16 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * A "match" is a data structure which acts like a mask or expression to match
+ * certain windows or not. For example, when using commands, you can specify a
+ * command like this: [title="*Firefox*"] kill. The title member of the match
+ * data structure will then be filled and i3 will check each window using
+ * match_matches_window() to find the windows affected by this command.
+ *
+ */
 #ifndef _MATCH_H
 #define _MATCH_H
 
@@ -28,4 +41,10 @@ void match_copy(Match *dest, Match *src);
  */
 bool match_matches_window(Match *match, i3Window *window);
 
+/**
+ * Frees the given match. It must not be used afterwards!
+ *
+ */
+void match_free(Match *match);
+
 #endif
index d0c97014034b831fcb06654ae5845c081a354140..22b6e809ef05b558a475ea8a7bfa5fb476a6e015 100644 (file)
@@ -1,7 +1,12 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * move.c: Moving containers into some direction.
+ *
  */
-
 #ifndef _MOVE_H
 #define _MOVE_H
 
index 67652fa102964aa713230ca2f3856b1354be76b5..d488ad30c4baef8b67c7654bd4da0ddccffead3b 100644 (file)
@@ -1,7 +1,12 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * output.c: Output (monitor) related functions.
+ *
  */
-
 #ifndef _OUTPUT_H
 #define _OUTPUT_H
 
index 9c09f2b1276b11f55e6fc306170bd9467cb34586..4cefba804ea7b13944a822dfa859885a721775f4 100644 (file)
@@ -1,19 +1,20 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * For more information on RandR, please see the X.org RandR specification at
+ * http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
+ * (take your time to read it completely, it answers all questions).
  *
  */
-#include "data.h"
-#include <xcb/randr.h>
-
 #ifndef _RANDR_H
 #define _RANDR_H
 
+#include "data.h"
+#include <xcb/randr.h>
+
 TAILQ_HEAD(outputs_head, xoutput);
 extern struct outputs_head outputs;
 
diff --git a/include/regex.h b/include/regex.h
new file mode 100644 (file)
index 0000000..d55bb6c
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * regex.c: Interface to libPCRE (perl compatible regular expressions).
+ *
+ */
+#ifndef _REGEX_H
+#define _REGEX_H
+
+/**
+ * Creates a new 'regex' struct containing the given pattern and a PCRE
+ * compiled regular expression. Also, calls pcre_study because this regex will
+ * most likely be used often (like for every new window and on every relevant
+ * property change of existing windows).
+ *
+ * Returns NULL if the pattern could not be compiled into a regular expression
+ * (and ELOGs an appropriate error message).
+ *
+ */
+struct regex *regex_new(const char *pattern);
+
+/**
+ * Frees the given regular expression. It must not be used afterwards!
+ *
+ */
+void regex_free(struct regex *regex);
+
+/**
+ * Checks if the given regular expression matches the given input and returns
+ * true if it does. In either case, it logs the outcome using LOG(), so it will
+ * be visible without any debug loglevel.
+ *
+ */
+bool regex_matches(struct regex *regex, const char *input);
+
+#endif
index 94084489d3331c7b6c1fd491e69002d778aa3ca7..1f31fb0f962e2bbc367dbd1240bbf3307ecd0e9d 100644 (file)
@@ -1,7 +1,13 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * render.c: Renders (determines position/sizes) the layout tree, updating the
+ *           various rects. Needs to be pushed to X11 (see x.c) to be visible.
+ *
  */
-
 #ifndef _RENDER_H
 #define _RENDER_H
 
index 5c8ea5d5d072ab92eb7f6f0a2089cbb23e8557b3..99646ea087a857b07d6412e24ec72dd74f6cf4d3 100644 (file)
@@ -1,6 +1,15 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * resize.c: Interactive resizing.
+ *
+ */
 #ifndef _RESIZE_H
 #define _RESIZE_H
 
-int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, xcb_button_press_event_t *event);
+int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
 
 #endif
diff --git a/include/sd-daemon.h b/include/sd-daemon.h
new file mode 100644 (file)
index 0000000..4b853a1
--- /dev/null
@@ -0,0 +1,265 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosddaemonhfoo
+#define foosddaemonhfoo
+
+/***
+  Copyright 2010 Lennart Poettering
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+  Reference implementation of a few systemd related interfaces for
+  writing daemons. These interfaces are trivial to implement. To
+  simplify porting we provide this reference implementation.
+  Applications are welcome to reimplement the algorithms described
+  here if they do not want to include these two source files.
+
+  The following functionality is provided:
+
+  - Support for logging with log levels on stderr
+  - File descriptor passing for socket-based activation
+  - Daemon startup and status notification
+  - Detection of systemd boots
+
+  You may compile this with -DDISABLE_SYSTEMD to disable systemd
+  support. This makes all those calls NOPs that are directly related to
+  systemd (i.e. only sd_is_xxx() will stay useful).
+
+  Since this is drop-in code we don't want any of our symbols to be
+  exported in any case. Hence we declare hidden visibility for all of
+  them.
+
+  You may find an up-to-date version of these source files online:
+
+  http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.h
+  http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.c
+
+  This should compile on non-Linux systems, too, but with the
+  exception of the sd_is_xxx() calls all functions will become NOPs.
+
+  See sd-daemon(7) for more information.
+*/
+
+#ifndef _sd_printf_attr_
+#if __GNUC__ >= 4
+#define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b)))
+#else
+#define _sd_printf_attr_(a,b)
+#endif
+#endif
+
+#ifndef _sd_hidden_
+#if (__GNUC__ >= 4) && !defined(SD_EXPORT_SYMBOLS)
+#define _sd_hidden_ __attribute__ ((visibility("hidden")))
+#else
+#define _sd_hidden_
+#endif
+#endif
+
+/*
+  Log levels for usage on stderr:
+
+          fprintf(stderr, SD_NOTICE "Hello World!\n");
+
+  This is similar to printk() usage in the kernel.
+*/
+#define SD_EMERG   "<0>"  /* system is unusable */
+#define SD_ALERT   "<1>"  /* action must be taken immediately */
+#define SD_CRIT    "<2>"  /* critical conditions */
+#define SD_ERR     "<3>"  /* error conditions */
+#define SD_WARNING "<4>"  /* warning conditions */
+#define SD_NOTICE  "<5>"  /* normal but significant condition */
+#define SD_INFO    "<6>"  /* informational */
+#define SD_DEBUG   "<7>"  /* debug-level messages */
+
+/* The first passed file descriptor is fd 3 */
+#define SD_LISTEN_FDS_START 3
+
+/*
+  Returns how many file descriptors have been passed, or a negative
+  errno code on failure. Optionally, removes the $LISTEN_FDS and
+  $LISTEN_PID file descriptors from the environment (recommended, but
+  problematic in threaded environments). If r is the return value of
+  this function you'll find the file descriptors passed as fds
+  SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative
+  errno style error code on failure. This function call ensures that
+  the FD_CLOEXEC flag is set for the passed file descriptors, to make
+  sure they are not passed on to child processes. If FD_CLOEXEC shall
+  not be set, the caller needs to unset it after this call for all file
+  descriptors that are used.
+
+  See sd_listen_fds(3) for more information.
+*/
+int sd_listen_fds(int unset_environment) _sd_hidden_;
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is a FIFO in the file system stored under the
+  specified path, 0 otherwise. If path is NULL a path name check will
+  not be done and the call only verifies if the file descriptor
+  refers to a FIFO. Returns a negative errno style error code on
+  failure.
+
+  See sd_is_fifo(3) for more information.
+*/
+int sd_is_fifo(int fd, const char *path) _sd_hidden_;
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is a socket of the specified family (AF_INET,
+  ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If
+  family is 0 a socket family check will not be done. If type is 0 a
+  socket type check will not be done and the call only verifies if
+  the file descriptor refers to a socket. If listening is > 0 it is
+  verified that the socket is in listening mode. (i.e. listen() has
+  been called) If listening is == 0 it is verified that the socket is
+  not in listening mode. If listening is < 0 no listening mode check
+  is done. Returns a negative errno style error code on failure.
+
+  See sd_is_socket(3) for more information.
+*/
+int sd_is_socket(int fd, int family, int type, int listening) _sd_hidden_;
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is an Internet socket, of the specified family
+  (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM,
+  SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version
+  check is not done. If type is 0 a socket type check will not be
+  done. If port is 0 a socket port check will not be done. The
+  listening flag is used the same way as in sd_is_socket(). Returns a
+  negative errno style error code on failure.
+
+  See sd_is_socket_inet(3) for more information.
+*/
+int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) _sd_hidden_;
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is an AF_UNIX socket of the specified type
+  (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0
+  a socket type check will not be done. If path is NULL a socket path
+  check will not be done. For normal AF_UNIX sockets set length to
+  0. For abstract namespace sockets set length to the length of the
+  socket name (including the initial 0 byte), and pass the full
+  socket path in path (including the initial 0 byte). The listening
+  flag is used the same way as in sd_is_socket(). Returns a negative
+  errno style error code on failure.
+
+  See sd_is_socket_unix(3) for more information.
+*/
+int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) _sd_hidden_;
+
+/*
+  Informs systemd about changed daemon state. This takes a number of
+  newline separated environment-style variable assignments in a
+  string. The following variables are known:
+
+     READY=1      Tells systemd that daemon startup is finished (only
+                  relevant for services of Type=notify). The passed
+                  argument is a boolean "1" or "0". Since there is
+                  little value in signaling non-readiness the only
+                  value daemons should send is "READY=1".
+
+     STATUS=...   Passes a single-line status string back to systemd
+                  that describes the daemon state. This is free-from
+                  and can be used for various purposes: general state
+                  feedback, fsck-like programs could pass completion
+                  percentages and failing programs could pass a human
+                  readable error message. Example: "STATUS=Completed
+                  66% of file system check..."
+
+     ERRNO=...    If a daemon fails, the errno-style error code,
+                  formatted as string. Example: "ERRNO=2" for ENOENT.
+
+     BUSERROR=... If a daemon fails, the D-Bus error-style error
+                  code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut"
+
+     MAINPID=...  The main pid of a daemon, in case systemd did not
+                  fork off the process itself. Example: "MAINPID=4711"
+
+  Daemons can choose to send additional variables. However, it is
+  recommended to prefix variable names not listed above with X_.
+
+  Returns a negative errno-style error code on failure. Returns > 0
+  if systemd could be notified, 0 if it couldn't possibly because
+  systemd is not running.
+
+  Example: When a daemon finished starting up, it could issue this
+  call to notify systemd about it:
+
+     sd_notify(0, "READY=1");
+
+  See sd_notifyf() for more complete examples.
+
+  See sd_notify(3) for more information.
+*/
+int sd_notify(int unset_environment, const char *state) _sd_hidden_;
+
+/*
+  Similar to sd_notify() but takes a format string.
+
+  Example 1: A daemon could send the following after initialization:
+
+     sd_notifyf(0, "READY=1\n"
+                   "STATUS=Processing requests...\n"
+                   "MAINPID=%lu",
+                   (unsigned long) getpid());
+
+  Example 2: A daemon could send the following shortly before
+  exiting, on failure:
+
+     sd_notifyf(0, "STATUS=Failed to start up: %s\n"
+                   "ERRNO=%i",
+                   strerror(errno),
+                   errno);
+
+  See sd_notifyf(3) for more information.
+*/
+int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3) _sd_hidden_;
+
+/*
+  Returns > 0 if the system was booted with systemd. Returns < 0 on
+  error. Returns 0 if the system was not booted with systemd. Note
+  that all of the functions above handle non-systemd boots just
+  fine. You should NOT protect them with a call to this function. Also
+  note that this function checks whether the system, not the user
+  session is controlled by systemd. However the functions above work
+  for both user and system services.
+
+  See sd_booted(3) for more information.
+*/
+int sd_booted(void) _sd_hidden_;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
index 02a4f5dd6c37b99b2b03b00a479b09bf0fc48238..5ffef2a35cc3ef5dccf14b3c9b6c92f4e5e915b2 100644 (file)
@@ -2,11 +2,11 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  * © 2009-2010 Jan-Erik Rediger
  *
- * See file LICENSE for license information.
+ * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
+ *               to restart inplace).
  *
  */
 #ifndef _SIGHANDLER_H
diff --git a/include/startup.h b/include/startup.h
new file mode 100644 (file)
index 0000000..290c8d2
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * startup.c: Startup notification code. Ensures a startup notification context
+ *            is setup when launching applications. We store the current
+ *            workspace to open windows in that startup notification context on
+ *            the appropriate workspace.
+ *
+ */
+#ifndef _STARTUP_H
+#define _STARTUP_H
+
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-monitor.h>
+
+/**
+ * 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), the application is reparented to init (process-id 1), which
+ * correctly handles childs, so we don’t have to do it :-).
+ *
+ * The shell is determined by looking for the SHELL environment variable. If
+ * it does not exist, /bin/sh is used.
+ *
+ * The no_startup_id flag determines whether a startup notification context
+ * (and ID) should be created, which is the default and encouraged behavior.
+ *
+ */
+void start_application(const char *command, bool no_startup_id);
+
+/**
+ * Called by libstartup-notification when something happens
+ *
+ */
+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.
+ *
+ */
+char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply);
+
+#endif
index b483434fcdaad90891cbdf09d8fd5a4f8f34ade7..81fdbe6acab95374499b138a4d09f2b9eaf18e4d 100644 (file)
@@ -1,7 +1,12 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * tree.c: Everything that primarily modifies the layout tree data structure.
+ *
  */
-
 #ifndef _TREE_H
 #define _TREE_H
 
index edc51d812de74580d6387aca300a70a5e4c597df..4a5920d2fbfa692627f17a09bc34a3fe5e8d1b97 100644 (file)
@@ -1,20 +1,20 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * util.c: Utility functions, which can be useful everywhere within i3 (see
+ *         also libi3).
  *
  */
+#ifndef _UTIL_H
+#define _UTIL_H
+
 #include <err.h>
 
 #include "data.h"
 
-#ifndef _UTIL_H
-#define _UTIL_H
-
 #define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
 #define exit_if_null(pointer, ...) { if (pointer == NULL) die(__VA_ARGS__); }
 #define STARTS_WITH(string, needle) (strncasecmp(string, needle, strlen(needle)) == 0)
@@ -66,46 +66,6 @@ Rect rect_add(Rect a, Rect b);
  */
 bool update_if_necessary(uint32_t *destination, const uint32_t new_value);
 
-/**
- * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that
- * there is no more memory available)
- *
- */
-void *smalloc(size_t size);
-
-/**
- * Safe-wrapper around calloc which exits if malloc returns NULL (meaning that
- * there is no more memory available)
- *
- */
-void *scalloc(size_t size);
-
-/**
- * Safe-wrapper around realloc which exits if realloc returns NULL (meaning
- * that there is no more memory available).
- *
- */
-void *srealloc(void *ptr, size_t size);
-
-/**
- * Safe-wrapper around strdup which exits if malloc returns NULL (meaning that
- * there is no more memory available)
- *
- */
-char *sstrdup(const char *str);
-
-/**
- * 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), the application is reparented to init (process-id 1), which
- * correctly handles childs, so we don’t have to do it :-).
- *
- * The shell is determined by looking for the SHELL environment variable. If
- * it does not exist, /bin/sh is used.
- *
- */
-void start_application(const char *command);
-
 /**
  * exec()s an i3 utility, for example the config file migration script or
  * i3-nagbar. This function first searches $PATH for the given utility named,
@@ -179,16 +139,4 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);
 
 #endif
 
-#if defined(__APPLE__)
-
-/*
- * Taken from FreeBSD
- * Returns a pointer to a new string which is a duplicate of the
- * string, but only copies at most n characters.
- *
- */
-char *strndup(const char *str, size_t n);
-
-#endif
-
 #endif
index fe282aa08208f2bc8d9e55888b3b1575fb041852..61ec8614b52ef783d4df6fc46d670934ed7ae0fa 100644 (file)
@@ -1,3 +1,12 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * window.c: Updates window attributes (X11 hints/properties).
+ *
+ */
 #ifndef _WINDOW_H
 #define _WINDOW_H
 
@@ -42,4 +51,10 @@ void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop);
  */
 void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop);
 
+/**
+ * Updates the WM_WINDOW_ROLE
+ *
+ */
+void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
+
 #endif
index aebf13656d866e8b15e3de5a03678f3bb9f3baed..995499f2c939706877694b3f35ff9d0c777a6d52 100644 (file)
@@ -1,21 +1,20 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * workspace.c: Modifying workspaces, accessing them, moving containers to
+ *              workspaces.
  *
  */
+#ifndef _WORKSPACE_H
+#define _WORKSPACE_H
 
 #include "data.h"
 #include "tree.h"
 #include "randr.h"
 
-#ifndef _WORKSPACE_H
-#define _WORKSPACE_H
-
 /**
  * Returns a pointer to the workspace with the given number (starting at 0),
  * creating the workspace if necessary (by allocating the necessary amount of
@@ -46,20 +45,36 @@ void workspace_set_name(Workspace *ws, const char *name);
  */
 bool workspace_is_visible(Con *ws);
 
-/** Switches to the given workspace */
-void workspace_show(const char *num);
+/**
+ * Switches to the given workspace
+ *
+ */
+void workspace_show(Con *ws);
+
+/**
+ * Looks up the workspace by name and switches to it.
+ *
+ */
+void workspace_show_by_name(const char *num);
 
 /**
- * Focuses the next workspace.
+ * Returns the next workspace.
  *
  */
-void workspace_next();
+Con* workspace_next();
 
 /**
- * Focuses the previous workspace.
+ * Returns the previous workspace.
  *
  */
-void workspace_prev();
+Con* workspace_prev();
+
+/**
+ * Focuses the previously focused workspace.
+ *
+ */
+void workspace_back_and_forth();
+
 
 #if 0
 /**
index 29a8bec20c87d26b8278937674302ebbeb78605f..2d41b4373a1586d22b7e360145e4b4577beff182 100644 (file)
@@ -1,7 +1,13 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * x.c: Interface to X11, transfers our in-memory state to X11 (see also
+ *      render.c). Basically a big state machine.
+ *
  */
-
 #ifndef _X_H
 #define _X_H
 
index 185163b434a5c895240753e25b5c39920a220d30..01e2b66728ec28b022e6eaa7b5308b78b9dd2f5e 100644 (file)
@@ -2,10 +2,9 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * xcb.c: Helper functions for easier usage of XCB
  *
  */
 #ifndef _XCB_H
@@ -23,6 +22,7 @@
 #define XCB_CURSOR_LEFT_PTR     68
 #define XCB_CURSOR_SB_H_DOUBLE_ARROW 108
 #define XCB_CURSOR_SB_V_DOUBLE_ARROW 116
+#define XCB_CURSOR_WATCH 150
 
 /* from X11/keysymdef.h */
 #define XCB_NUM_LOCK                    0xff7f
 
 extern unsigned int xcb_numlock_mask;
 
-/**
- * Loads a font for usage, also getting its height. If fallback is true,
- * i3 loads 'fixed' or '-misc-*' if the font cannot be found instead of
- * exiting.
- *
- */
-i3Font load_font(const char *pattern, bool fallback);
-
-/**
- * Returns the colorpixel to use for the given hex color (think of HTML).
- *
- * The hex_color has to start with #, for example #FF00FF.
- *
- * NOTE that get_colorpixel() does _NOT_ check the given color code for
- * validity.  This has to be done by the caller.
- *
- */
-uint32_t get_colorpixel(char *hex);
-
 /**
  * Convenience wrapper around xcb_create_window which takes care of depth,
  * generating an ID and checking for errors.
@@ -78,14 +59,6 @@ uint32_t get_colorpixel(char *hex);
 xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t window_class,
         enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values);
 
-/**
- * Changes a single value in the graphic context (so one doesn’t have to
- * define an array of values)
- *
- */
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc,
-                          uint32_t mask, uint32_t value);
-
 /**
  * Draws a line from x,y to to_x,to_y using the given color
  *
@@ -102,14 +75,6 @@ void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable,
                    xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x,
                    uint32_t y, uint32_t width, uint32_t height);
 
-/**
- * Generates a configure_notify event and sends it to the given window
- * Applications need this to think they’ve configured themselves correctly.
- * The truth is, however, that we will manage them.
- *
- */
-void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window, int border_width);
-
 /**
  * Generates a configure_notify_event with absolute coordinates (relative to
  * the X root window, not to the client’s frame) for the given client.
@@ -123,13 +88,6 @@ void fake_absolute_configure_notify(Con *con);
  */
 void send_take_focus(xcb_window_t window);
 
-/**
- * Finds out which modifier mask is the one for numlock, as the user may
- * change this.
- *
- */
-void xcb_get_numlock_mask(xcb_connection_t *conn);
-
 /**
  * Raises the given window (typically client->frame) above all other windows
  *
@@ -158,4 +116,12 @@ bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom);
  */
 void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect);
 
+/**
+ * Set the cursor of the root window to the given cursor id.
+ * This function should only be used if xcursor_supported == false.
+ * Otherwise, use xcursor_set_root_cursor().
+ *
+ */
+void xcb_set_root_cursor(int cursor);
+
 #endif
index ec872a3abeabf3eb9d4cfe4e39cc1f826680e887..260ac4e56a996bd5c2cdc34946efb6ea0897f8d3 100644 (file)
@@ -1,3 +1,14 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * xcb_compat.h: uses #define to create aliases for xcb functions which got
+ *               renamed. Makes the code work with >= 0.3.8 xcb-util and
+ *               older versions.
+ *
+ */
 #ifndef _XCB_COMPAT_H
 #define _XCB_COMPAT_H
 
index e129a36fe11a39db36f3378e7b102647d1f2f149..b512ca237f7f4a0cc86529a934998cafc7bdf4af 100644 (file)
@@ -1,5 +1,11 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * xcursor.c: libXcursor support for themed cursors.
+ *
  */
 #ifndef _XCURSOR_CURSOR_H
 #define _XCURSOR_CURSOR_H
@@ -10,6 +16,7 @@ enum xcursor_cursor_t {
     XCURSOR_CURSOR_POINTER = 0,
     XCURSOR_CURSOR_RESIZE_HORIZONTAL,
     XCURSOR_CURSOR_RESIZE_VERTICAL,
+    XCURSOR_CURSOR_WATCH,
     XCURSOR_CURSOR_MAX
 };
 
index 600b77f338d44068bcb775d36792d132a99e76eb..e6304b56bea79f4ab30760a3fb14caadd75ff25f 100644 (file)
@@ -1,18 +1,19 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * This is LEGACY code (we support RandR, which can do much more than
+ * Xinerama), but necessary for the poor users of the nVidia binary
+ * driver which does not support RandR in 2011 *sigh*.
  *
  */
-#include "data.h"
-
 #ifndef _XINERAMA_H
 #define _XINERAMA_H
 
+#include "data.h"
+
 /**
  * We have just established a connection to the X server and need the initial
  * Xinerama information to setup workspaces for each screen.
diff --git a/libi3/Makefile b/libi3/Makefile
new file mode 100644 (file)
index 0000000..e9efcf7
--- /dev/null
@@ -0,0 +1,26 @@
+# Default value so one can compile i3-msg standalone
+TOPDIR=..
+
+include $(TOPDIR)/common.mk
+
+CFLAGS += -I$(TOPDIR)/include
+
+# Depend on the object files of all source-files in src/*.c and on all header files
+FILES=$(patsubst %.c,%.o,$(wildcard *.c))
+HEADERS=$(wildcard *.h)
+
+# Depend on the specific file (.c for each .o) and on all headers
+%.o: %.c ${HEADERS}
+       echo "[libi3] CC $<"
+       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+all: libi3.a
+
+libi3.a: ${FILES}
+       echo "[libi3] AR libi3.a"
+       ar rcs libi3.a ${FILES}
+
+clean:
+       rm -f *.o libi3.a
+
+distclean: clean
diff --git a/libi3/README b/libi3/README
new file mode 100644 (file)
index 0000000..740ef22
--- /dev/null
@@ -0,0 +1,16 @@
+Introduction
+============
+
+libi3 is an *INTERNAL* library which contains functions that i3 and related
+tools (i3-msg, i3-input, i3-nagbar, i3-config-wizard, i3bar) use.
+
+It is NOT to be used by other programs.
+
+Structure
+=========
+
+Every function gets its own .c file, which in turn gets compiled into an .o
+object file. Afterwards, all .o files are archived into one static library
+(libi3.a). This library will be linked into all i3 binaries. The linker is able
+to eliminate unused .o files when linking, so only the functions which you
+actually use will be included in the corresponding binary.
diff --git a/libi3/fake_configure_notify.c b/libi3/fake_configure_notify.c
new file mode 100644 (file)
index 0000000..472d235
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xproto.h>
+
+#include "libi3.h"
+
+/*
+ * Generates a configure_notify event and sends it to the given window
+ * Applications need this to think they’ve configured themselves correctly.
+ * The truth is, however, that we will manage them.
+ *
+ */
+void fake_configure_notify(xcb_connection_t *conn, xcb_rectangle_t r, xcb_window_t window, int border_width) {
+    /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
+     * In order to properly initialize these bytes, we allocate 32 bytes even
+     * though we only need less for an xcb_configure_notify_event_t */
+    void *event = scalloc(32);
+    xcb_configure_notify_event_t *generated_event = event;
+
+    generated_event->event = window;
+    generated_event->window = window;
+    generated_event->response_type = XCB_CONFIGURE_NOTIFY;
+
+    generated_event->x = r.x;
+    generated_event->y = r.y;
+    generated_event->width = r.width;
+    generated_event->height = r.height;
+
+    generated_event->border_width = border_width;
+    generated_event->above_sibling = XCB_NONE;
+    generated_event->override_redirect = false;
+
+    xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)generated_event);
+    xcb_flush(conn);
+
+    free(event);
+}
diff --git a/libi3/get_colorpixel.c b/libi3/get_colorpixel.c
new file mode 100644 (file)
index 0000000..73bbef3
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "libi3.h"
+
+/*
+ * Returns the colorpixel to use for the given hex color (think of HTML). Only
+ * works for true-color (vast majority of cases) at the moment, avoiding a
+ * roundtrip to X11.
+ *
+ * The hex_color has to start with #, for example #FF00FF.
+ *
+ * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
+ * This has to be done by the caller.
+ *
+ * NOTE that this function may in the future rely on a global xcb_connection_t
+ * variable called 'conn' to be present.
+ *
+ */
+uint32_t get_colorpixel(const char *hex) {
+    char strgroups[3][3] = {{hex[1], hex[2], '\0'},
+                            {hex[3], hex[4], '\0'},
+                            {hex[5], hex[6], '\0'}};
+    uint8_t r = strtol(strgroups[0], NULL, 16);
+    uint8_t g = strtol(strgroups[1], NULL, 16);
+    uint8_t b = strtol(strgroups[2], NULL, 16);
+
+    return (r << 16 | g << 8 | b);
+}
diff --git a/libi3/get_mod_mask.c b/libi3/get_mod_mask.c
new file mode 100644 (file)
index 0000000..752cea5
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_keysyms.h>
+
+#include "libi3.h"
+
+extern xcb_connection_t *conn;
+
+/*
+ * All-in-one function which returns the modifier mask (XCB_MOD_MASK_*) for the
+ * given keysymbol, for example for XCB_NUM_LOCK (usually configured to mod2).
+ *
+ * This function initiates one round-trip. Use get_mod_mask_for() directly if
+ * you already have the modifier mapping and key symbols.
+ *
+ */
+uint32_t aio_get_mod_mask_for(uint32_t keysym, xcb_key_symbols_t *symbols) {
+    xcb_get_modifier_mapping_cookie_t cookie;
+    xcb_get_modifier_mapping_reply_t *modmap_r;
+
+    xcb_flush(conn);
+
+    /* Get the current modifier mapping (this is blocking!) */
+    cookie = xcb_get_modifier_mapping(conn);
+    if (!(modmap_r = xcb_get_modifier_mapping_reply(conn, cookie, NULL)))
+        return 0;
+
+    uint32_t result = get_mod_mask_for(keysym, symbols, modmap_r);
+    free(modmap_r);
+    return result;
+}
+
+/*
+ * Returns the modifier mask (XCB_MOD_MASK_*) for the given keysymbol, for
+ * example for XCB_NUM_LOCK (usually configured to mod2).
+ *
+ * This function does not initiate any round-trips.
+ *
+ */
+uint32_t get_mod_mask_for(uint32_t keysym,
+                           xcb_key_symbols_t *symbols,
+                           xcb_get_modifier_mapping_reply_t *modmap_reply) {
+    xcb_keycode_t *codes, *modmap;
+    xcb_keycode_t mod_code;
+
+    modmap = xcb_get_modifier_mapping_keycodes(modmap_reply);
+
+    /* Get the list of keycodes for the given symbol */
+    if (!(codes = xcb_key_symbols_get_keycode(symbols, keysym)))
+        return 0;
+
+    /* Loop through all modifiers (Mod1-Mod5, Shift, Control, Lock) */
+    for (int mod = 0; mod < 8; mod++)
+        for (int j = 0; j < modmap_reply->keycodes_per_modifier; j++) {
+            /* Store the current keycode (for modifier 'mod') */
+            mod_code = modmap[(mod * modmap_reply->keycodes_per_modifier) + j];
+            /* Check if that keycode is in the list of previously resolved
+             * keycodes for our symbol. If so, return the modifier mask. */
+            for (xcb_keycode_t *code = codes; *code; code++) {
+                if (*code != mod_code)
+                    continue;
+
+                free(codes);
+                /* This corresponds to the XCB_MOD_MASK_* constants */
+                return (1 << mod);
+            }
+        }
+
+    return 0;
+}
diff --git a/libi3/get_socket_path.c b/libi3/get_socket_path.c
new file mode 100644 (file)
index 0000000..d623b6c
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <limits.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+
+#include "libi3.h"
+
+/*
+ * Try to get the socket path from X11 and return NULL if it doesn’t work.
+ *
+ * The memory for the socket path is dynamically allocated and has to be
+ * free()d by the caller.
+ *
+ */
+char *socket_path_from_x11() {
+    xcb_connection_t *conn;
+    xcb_intern_atom_cookie_t atom_cookie;
+    xcb_intern_atom_reply_t *atom_reply;
+    int screen;
+    char *socket_path;
+
+    if ((conn = xcb_connect(NULL, &screen)) == NULL ||
+        xcb_connection_has_error(conn))
+        return NULL;
+
+    atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
+
+    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
+    xcb_window_t root = root_screen->root;
+
+    atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
+    if (atom_reply == NULL)
+        return NULL;
+
+    xcb_get_property_cookie_t prop_cookie;
+    xcb_get_property_reply_t *prop_reply;
+    prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
+                                             XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
+    prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
+    if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
+        return NULL;
+    if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
+                 (char*)xcb_get_property_value(prop_reply)) == -1)
+        return NULL;
+    xcb_disconnect(conn);
+    return socket_path;
+}
+
diff --git a/libi3/ipc_connect.c b/libi3/ipc_connect.c
new file mode 100644 (file)
index 0000000..44ff705
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <err.h>
+#include <stdlib.h>
+
+#include "libi3.h"
+
+/*
+ * Connects to the i3 IPC socket and returns the file descriptor for the
+ * socket. die()s if anything goes wrong.
+ *
+ */
+int ipc_connect(const char *socket_path) {
+    int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+    if (sockfd == -1)
+        err(EXIT_FAILURE, "Could not create socket");
+
+    struct sockaddr_un addr;
+    memset(&addr, 0, sizeof(struct sockaddr_un));
+    addr.sun_family = AF_LOCAL;
+    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
+    if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
+        err(EXIT_FAILURE, "Could not connect to i3");
+
+    return sockfd;
+}
diff --git a/libi3/ipc_recv_message.c b/libi3/ipc_recv_message.c
new file mode 100644 (file)
index 0000000..2a2f85c
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <i3/ipc.h>
+
+#include "libi3.h"
+
+/*
+ * Reads a message from the given socket file descriptor and stores its length
+ * (reply_length) as well as a pointer to its contents (reply).
+ *
+ * Returns -1 when read() fails, errno will remain.
+ * Returns -2 when the IPC protocol is violated (invalid magic, unexpected
+ * message type, EOF instead of a message). Additionally, the error will be
+ * printed to stderr.
+ * Returns 0 on success.
+ *
+ */
+int ipc_recv_message(int sockfd, uint32_t message_type,
+                     uint32_t *reply_length, uint8_t **reply) {
+    /* Read the message header first */
+    uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
+    char msg[to_read];
+    char *walk = msg;
+
+    uint32_t read_bytes = 0;
+    while (read_bytes < to_read) {
+        int n = read(sockfd, msg + read_bytes, to_read);
+        if (n == -1)
+            return -1;
+        if (n == 0) {
+            fprintf(stderr, "IPC: received EOF instead of reply\n");
+            return -2;
+        }
+
+        read_bytes += n;
+        to_read -= n;
+    }
+
+    if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
+        fprintf(stderr, "IPC: invalid magic in reply\n");
+        return -2;
+    }
+
+    walk += strlen(I3_IPC_MAGIC);
+    *reply_length = *((uint32_t*)walk);
+    walk += sizeof(uint32_t);
+    if (*((uint32_t*)walk) != message_type) {
+        fprintf(stderr, "IPC: unexpected reply type (got %d, expected %d)\n", *((uint32_t*)walk), message_type);
+        return -2;
+    }
+    walk += sizeof(uint32_t);
+
+    *reply = smalloc(*reply_length);
+
+    to_read = *reply_length;
+    read_bytes = 0;
+    while (read_bytes < to_read) {
+        int n = read(sockfd, *reply + read_bytes, to_read);
+        if (n == -1)
+            return -1;
+
+        read_bytes += n;
+        to_read -= n;
+    }
+
+    return 0;
+}
diff --git a/libi3/ipc_send_message.c b/libi3/ipc_send_message.c
new file mode 100644 (file)
index 0000000..850fbdd
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <err.h>
+
+#include <i3/ipc.h>
+
+#include "libi3.h"
+
+/*
+ * Formats a message (payload) of the given size and type and sends it to i3 via
+ * the given socket file descriptor.
+ *
+ * Returns -1 when write() fails, errno will remain.
+ * Returns 0 on success.
+ *
+ */
+int ipc_send_message(int sockfd, uint32_t message_size,
+                     uint32_t message_type, const uint8_t *payload) {
+    int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
+    char msg[buffer_size];
+    char *walk = msg;
+
+    strncpy(walk, I3_IPC_MAGIC, buffer_size - 1);
+    walk += strlen(I3_IPC_MAGIC);
+    memcpy(walk, &message_size, sizeof(uint32_t));
+    walk += sizeof(uint32_t);
+    memcpy(walk, &message_type, sizeof(uint32_t));
+    walk += sizeof(uint32_t);
+    memcpy(walk, payload, message_size);
+
+    int sent_bytes = 0;
+    int bytes_to_go = buffer_size;
+    while (sent_bytes < bytes_to_go) {
+        int n = write(sockfd, msg + sent_bytes, bytes_to_go);
+        if (n == -1)
+            return -1;
+
+        sent_bytes += n;
+        bytes_to_go -= n;
+    }
+
+    return 0;
+}
diff --git a/libi3/load_font.c b/libi3/load_font.c
new file mode 100644 (file)
index 0000000..acb52c0
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <err.h>
+
+#include "libi3.h"
+
+extern xcb_connection_t *conn;
+
+/*
+ * Loads a font for usage, also getting its height. If fallback is true,
+ * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
+ *
+ */
+i3Font load_font(const char *pattern, bool fallback) {
+    i3Font font;
+    xcb_void_cookie_t font_cookie;
+    xcb_list_fonts_with_info_cookie_t info_cookie;
+    xcb_list_fonts_with_info_reply_t *info_reply;
+    xcb_generic_error_t *error;
+
+    /* Send all our requests first */
+    font.id = xcb_generate_id(conn);
+    font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
+    info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
+
+    /* Check for errors. If errors, fall back to default font. */
+    error = xcb_request_check(conn, font_cookie);
+
+    /* If we fail to open font, fall back to 'fixed' */
+    if (fallback && error != NULL) {
+        ELOG("Could not open font %s (X error %d). Trying fallback to 'fixed'.\n",
+             pattern, error->error_code);
+        pattern = "fixed";
+        font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
+        info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
+
+        /* Check if we managed to open 'fixed' */
+        error = xcb_request_check(conn, font_cookie);
+
+        /* Fall back to '-misc-*' if opening 'fixed' fails. */
+        if (error != NULL) {
+            ELOG("Could not open fallback font 'fixed', trying with '-misc-*'.\n");
+            pattern = "-misc-*";
+            font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
+            info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
+
+            if ((error = xcb_request_check(conn, font_cookie)) != NULL)
+                errx(EXIT_FAILURE, "Could open neither requested font nor fallbacks "
+                     "(fixed or -misc-*): X11 error %d", error->error_code);
+        }
+    }
+
+    /* Get information (height/name) for this font */
+    if (!(info_reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL)))
+        errx(EXIT_FAILURE, "Could not load font \"%s\"", pattern);
+
+    font.height = info_reply->font_ascent + info_reply->font_descent;
+
+    free(info_reply);
+
+    return font;
+}
diff --git a/libi3/safewrappers.c b/libi3/safewrappers.c
new file mode 100644 (file)
index 0000000..cf634ad
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <err.h>
+
+#include "libi3.h"
+
+/*
+ * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of
+ * the called functions returns NULL, meaning that there is no more memory available
+ *
+ */
+void *smalloc(size_t size) {
+    void *result = malloc(size);
+    if (result == NULL)
+        err(EXIT_FAILURE, "malloc(%zd)", size);
+    return result;
+}
+
+void *scalloc(size_t size) {
+    void *result = calloc(size, 1);
+    if (result == NULL)
+        err(EXIT_FAILURE, "calloc(%zd)", size);
+    return result;
+}
+
+void *srealloc(void *ptr, size_t size) {
+    void *result = realloc(ptr, size);
+    if (result == NULL && size > 0)
+        err(EXIT_FAILURE, "realloc(%zd)", size);
+    return result;
+}
+
+char *sstrdup(const char *str) {
+    char *result = strdup(str);
+    if (result == NULL)
+        err(EXIT_FAILURE, "strdup()");
+    return result;
+}
+
+int sasprintf(char **strp, const char *fmt, ...) {
+    va_list args;
+    int result;
+
+    va_start(args, fmt);
+    if ((result = vasprintf(strp, fmt, args)) == -1)
+        err(EXIT_FAILURE, "asprintf(%s)", fmt);
+    va_end(args);
+    return result;
+}
diff --git a/libi3/strndup.c b/libi3/strndup.c
new file mode 100644 (file)
index 0000000..eec1a0e
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <sys/types.h>
+#include <string.h>
+
+#include "libi3.h"
+
+#if defined(__APPLE__)
+
+/*
+ * Taken from FreeBSD
+ * Returns a pointer to a new string which is a duplicate of the
+ * string, but only copies at most n characters.
+ *
+ */
+char *strndup(const char *str, size_t n) {
+    size_t len;
+    char *copy;
+
+    for (len = 0; len < n && str[len]; len++)
+        continue;
+
+    copy = smalloc(len + 1);
+    memcpy(copy, str, len);
+    copy[len] = '\0';
+    return (copy);
+}
+
+#endif
index facea28732c1f83b0157422af8ac5b82a69a68dc..44b2df6e6e88e2337d30f934eaf5f6cba2033249 100644 (file)
@@ -1,6 +1,6 @@
 A2M:=a2x -f manpage --asciidoc-opts="-f asciidoc.conf"
 
-all: i3.1 i3-msg.1 i3-input.1 i3-nagbar.1 i3-wsbar.1 i3-config-wizard.1 i3-migrate-config-to-v4.1
+all: i3.1 i3-msg.1 i3-input.1 i3-nagbar.1 i3-wsbar.1 i3-config-wizard.1 i3-migrate-config-to-v4.1 i3-sensible-editor.1 i3-sensible-pager.1 i3-sensible-terminal.1
 
 %.1: %.man asciidoc.conf
        ${A2M} $<
@@ -9,7 +9,7 @@ i3-wsbar.1: ../i3-wsbar
        pod2man $^ > $@
 
 clean:
-       for file in $$(echo i3 i3-msg i3-input i3-nagbar i3-wsbar i3-config-wizard i3-migrate-config-to-v4); \
+       for file in $$(echo i3 i3-msg i3-input i3-nagbar i3-wsbar i3-config-wizard i3-migrate-config-to-v4 i3-sensible-editor i3-sensible-pager i3-sensible-terminal); \
        do \
                rm -f $${file}.1 $${file}.html $${file}.xml; \
        done
index cca0680bfe0b17fd70d12c7bbe3516f4dafa095e..ead3567ff477c7cbb6b77d2b93bdcae797b7af29 100644 (file)
@@ -7,7 +7,7 @@ template::[header-declarations]
 <refentrytitle>{mantitle}</refentrytitle>
 <manvolnum>{manvolnum}</manvolnum>
 <refmiscinfo class="source">i3</refmiscinfo>
-<refmiscinfo class="version">4.0.2</refmiscinfo>
+<refmiscinfo class="version">4.1</refmiscinfo>
 <refmiscinfo class="manual">i3 Manual</refmiscinfo>
 </refmeta>
 <refnamediv>
index cd85c92c622e62d0b82a50c43128e12cb57882ea..df7c4d9ce82bd85845230acc959b9829d71c25fb 100644 (file)
@@ -1,7 +1,7 @@
 i3-input(1)
 =========
 Michael Stapelberg <michael+i3@stapelberg.de>
-v3.delta, November 2009
+v4.1, September 2011
 
 == NAME
 
@@ -9,7 +9,7 @@ i3-input - interactively take a command for i3 window manager
 
 == SYNOPSIS
 
-i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]
+i3-input [-s <socket>] [-F <format>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]
 
 == DESCRIPTION
 
@@ -17,19 +17,28 @@ i3-input is a tool to take commands (or parts of a command) composed by
 the user, and send it/them to i3. This is useful, for example, for the
 mark/goto command.
 
+The -F option takes a format string. In this string, every occurence of %s is
+replaced by the user input.
+
 == EXAMPLE
 
 ------------------------------------------------
-i3-input -p 'mark ' -l 1 -P 'Mark: '
+i3-input -F 'mark %s' -l 1 -P 'Mark: '
 ------------------------------------------------
 
 == ENVIRONMENT
 
 === I3SOCK
 
-If no ipc-socket is specified on the commandline, this variable is used
-to determine the path, at wich the unix domain socket is expected, on which
-to connect to i3.
+i3-input handles the different sources of socket paths in the following order:
+
+* I3SOCK environment variable
+* I3SOCK gets overwritten by the -s parameter, if specified
+* if neither are available, i3-input reads the socket path from the X11
+  property, which is the recommended way
+* if everything fails, i3-input tries +/tmp/i3-ipc.sock+
+
+The socket path is necessary to connect to i3 and actually issue the command.
 
 == SEE ALSO
 
diff --git a/man/i3-sensible-editor.man b/man/i3-sensible-editor.man
new file mode 100644 (file)
index 0000000..86fbf84
--- /dev/null
@@ -0,0 +1,36 @@
+i3-sensible-editor(1)
+===================
+Michael Stapelberg <michael+i3@stapelberg.de>
+v4.1, November 2011
+
+== NAME
+
+i3-sensible-editor - launches $EDITOR with fallbacks
+
+== SYNOPSIS
+
+i3-sensible-editor [arguments]
+
+== DESCRIPTION
+
+i3-sensible-editor is used by i3-nagbar(1) when you click on the edit button.
+
+It tries to start one of the following (in that order):
+
+* $VISUAL
+* $EDITOR
+* nano
+* vim
+* vi
+* emacs
+
+Please don’t complain about the order: If the user has any preference, he will
+have $VISUAL or $EDITOR set.
+
+== SEE ALSO
+
+i3(1)
+
+== AUTHOR
+
+Michael Stapelberg and contributors
diff --git a/man/i3-sensible-pager.man b/man/i3-sensible-pager.man
new file mode 100644 (file)
index 0000000..73bd2fd
--- /dev/null
@@ -0,0 +1,34 @@
+i3-sensible-pager(1)
+===================
+Michael Stapelberg <michael+i3@stapelberg.de>
+v4.1, November 2011
+
+== NAME
+
+i3-sensible-pager - launches $PAGER with fallbacks
+
+== SYNOPSIS
+
+i3-sensible-pager [arguments]
+
+== DESCRIPTION
+
+i3-sensible-pager is used by i3-nagbar(1) when you click on the view button.
+
+It tries to start one of the following (in that order):
+
+* $PAGER
+* most
+* less
+* i3-sensible-editor(1)
+
+Please don’t complain about the order: If the user has any preference, he will
+have $PAGER set.
+
+== SEE ALSO
+
+i3(1)
+
+== AUTHOR
+
+Michael Stapelberg and contributors
diff --git a/man/i3-sensible-terminal.man b/man/i3-sensible-terminal.man
new file mode 100644 (file)
index 0000000..140e412
--- /dev/null
@@ -0,0 +1,39 @@
+i3-sensible-terminal(1)
+=======================
+Michael Stapelberg <michael+i3@stapelberg.de>
+v4.1, November 2011
+
+== NAME
+
+i3-sensible-terminal - launches $TERMINAL with fallbacks
+
+== SYNOPSIS
+
+i3-sensible-terminal [arguments]
+
+== DESCRIPTION
+
+i3-sensible-terminal is invoked in the i3 default config to start a terminal.
+This wrapper script is necessary since there is no distribution-independent
+terminal launcher (but for example Debian has x-terminal-emulator).
+Distribution packagers are responsible for shipping this script in a way which
+is appropriate for the distribution.
+
+It tries to start one of the following (in that order):
+
+* $TERMINAL (this is a non-standard variable)
+* xterm
+* urxvt
+* rxvt
+* roxterm
+
+Please don’t complain about the order: If the user has any preference, he will
+have $TERMINAL set or modified his i3 configuration file.
+
+== SEE ALSO
+
+i3(1)
+
+== AUTHOR
+
+Michael Stapelberg and contributors
index 868ff8ccd4f601ed0b628d72a86d76b1971c623b..019c9124e54faf6a8ccb05f6f4b9418c957f61ab 100644 (file)
@@ -238,9 +238,10 @@ bindsym Mod1+Shift+r restart
 # exit i3 (logs you out of your X session)
 bindsym Mod1+Shift+e exit
 
-# Start i3bar to display a workspace bar (plus the system information i3status
-# finds out, if available)
-exec i3status | i3bar -d
+# display workspace buttons plus a statusline generated by i3status
+bar {
+    status_command i3status
+}
 -------------------------------------------------------------
 
 === ~/.xsession
index 50f4852644f3386d9b7d0269ff03fae899399447..9ef84707f13da20d53b72bc96cbf9b525170735a 100644 (file)
@@ -4,6 +4,8 @@
  * i3 - an improved dynamic tiling window manager
  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
+ * assignments.c: Assignments for specific windows (for_window).
+ *
  */
 #include "all.h"
 
@@ -13,7 +15,7 @@
  *
  */
 void run_assignments(i3Window *window) {
-    DLOG("Checking assignments...\n");
+    DLOG("Checking if any assignments match this window\n");
 
     /* Check if any assignments match */
     Assignment *current;
@@ -38,7 +40,7 @@ void run_assignments(i3Window *window) {
         if (current->type == A_COMMAND) {
             DLOG("execute command %s\n", current->dest.command);
             char *full_command;
-            asprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
+            sasprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
             char *json_result = parse_cmd(full_command);
             FREE(full_command);
             FREE(json_result);
index 4cf1a1c3777a5fd1f7a115c7cf85b3123764e055..1566e24f879fabd4a162500cb28057a8b4461344 100644 (file)
 #include <stdint.h>
 #include <xcb/xcb.h>
 
+#include "log.h"
 #include "data.h"
 #include "config.h"
-#include "log.h"
 #include "util.h"
+#include "libi3.h"
 
 #include "cfgparse.tab.h"
 
@@ -36,6 +37,11 @@ int yycolumn = 1;
     yy_push_state(EAT_WHITESPACE); \
 } while (0)
 
+#define BAR_DOUBLE_COLOR do { \
+    yy_push_state(BAR_COLOR); \
+    yy_push_state(BAR_COLOR); \
+} while (0)
+
 %}
 
 EOL     (\r?\n)
@@ -49,7 +55,15 @@ EOL     (\r?\n)
 %s OUTPUT_COND
 %s FOR_WINDOW_COND
 %s EAT_WHITESPACE
+
 %x BUFFER_LINE
+%x BAR
+%x BAR_MODE
+%x BAR_POSITION
+%x BAR_COLORS
+%x BAR_COLOR
+
+%x EXEC
 
 %%
 
@@ -73,8 +87,48 @@ EOL     (\r?\n)
     yycolumn = 1;
 }
 
+ /* This part of the lexer handles the bar {} blocks */
+<BAR,BAR_MODE,BAR_POSITION,BAR_COLORS,BAR_COLOR>[ \t]+ { /* ignore whitespace */ ; }
+<BAR>"{"                        { return '{'; }
+<BAR>"}"                        { yy_pop_state(); return '}'; }
+<BAR>^[ \t]*#[^\n]*             { return TOKCOMMENT; }
+<BAR>output                     { WS_STRING; return TOK_BAR_OUTPUT; }
+<BAR>tray_output                { WS_STRING; return TOK_BAR_TRAY_OUTPUT; }
+<BAR>socket_path                { WS_STRING; return TOK_BAR_SOCKET_PATH; }
+<BAR>mode                       { yy_push_state(BAR_MODE); return TOK_BAR_MODE; }
+<BAR_MODE>hide                  { yy_pop_state(); return TOK_BAR_HIDE; }
+<BAR_MODE>dock                  { yy_pop_state(); return TOK_BAR_DOCK; }
+<BAR>position                   { yy_push_state(BAR_POSITION); return TOK_BAR_POSITION; }
+<BAR_POSITION>bottom            { yy_pop_state(); return TOK_BAR_BOTTOM; }
+<BAR_POSITION>top               { yy_pop_state(); return TOK_BAR_TOP; }
+<BAR>status_command             { WS_STRING; return TOK_BAR_STATUS_COMMAND; }
+<BAR>font                       { WS_STRING; return TOK_BAR_FONT; }
+<BAR>workspace_buttons          { return TOK_BAR_WORKSPACE_BUTTONS; }
+<BAR>verbose                    { return TOK_BAR_VERBOSE; }
+<BAR>colors                     { yy_push_state(BAR_COLORS); return TOK_BAR_COLORS; }
+<BAR_COLORS>"{"                 { return '{'; }
+<BAR_COLORS>"}"                 { yy_pop_state(); return '}'; }
+<BAR_COLORS>^[ \t]*#[^\n]*      { return TOKCOMMENT; }
+<BAR_COLORS>background          { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_BACKGROUND; }
+<BAR_COLORS>statusline          { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_STATUSLINE; }
+<BAR_COLORS>focused_workspace   { BAR_DOUBLE_COLOR; return TOK_BAR_COLOR_FOCUSED_WORKSPACE; }
+<BAR_COLORS>active_workspace    { BAR_DOUBLE_COLOR; return TOK_BAR_COLOR_ACTIVE_WORKSPACE; }
+<BAR_COLORS>inactive_workspace  { BAR_DOUBLE_COLOR; return TOK_BAR_COLOR_INACTIVE_WORKSPACE; }
+<BAR_COLORS>urgent_workspace    { BAR_DOUBLE_COLOR; return TOK_BAR_COLOR_URGENT_WORKSPACE; }
+<BAR_COLOR>#[0-9a-fA-F]+        { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
+<BAR,BAR_COLORS,BAR_MODE,BAR_POSITION>[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; }
+
+
 
 <FOR_WINDOW_COND>"]"            { yy_pop_state(); return ']'; }
+<ASSIGN_COND>"["                {
+                                  /* this is the case for the new assign syntax
+                                   * that uses criteria */
+                                  yy_pop_state();
+                                  yy_push_state(FOR_WINDOW_COND);
+                                  /* afterwards we will be in ASSIGN_TARGET_COND */
+                                  return '[';
+                                }
 <EAT_WHITESPACE>[ \t]*          { yy_pop_state(); }
 <WANT_QSTRING>\"[^\"]+\"        {
                                   yy_pop_state();
@@ -84,27 +138,23 @@ EOL     (\r?\n)
                                   yylval.string = copy;
                                   return STR;
                                 }
-<WANT_STRING>[^\n]+             { BEGIN(INITIAL); yylval.string = sstrdup(yytext); return STR; }
-<OUTPUT_COND>[a-zA-Z0-9_-]+     { yylval.string = sstrdup(yytext); return OUTPUT; }
+<WANT_STRING>[^\n]+             { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; }
+<OUTPUT_COND>[a-zA-Z0-9_-]+     { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; }
 ^[ \t]*#[^\n]*                  { return TOKCOMMENT; }
-<COLOR_COND>[0-9a-fA-F]+        { yylval.string = sstrdup(yytext); return HEX; }
+<COLOR_COND>#[0-9a-fA-F]+       { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
 <ASSIGN_TARGET_COND>[ \t]*→[ \t]*     { BEGIN(WANT_STRING); }
 <ASSIGN_TARGET_COND>[ \t]+      { BEGIN(WANT_STRING); }
+<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(); }
 [0-9]+                          { yylval.number = atoi(yytext); return NUMBER; }
+bar                             { yy_push_state(BAR); return TOK_BAR; }
 mode                            { return TOKMODE; }
 bind                            { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
 bindcode                        { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
 bindsym                         { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; }
-floating_modifier               { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; }
-workspace                       { BEGIN(INITIAL); return TOKWORKSPACE; }
+floating_modifier               { return TOKFLOATING_MODIFIER; }
+workspace                       { return TOKWORKSPACE; }
 output                          { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; }
-screen                          {
-                                  /* for compatibility until v3.φ */
-                                  ELOG("Assignments to screens are DEPRECATED and will not work. " \
-                                       "Please replace them with assignments to outputs.\n");
-                                  yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE);
-                                  return TOKOUTPUT;
-                                }
 terminal                        { WS_STRING; return TOKTERMINAL; }
 font                            { WS_STRING; return TOKFONT; }
 assign                          { yy_push_state(ASSIGN_TARGET_COND); yy_push_state(ASSIGN_COND); return TOKASSIGN; }
@@ -118,11 +168,14 @@ vertical                        { return TOK_VERT; }
 auto                            { return TOK_AUTO; }
 workspace_layout                { return TOK_WORKSPACE_LAYOUT; }
 new_window                      { return TOKNEWWINDOW; }
+new_float                       { return TOKNEWFLOAT; }
 normal                          { return TOK_NORMAL; }
 none                            { return TOK_NONE; }
 1pixel                          { return TOK_1PIXEL; }
 focus_follows_mouse             { return TOKFOCUSFOLLOWSMOUSE; }
 force_focus_wrapping            { return TOK_FORCE_FOCUS_WRAPPING; }
+force_xinerama                  { return TOK_FORCE_XINERAMA; }
+workspace_auto_back_and_forth   { return TOK_WORKSPACE_AUTO_BAF; }
 workspace_bar                   { return TOKWORKSPACEBAR; }
 popup_during_fullscreen         { return TOK_POPUP_DURING_FULLSCREEN; }
 ignore                          { return TOK_IGNORE; }
@@ -146,16 +199,16 @@ tabbed                          { /* yylval.number = MODE_TABBED; */return TOK_T
 stack-limit                     { return TOKSTACKLIMIT; }
 cols                            { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; }
 rows                            { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; }
-exec                            { WS_STRING; return TOKEXEC; }
-exec_always                     { WS_STRING; return TOKEXEC_ALWAYS; }
-client.background               { BEGIN(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; }
-client.focused                  { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; }
-client.focused_inactive         { BEGIN(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; }
-client.unfocused                { BEGIN(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; }
-client.urgent                   { BEGIN(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; }
-bar.focused                     { BEGIN(COLOR_COND); yylval.color = &config.bar.focused; return TOKCOLOR; }
-bar.unfocused                   { BEGIN(COLOR_COND); yylval.color = &config.bar.unfocused; return TOKCOLOR; }
-bar.urgent                      { BEGIN(COLOR_COND); yylval.color = &config.bar.urgent; return TOKCOLOR; }
+exec                            { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOKEXEC; }
+exec_always                     { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOKEXEC_ALWAYS; }
+client.background               { yy_push_state(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; }
+client.focused                  { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; }
+client.focused_inactive         { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; }
+client.unfocused                { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; }
+client.urgent                   { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; }
+bar.focused                     { yy_push_state(COLOR_COND); yylval.color = &config.bar.focused; return TOKCOLOR; }
+bar.unfocused                   { yy_push_state(COLOR_COND); yylval.color = &config.bar.unfocused; return TOKCOLOR; }
+bar.urgent                      { yy_push_state(COLOR_COND); yylval.color = &config.bar.urgent; return TOKCOLOR; }
 Mod1                            { yylval.number = BIND_MOD1; return MODIFIER; }
 Mod2                            { yylval.number = BIND_MOD2; return MODIFIER; }
 Mod3                            { yylval.number = BIND_MOD3; return MODIFIER; }
@@ -168,32 +221,31 @@ shift                           { return TOKSHIFT; }
 
 class                           { yy_push_state(WANT_QSTRING); return TOK_CLASS; }
 instance                        { yy_push_state(WANT_QSTRING); return TOK_INSTANCE; }
+window_role                     { yy_push_state(WANT_QSTRING); return TOK_WINDOW_ROLE; }
 id                              { yy_push_state(WANT_QSTRING); return TOK_ID; }
 con_id                          { yy_push_state(WANT_QSTRING); return TOK_CON_ID; }
 con_mark                        { yy_push_state(WANT_QSTRING); return TOK_MARK; }
 title                           { yy_push_state(WANT_QSTRING); return TOK_TITLE; }
 
-{EOL}                           {
+<*>{EOL}                        {
                                   FREE(context->line_copy);
                                   context->line_number++;
-                                  BEGIN(INITIAL);
                                   yy_push_state(BUFFER_LINE);
                                 }
-<BINDSYM_COND>[ \t]+            { BEGIN(WANT_STRING); }
-<OUTPUT_COND>[ \t]+             { BEGIN(WANT_STRING); }
+<BINDSYM_COND>[ \t]+            { yy_pop_state(); yy_push_state(WANT_STRING); }
+<OUTPUT_COND>[ \t]+             { yy_pop_state(); yy_push_state(WANT_STRING); }
 [ \t]+                          { /* ignore whitespace */ ; }
 \"[^\"]+\"                      {
                                   /* if ASSIGN_COND then */
                                   if (yy_start_stack_ptr > 0)
                                       yy_pop_state();
-                                  else BEGIN(INITIAL);
                                   /* yylval will be the string, but without quotes */
                                   char *copy = sstrdup(yytext+1);
                                   copy[strlen(copy)-1] = '\0';
                                   yylval.string = copy;
                                   return QUOTEDSTRING;
                                 }
-<ASSIGN_COND>[^ \t\"]+          { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; }
+<ASSIGN_COND>[^ \t\"\[]+        { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; }
 <BINDSYM_COND>[a-zA-Z0-9_]+     { yylval.string = sstrdup(yytext); return WORD; }
 [a-zA-Z]+                       { yylval.string = sstrdup(yytext); return WORD; }
 .                               { return (int)yytext[0]; }
index dc29860cda021f1f23552ed0bfce8cfaf25aa2d6..79da317d2e19a3b9cc668aee4918d5af043d76e3 100644 (file)
 static pid_t configerror_pid = -1;
 
 static Match current_match;
+static Barconfig current_bar;
+/* The pattern which was specified by the user, for example -misc-fixed-*. We
+ * store this in a separate variable because in the i3 config struct we just
+ * store the i3Font. */
+static char *font_pattern;
 
 typedef struct yy_buffer_state *YY_BUFFER_STATE;
 extern int yylex(struct context *context);
@@ -240,6 +245,23 @@ static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
     configerror_pid = -1;
 }
 
+/* We need ev >= 4 for the following code. Since it is not *that* important (it
+ * only makes sure that there are no i3-nagbar instances left behind) we still
+ * support old systems with libev 3. */
+#if EV_VERSION_MAJOR >= 4
+/*
+ * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
+ * SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
+ *
+ */
+static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
+    if (configerror_pid != -1) {
+        LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", configerror_pid);
+        kill(configerror_pid, SIGKILL);
+    }
+}
+#endif
+
 /*
  * Starts an i3-nagbar process which alerts the user that his configuration
  * file contains one or more errors. Also offers two buttons: One to launch an
@@ -248,7 +270,10 @@ static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
  *
  */
 static void start_configerror_nagbar(const char *config_path) {
-    fprintf(stderr, "Would start i3-nagscreen now\n");
+    if (only_check_config)
+        return;
+
+    fprintf(stderr, "Starting i3-nagbar due to configuration errors\n");
     configerror_pid = fork();
     if (configerror_pid == -1) {
         warn("Could not fork()");
@@ -259,19 +284,21 @@ static void start_configerror_nagbar(const char *config_path) {
     if (configerror_pid == 0) {
         char *editaction,
              *pageraction;
-        if (asprintf(&editaction, TERM_EMU " -e sh -c \"${EDITOR:-vi} \"%s\" && i3-msg reload\"", config_path) == -1)
-            exit(1);
-        if (asprintf(&pageraction, TERM_EMU " -e sh -c \"${PAGER:-less} \"%s\"\"", errorfilename) == -1)
-            exit(1);
+        sasprintf(&editaction, "i3-sensible-terminal -e sh -c \"i3-sensible-editor \\\"%s\\\" && i3-msg reload\"", config_path);
+        sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename);
         char *argv[] = {
             NULL, /* will be replaced by the executable path */
+            "-t",
+            (context->has_errors ? "error" : "warning"),
             "-m",
-            "You have an error in your i3 config file!",
+            (context->has_errors ?
+             "You have an error in your i3 config file!" :
+             "Your config is outdated. Please fix the warnings to make sure everything works."),
             "-b",
             "edit config",
             editaction,
             (errorfilename ? "-b" : NULL),
-            "show errors",
+            (context->has_errors ? "show errors" : "show warnings"),
             pageraction,
             NULL
         };
@@ -283,6 +310,17 @@ static void start_configerror_nagbar(const char *config_path) {
     ev_child *child = smalloc(sizeof(ev_child));
     ev_child_init(child, &nagbar_exited, configerror_pid, 0);
     ev_child_start(main_loop, child);
+
+/* We need ev >= 4 for the following code. Since it is not *that* important (it
+ * only makes sure that there are no i3-nagbar instances left behind) we still
+ * support old systems with libev 3. */
+#if EV_VERSION_MAJOR >= 4
+    /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
+     * still running) */
+    ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
+    ev_cleanup_init(cleanup, nagbar_cleanup);
+    ev_cleanup_start(main_loop, cleanup);
+#endif
 }
 
 /*
@@ -311,6 +349,78 @@ void kill_configerror_nagbar(bool wait_for_it) {
     waitpid(configerror_pid, NULL, 0);
 }
 
+/*
+ * Checks for duplicate key bindings (the same keycode or keysym is configured
+ * more than once). If a duplicate binding is found, a message is printed to
+ * stderr and the has_errors variable is set to true, which will start
+ * i3-nagbar.
+ *
+ */
+static void check_for_duplicate_bindings(struct context *context) {
+    Binding *bind, *current;
+    TAILQ_FOREACH(current, bindings, bindings) {
+        TAILQ_FOREACH(bind, bindings, bindings) {
+            /* Abort when we reach the current keybinding, only check the
+             * bindings before */
+            if (bind == current)
+                break;
+
+            /* Check if one is using keysym while the other is using bindsym.
+             * If so, skip. */
+            /* XXX: It should be checked at a later place (when translating the
+             * keysym to keycodes) if there are any duplicates */
+            if ((bind->symbol == NULL && current->symbol != NULL) ||
+                (bind->symbol != NULL && current->symbol == NULL))
+                continue;
+
+            /* If bind is NULL, current has to be NULL, too (see above).
+             * If the keycodes differ, it can't be a duplicate. */
+            if (bind->symbol != NULL &&
+                strcasecmp(bind->symbol, current->symbol) != 0)
+                continue;
+
+            /* Check if the keycodes or modifiers are different. If so, they
+             * can't be duplicate */
+            if (bind->keycode != current->keycode ||
+                bind->mods != current->mods)
+                continue;
+            context->has_errors = true;
+            if (current->keycode != 0) {
+                ELOG("Duplicate keybinding in config file:\n  modmask %d with keycode %d, command \"%s\"\n",
+                     current->mods, current->keycode, current->command);
+            } else {
+                ELOG("Duplicate keybinding in config file:\n  modmask %d with keysym %s, command \"%s\"\n",
+                     current->mods, current->symbol, current->command);
+            }
+        }
+    }
+}
+
+static void migrate_i3bar_exec(struct Autostart *exec) {
+    ELOG("**********************************************************************\n");
+    ELOG("IGNORING exec command: %s\n", exec->command);
+    ELOG("It contains \"i3bar\". Since i3 v4.1, i3bar will be automatically started\n");
+    ELOG("for each 'bar' configuration block in your i3 config. Please remove the exec\n");
+    ELOG("line and add the following to your i3 config:\n");
+    ELOG("\n");
+    ELOG("    bar {\n");
+    ELOG("        status_command i3status\n");
+    ELOG("    }\n");
+    ELOG("**********************************************************************\n");
+
+    /* Generate a dummy bar configuration */
+    Barconfig *bar_config = scalloc(sizeof(Barconfig));
+    /* The hard-coded ID is not a problem. It does not conflict with the
+     * auto-generated bar IDs and having multiple hard-coded IDs is irrelevant
+     * – they all just contain status_command = i3status */
+    bar_config->id = sstrdup("migrate-bar");
+    bar_config->status_command = sstrdup("i3status");
+    TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
+
+    /* Trigger an i3-nagbar */
+    context->has_warnings = true;
+}
+
 void parse_file(const char *f) {
     SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
     int fd, ret, read_bytes = 0;
@@ -472,13 +582,40 @@ void parse_file(const char *f) {
         exit(1);
     }
 
-    if (context->has_errors) {
+    check_for_duplicate_bindings(context);
+
+    /* XXX: The following code will be removed in i3 v4.3 (three releases from
+     * now, as of 2011-10-22) */
+    /* Check for any exec or exec_always lines starting i3bar. We remove these
+     * and add a bar block instead. Additionally, a i3-nagbar warning (not an
+     * error) will be displayed so that users update their config file. */
+    struct Autostart *exec, *next;
+    for (exec = TAILQ_FIRST(&autostarts); exec; ) {
+        next = TAILQ_NEXT(exec, autostarts);
+        if (strstr(exec->command, "i3bar") != NULL) {
+            migrate_i3bar_exec(exec);
+            TAILQ_REMOVE(&autostarts, exec, autostarts);
+        }
+        exec = next;
+    }
+
+    for (exec = TAILQ_FIRST(&autostarts_always); exec; ) {
+        next = TAILQ_NEXT(exec, autostarts_always);
+        if (strstr(exec->command, "i3bar") != NULL) {
+            migrate_i3bar_exec(exec);
+            TAILQ_REMOVE(&autostarts_always, exec, autostarts_always);
+        }
+        exec = next;
+    }
+
+    if (context->has_errors || context->has_warnings) {
         start_configerror_nagbar(f);
     }
 
     yylex_destroy();
     FREE(context->line_copy);
     free(context);
+    FREE(font_pattern);
     free(new);
     free(buf);
 
@@ -509,7 +646,7 @@ void parse_file(const char *f) {
 %token  <string>        WORD                        "<word>"
 %token  <string>        STR                         "<string>"
 %token  <string>        STR_NG                      "<string (non-greedy)>"
-%token  <string>        HEX                         "<hex>"
+%token  <string>        HEXCOLOR                    "#<hex>"
 %token  <string>        OUTPUT                      "<RandR output>"
 %token                  TOKBINDCODE
 %token                  TOKTERMINAL
@@ -533,17 +670,21 @@ void parse_file(const char *f) {
 %token  <color>         TOKCOLOR
 %token                  TOKARROW                    "→"
 %token                  TOKMODE                     "mode"
+%token                  TOK_BAR                     "bar"
 %token                  TOK_ORIENTATION             "default_orientation"
 %token                  TOK_HORIZ                   "horizontal"
 %token                  TOK_VERT                    "vertical"
 %token                  TOK_AUTO                    "auto"
 %token                  TOK_WORKSPACE_LAYOUT        "workspace_layout"
 %token                  TOKNEWWINDOW                "new_window"
+%token                  TOKNEWFLOAT                 "new_float"
 %token                  TOK_NORMAL                  "normal"
 %token                  TOK_NONE                    "none"
 %token                  TOK_1PIXEL                  "1pixel"
 %token                  TOKFOCUSFOLLOWSMOUSE        "focus_follows_mouse"
 %token                  TOK_FORCE_FOCUS_WRAPPING    "force_focus_wrapping"
+%token                  TOK_FORCE_XINERAMA          "force_xinerama"
+%token                  TOK_WORKSPACE_AUTO_BAF      "workspace_auto_back_and_forth"
 %token                  TOKWORKSPACEBAR             "workspace_bar"
 %token                  TOK_DEFAULT                 "default"
 %token                  TOK_STACKING                "stacking"
@@ -554,9 +695,32 @@ void parse_file(const char *f) {
 %token                  TOK_LEAVE_FULLSCREEN        "leave_fullscreen"
 %token                  TOK_FOR_WINDOW              "for_window"
 
+%token                  TOK_BAR_OUTPUT              "output (bar)"
+%token                  TOK_BAR_TRAY_OUTPUT         "tray_output"
+%token                  TOK_BAR_SOCKET_PATH         "socket_path"
+%token                  TOK_BAR_MODE                "mode (bar)"
+%token                  TOK_BAR_HIDE                "hide"
+%token                  TOK_BAR_DOCK                "dock"
+%token                  TOK_BAR_POSITION            "position"
+%token                  TOK_BAR_BOTTOM              "bottom"
+%token                  TOK_BAR_TOP                 "top"
+%token                  TOK_BAR_STATUS_COMMAND      "status_command"
+%token                  TOK_BAR_FONT                "font (bar)"
+%token                  TOK_BAR_WORKSPACE_BUTTONS   "workspace_buttons"
+%token                  TOK_BAR_VERBOSE             "verbose"
+%token                  TOK_BAR_COLORS              "colors"
+%token                  TOK_BAR_COLOR_BACKGROUND    "background"
+%token                  TOK_BAR_COLOR_STATUSLINE    "statusline"
+%token                  TOK_BAR_COLOR_FOCUSED_WORKSPACE "focused_workspace"
+%token                  TOK_BAR_COLOR_ACTIVE_WORKSPACE "active_workspace"
+%token                  TOK_BAR_COLOR_INACTIVE_WORKSPACE "inactive_workspace"
+%token                  TOK_BAR_COLOR_URGENT_WORKSPACE "urgent_workspace"
+%token                  TOK_NO_STARTUP_ID           "--no-startup-id"
+
 %token              TOK_MARK            "mark"
 %token              TOK_CLASS           "class"
 %token              TOK_INSTANCE        "instance"
+%token              TOK_WINDOW_ROLE     "window_role"
 %token              TOK_ID              "id"
 %token              TOK_CON_ID          "con_id"
 %token              TOK_TITLE           "title"
@@ -570,11 +734,16 @@ void parse_file(const char *f) {
 %type   <number>        layout_mode
 %type   <number>        border_style
 %type   <number>        new_window
+%type   <number>        new_float
 %type   <number>        colorpixel
 %type   <number>        bool
 %type   <number>        popup_setting
+%type   <number>        bar_position_position
+%type   <number>        bar_mode_mode
+%type   <number>        optional_no_startup_id
 %type   <string>        command
 %type   <string>        word_or_number
+%type   <string>        qstring_or_number
 %type   <string>        optional_workspace_name
 %type   <string>        workspace_name
 %type   <string>        window_class
@@ -590,12 +759,16 @@ line:
     bindline
     | for_window
     | mode
+    | bar
     | floating_modifier
     | orientation
     | workspace_layout
     | new_window
+    | new_float
     | focus_follows_mouse
     | force_focus_wrapping
+    | force_xinerama
+    | workspace_back_and_forth
     | workspace_bar
     | workspace
     | assign
@@ -706,12 +879,20 @@ criterion:
     TOK_CLASS '=' STR
     {
         printf("criteria: class = %s\n", $3);
-        current_match.class = $3;
+        current_match.class = regex_new($3);
+        free($3);
     }
     | TOK_INSTANCE '=' STR
     {
         printf("criteria: instance = %s\n", $3);
-        current_match.instance = $3;
+        current_match.instance = regex_new($3);
+        free($3);
+    }
+    | TOK_WINDOW_ROLE '=' STR
+    {
+        printf("criteria: window_role = %s\n", $3);
+        current_match.role = regex_new($3);
+        free($3);
     }
     | TOK_CON_ID '=' STR
     {
@@ -746,22 +927,27 @@ criterion:
     | TOK_MARK '=' STR
     {
         printf("criteria: mark = %s\n", $3);
-        current_match.mark = $3;
+        current_match.mark = regex_new($3);
+        free($3);
     }
     | TOK_TITLE '=' STR
     {
         printf("criteria: title = %s\n", $3);
-        current_match.title = $3;
+        current_match.title = regex_new($3);
+        free($3);
     }
     ;
 
-
+qstring_or_number:
+    QUOTEDSTRING
+    | NUMBER { sasprintf(&$$, "%d", $1); }
+    ;
 
 word_or_number:
     WORD
     | NUMBER
     {
-        asprintf(&$$, "%d", $1);
+        sasprintf(&$$, "%d", $1);
     }
     ;
 
@@ -807,6 +993,211 @@ modeline:
     }
     ;
 
+bar:
+    TOK_BAR '{' barlines '}'
+    {
+        printf("\t new bar configuration finished, saving.\n");
+        /* Generate a unique ID for this bar */
+        current_bar.id = sstrdup("bar-XXXXXX");
+        /* This works similar to mktemp in that it replaces the last six X with
+         * random letters, but without the restriction that the given buffer
+         * has to contain a valid path name. */
+        char *x = current_bar.id + strlen("bar-");
+        while (*x != '\0') {
+            *(x++) = (rand() % 26) + 'a';
+        }
+
+        /* If no font was explicitly set, we use the i3 font as default */
+        if (!current_bar.font && font_pattern)
+            current_bar.font = sstrdup(font_pattern);
+
+        /* Copy the current (static) structure into a dynamically allocated
+         * one, then cleanup our static one. */
+        Barconfig *bar_config = scalloc(sizeof(Barconfig));
+        memcpy(bar_config, &current_bar, sizeof(Barconfig));
+        TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
+
+        memset(&current_bar, '\0', sizeof(Barconfig));
+    }
+    ;
+
+barlines:
+    /* empty */
+    | barlines barline
+    ;
+
+barline:
+    comment
+    | bar_status_command
+    | bar_output
+    | bar_tray_output
+    | bar_position
+    | bar_mode
+    | bar_font
+    | bar_workspace_buttons
+    | bar_verbose
+    | bar_socket_path
+    | bar_colors
+    | bar_color_background
+    | bar_color_statusline
+    | bar_color_focused_workspace
+    | bar_color_active_workspace
+    | bar_color_inactive_workspace
+    | bar_color_urgent_workspace
+    ;
+
+bar_status_command:
+    TOK_BAR_STATUS_COMMAND STR
+    {
+        DLOG("should add status command %s\n", $2);
+        FREE(current_bar.status_command);
+        current_bar.status_command = $2;
+    }
+    ;
+
+bar_output:
+    TOK_BAR_OUTPUT STR
+    {
+        DLOG("bar output %s\n", $2);
+        int new_outputs = current_bar.num_outputs + 1;
+        current_bar.outputs = srealloc(current_bar.outputs, sizeof(char*) * new_outputs);
+        current_bar.outputs[current_bar.num_outputs] = $2;
+        current_bar.num_outputs = new_outputs;
+    }
+    ;
+
+bar_tray_output:
+    TOK_BAR_TRAY_OUTPUT STR
+    {
+        DLOG("tray %s\n", $2);
+        FREE(current_bar.tray_output);
+        current_bar.tray_output = $2;
+    }
+    ;
+
+bar_position:
+    TOK_BAR_POSITION bar_position_position
+    {
+        DLOG("position %d\n", $2);
+        current_bar.position = $2;
+    }
+    ;
+
+bar_position_position:
+    TOK_BAR_TOP      { $$ = P_TOP; }
+    | TOK_BAR_BOTTOM { $$ = P_BOTTOM; }
+    ;
+
+bar_mode:
+    TOK_BAR_MODE bar_mode_mode
+    {
+        DLOG("mode %d\n", $2);
+        current_bar.mode = $2;
+    }
+    ;
+
+bar_mode_mode:
+    TOK_BAR_HIDE   { $$ = M_HIDE; }
+    | TOK_BAR_DOCK { $$ = M_DOCK; }
+    ;
+
+bar_font:
+    TOK_BAR_FONT STR
+    {
+        DLOG("font %s\n", $2);
+        FREE(current_bar.font);
+        current_bar.font = $2;
+    }
+    ;
+
+bar_workspace_buttons:
+    TOK_BAR_WORKSPACE_BUTTONS bool
+    {
+        DLOG("workspace_buttons = %d\n", $2);
+        /* We store this inverted to make the default setting right when
+         * initializing the struct with zero. */
+        current_bar.hide_workspace_buttons = !($2);
+    }
+    ;
+
+bar_verbose:
+    TOK_BAR_VERBOSE bool
+    {
+        DLOG("verbose = %d\n", $2);
+        current_bar.verbose = $2;
+    }
+    ;
+
+bar_socket_path:
+    TOK_BAR_SOCKET_PATH STR
+    {
+        DLOG("socket_path = %s\n", $2);
+        FREE(current_bar.socket_path);
+        current_bar.socket_path = $2;
+    }
+    ;
+
+bar_colors:
+    TOK_BAR_COLORS '{' barlines '}'
+    {
+        /* At the moment, the TOK_BAR_COLORS token is only to make the config
+         * friendlier for humans. We might change this in the future if it gets
+         * more complex. */
+    }
+    ;
+
+bar_color_background:
+    TOK_BAR_COLOR_BACKGROUND HEXCOLOR
+    {
+        DLOG("background = %s\n", $2);
+        current_bar.colors.background = $2;
+    }
+    ;
+
+bar_color_statusline:
+    TOK_BAR_COLOR_STATUSLINE HEXCOLOR
+    {
+        DLOG("statusline = %s\n", $2);
+        current_bar.colors.statusline = $2;
+    }
+    ;
+
+bar_color_focused_workspace:
+    TOK_BAR_COLOR_FOCUSED_WORKSPACE HEXCOLOR HEXCOLOR
+    {
+        DLOG("focused_ws = %s and %s\n", $2, $3);
+        current_bar.colors.focused_workspace_text = $2;
+        current_bar.colors.focused_workspace_bg = $3;
+    }
+    ;
+
+bar_color_active_workspace:
+    TOK_BAR_COLOR_ACTIVE_WORKSPACE HEXCOLOR HEXCOLOR
+    {
+        DLOG("active_ws = %s and %s\n", $2, $3);
+        current_bar.colors.active_workspace_text = $2;
+        current_bar.colors.active_workspace_bg = $3;
+    }
+    ;
+
+bar_color_inactive_workspace:
+    TOK_BAR_COLOR_INACTIVE_WORKSPACE HEXCOLOR HEXCOLOR
+    {
+        DLOG("inactive_ws = %s and %s\n", $2, $3);
+        current_bar.colors.inactive_workspace_text = $2;
+        current_bar.colors.inactive_workspace_bg = $3;
+    }
+    ;
+
+bar_color_urgent_workspace:
+    TOK_BAR_COLOR_URGENT_WORKSPACE HEXCOLOR HEXCOLOR
+    {
+        DLOG("urgent_ws = %s and %s\n", $2, $3);
+        current_bar.colors.urgent_workspace_text = $2;
+        current_bar.colors.urgent_workspace_bg = $3;
+    }
+    ;
+
 floating_modifier:
     TOKFLOATING_MODIFIER binding_modifiers
     {
@@ -888,6 +1279,14 @@ new_window:
     }
     ;
 
+new_float:
+    TOKNEWFLOAT border_style
+    {
+       DLOG("new floating windows should start with border style %d\n", $2);
+       config.default_floating_border = $2;
+    }
+    ;
+
 border_style:
     TOK_NORMAL      { $$ = BS_NORMAL; }
     | TOK_NONE      { $$ = BS_NONE; }
@@ -926,6 +1325,22 @@ force_focus_wrapping:
     }
     ;
 
+force_xinerama:
+    TOK_FORCE_XINERAMA bool
+    {
+        DLOG("force xinerama = %d\n", $2);
+        config.force_xinerama = $2;
+    }
+    ;
+
+workspace_back_and_forth:
+    TOK_WORKSPACE_AUTO_BAF bool
+    {
+        DLOG("automatic workspace back-and-forth = %d\n", $2);
+        config.workspace_auto_back_and_forth = $2;
+    }
+    ;
+
 workspace_bar:
     TOKWORKSPACEBAR bool
     {
@@ -935,40 +1350,39 @@ workspace_bar:
     ;
 
 workspace:
-    TOKWORKSPACE NUMBER TOKOUTPUT OUTPUT optional_workspace_name
+    TOKWORKSPACE qstring_or_number TOKOUTPUT OUTPUT optional_workspace_name
     {
-        int ws_num = $2;
-        if (ws_num < 1) {
-            DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
-        } else {
-            char *ws_name = NULL;
-            if ($5 == NULL) {
-                asprintf(&ws_name, "%d", ws_num);
-            } else {
-                ws_name = $5;
-            }
+        char *ws_name = $2;
+
+        if ($5 != NULL) {
+            ELOG("The old (v3) syntax workspace <number> output <output> <name> is deprecated.\n");
+            ELOG("Please use the new syntax: workspace \"<workspace>\" output <output>\n");
+            ELOG("In your case, the following should work:\n");
+            ELOG("    workspace \"%s\" output %s\n", $5, $4);
+            ws_name = $5;
+            context->has_warnings = true;
+        }
 
-            DLOG("Should assign workspace %s to output %s\n", ws_name, $4);
-            /* Check for earlier assignments of the same workspace so that we
-             * don’t have assignments of a single workspace to different
-             * outputs */
-            struct Workspace_Assignment *assignment;
-            bool duplicate = false;
-            TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-                if (strcasecmp(assignment->name, ws_name) == 0) {
-                    ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
-                         ws_name);
-                    assignment->output = $4;
-                    duplicate = true;
-                }
-            }
-            if (!duplicate) {
-                assignment = scalloc(sizeof(struct Workspace_Assignment));
-                assignment->name = ws_name;
+        DLOG("Assigning workspace \"%s\" to output \"%s\"\n", ws_name, $4);
+        /* Check for earlier assignments of the same workspace so that we
+         * don’t have assignments of a single workspace to different
+         * outputs */
+        struct Workspace_Assignment *assignment;
+        bool duplicate = false;
+        TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
+            if (strcasecmp(assignment->name, ws_name) == 0) {
+                ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
+                     ws_name);
                 assignment->output = $4;
-                TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
+                duplicate = true;
             }
         }
+        if (!duplicate) {
+            assignment = scalloc(sizeof(struct Workspace_Assignment));
+            assignment->name = ws_name;
+            assignment->output = $4;
+            TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
+        }
     }
     | TOKWORKSPACE NUMBER workspace_name
     {
@@ -1001,6 +1415,13 @@ workspace_name:
 assign:
     TOKASSIGN window_class STR
     {
+        /* This is the old, deprecated form of assignments. It’s provided for
+         * compatibility in version (4.1, 4.2, 4.3) and will be removed
+         * afterwards. It triggers an i3-nagbar warning starting from 4.1. */
+        ELOG("You are using the old assign syntax (without criteria). "
+             "Please see the User's Guide for the new syntax and fix "
+             "your config file.\n");
+        context->has_warnings = true;
         printf("assignment of %s to *%s*\n", $2, $3);
         char *workspace = $3;
         char *criteria = $2;
@@ -1012,15 +1433,21 @@ assign:
         char *separator = NULL;
         if ((separator = strchr(criteria, '/')) != NULL) {
             *(separator++) = '\0';
-            match->title = sstrdup(separator);
+            char *pattern;
+            sasprintf(&pattern, "(?i)%s", separator);
+            match->title = regex_new(pattern);
+            free(pattern);
+            printf("  title = %s\n", separator);
+        }
+        if (*criteria != '\0') {
+            char *pattern;
+            sasprintf(&pattern, "(?i)%s", criteria);
+            match->class = regex_new(pattern);
+            free(pattern);
+            printf("  class = %s\n", criteria);
         }
-        if (*criteria != '\0')
-            match->class = sstrdup(criteria);
         free(criteria);
 
-        printf("  class = %s\n", match->class);
-        printf("  title = %s\n", match->title);
-
         /* Compatibility with older versions: If the assignment target starts
          * with ~, we create the equivalent of:
          *
@@ -1048,6 +1475,19 @@ assign:
         assignment->dest.workspace = workspace;
         TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
     }
+    | TOKASSIGN match STR
+    {
+        if (match_is_empty(&current_match)) {
+            ELOG("Match is empty, ignoring this assignment\n");
+            break;
+        }
+        printf("new assignment, using above criteria, to workspace %s\n", $3);
+        Assignment *assignment = scalloc(sizeof(Assignment));
+        assignment->match = current_match;
+        assignment->type = A_TO_WORKSPACE;
+        assignment->dest.workspace = $3;
+        TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+    }
     ;
 
 window_class:
@@ -1070,23 +1510,30 @@ restart_state:
     ;
 
 exec:
-    TOKEXEC STR
+    TOKEXEC optional_no_startup_id STR
     {
         struct Autostart *new = smalloc(sizeof(struct Autostart));
-        new->command = $2;
+        new->command = $3;
+        new->no_startup_id = $2;
         TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
     }
     ;
 
 exec_always:
-    TOKEXEC_ALWAYS STR
+    TOKEXEC_ALWAYS optional_no_startup_id STR
     {
         struct Autostart *new = smalloc(sizeof(struct Autostart));
-        new->command = $2;
+        new->command = $3;
+        new->no_startup_id = $2;
         TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always);
     }
     ;
 
+optional_no_startup_id:
+    /* empty */ { $$ = false; }
+    | TOK_NO_STARTUP_ID  { $$ = true; }
+    ;
+
 terminal:
     TOKTERMINAL STR
     {
@@ -1100,7 +1547,8 @@ font:
     {
         config.font = load_font($2, true);
         printf("font %s\n", $2);
-        free($2);
+        FREE(font_pattern);
+        font_pattern = $2;
     }
     ;
 
@@ -1124,14 +1572,10 @@ color:
     ;
 
 colorpixel:
-    '#' HEX
+    HEXCOLOR
     {
-        char *hex;
-        if (asprintf(&hex, "#%s", $2) == -1)
-            die("asprintf()");
-        free($2);
-        $$ = get_colorpixel(hex);
-        free(hex);
+        $$ = get_colorpixel($1);
+        free($1);
     }
     ;
 
index c270bdecdef3affe71ea3eb28f211ff8333f6549..0f0ea0dc36bde7c297dda2bab4d4143a0e8071c4 100644 (file)
@@ -2,15 +2,13 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * src/click.c: Contains the handlers for button press (mouse click) events
- *              because they are quite large.
+ * click.c: Button press (mouse click) events.
  *
  */
+#include "all.h"
+
 #include <time.h>
 #include <math.h>
 
@@ -19,9 +17,6 @@
 
 #include <X11/XKBlib.h>
 
-#include "all.h"
-
-
 typedef enum { CLICK_BORDER = 0, CLICK_DECORATION = 1, CLICK_INSIDE = 2 } click_destination_t;
 
 /*
@@ -30,7 +25,7 @@ typedef enum { CLICK_BORDER = 0, CLICK_DECORATION = 1, CLICK_INSIDE = 2 } click_
  * then calls resize_graphical_handler().
  *
  */
-static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) {
+static bool tiling_resize_for_border(Con *con, border_t border, const xcb_button_press_event_t *event) {
     DLOG("border = %d\n", border);
     char way = (border == BORDER_TOP || border == BORDER_LEFT ? 'p' : 'n');
     orientation_t orientation = (border == BORDER_TOP || border == BORDER_BOTTOM ? VERT : HORIZ);
@@ -81,7 +76,7 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
  * to the client).
  *
  */
-static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) {
+static bool floating_mod_on_tiled_client(Con *con, const xcb_button_press_event_t *event) {
     /* The client is in tiling layout. We can still initiate a resize with the
      * right mouse button, by chosing the border which is the most near one to
      * the position of the mouse pointer */
@@ -120,14 +115,33 @@ static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *eve
  * Finds out which border was clicked on and calls tiling_resize_for_border().
  *
  */
-static bool tiling_resize(Con *con, xcb_button_press_event_t *event, click_destination_t dest) {
+static bool tiling_resize(Con *con, const xcb_button_press_event_t *event, const click_destination_t dest) {
     /* check if this was a click on the window border (and on which one) */
     Rect bsr = con_border_style_rect(con);
     DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n",
             event->event_x, event->event_y, con, event->event);
     DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width);
-    if (dest == CLICK_DECORATION)
+    if (dest == CLICK_DECORATION) {
+        /* The user clicked on a window decoration. We ignore the following case:
+         * The container is a h-split, tabbed or stacked container with > 1
+         * window. Decorations will end up next to each other and the user
+         * expects to switch to a window by clicking on its decoration. */
+
+        /* Since the container might either be the child *or* already a split
+         * container (in the case of a nested split container), we need to make
+         * sure that we are dealing with the split container here. */
+        if (con_is_leaf(con) && con->parent->type == CT_CON)
+            con = con->parent;
+
+        if ((con->layout == L_STACKED ||
+             con->layout == L_TABBED ||
+             con->orientation == HORIZ) &&
+            con_num_children(con) > 1) {
+            DLOG("Not handling this resize, this container has > 1 child.\n");
+            return false;
+        }
         return tiling_resize_for_border(con, BORDER_TOP, event);
+    }
 
     if (event->event_x >= 0 && event->event_x <= bsr.x &&
         event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height)
@@ -148,7 +162,7 @@ static bool tiling_resize(Con *con, xcb_button_press_event_t *event, click_desti
  * functions for resizing/dragging.
  *
  */
-static int route_click(Con *con, xcb_button_press_event_t *event, bool mod_pressed, click_destination_t dest) {
+static int route_click(Con *con, const xcb_button_press_event_t *event, const bool mod_pressed, const click_destination_t dest) {
     DLOG("--> click properties: mod = %d, destination = %d\n", mod_pressed, dest);
     DLOG("--> OUTCOME = %p\n", con);
     DLOG("type = %d, name = %s\n", con->type, con->name);
@@ -259,8 +273,10 @@ int handle_button_press(xcb_button_press_event_t *event) {
     Con *con;
     DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event);
 
+    last_timestamp = event->time;
+
     const uint32_t mod = config.floating_modifier;
-    bool mod_pressed = (mod != 0 && (event->state & mod) == mod);
+    const bool mod_pressed = (mod != 0 && (event->state & mod) == mod);
     DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail);
     if ((con = con_by_window_id(event->event)))
         return route_click(con, event, mod_pressed, CLICK_INSIDE);
index 6c756b0d42850db11a014c5b1838ec3e21704ec4..5a727658aced1493281c617025fb05b29d23a253 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "config.h"
 #include "util.h"
+#include "libi3.h"
 
 int cmdyycolumn = 1;
 
@@ -47,6 +48,8 @@ EOL (\r?\n)
 /* handle a quoted string or everything up to the next whitespace */
 %s WANT_QSTRING
 
+%x EXEC
+
 %x BUFFER_LINE
 
 %%
@@ -71,10 +74,11 @@ EOL (\r?\n)
     cmdyycolumn = 1;
 }
 
-    /* the next/prev tokens are here to recognize them *before* handling
-     * strings ('workspace' command) */
-next                            { return TOK_NEXT; }
-prev                            { return TOK_PREV; }
+    /* the next/prev/back_and_forth tokens are here to recognize them *before*
+     * handling strings ('workspace' command) */
+next                            { BEGIN(INITIAL); return TOK_NEXT; }
+prev                            { BEGIN(INITIAL); return TOK_PREV; }
+back_and_forth                  { BEGIN(INITIAL); return TOK_BACK_AND_FORTH; }
 
 <WANT_STRING>\"[^\"]+\"         {
                                   BEGIN(INITIAL);
@@ -99,7 +103,9 @@ prev                            { return TOK_PREV; }
 <EAT_WHITESPACE>[ \t]*          { yy_pop_state(); }
 
 [ \t]*                          { /* ignore whitespace */ ; }
-exec                            { WS_STRING; return TOK_EXEC; }
+<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(); }
+exec                            { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOK_EXEC; }
 exit                            { return TOK_EXIT; }
 reload                          { return TOK_RELOAD; }
 restart                         { return TOK_RESTART; }
@@ -123,6 +129,7 @@ floating                        { return TOK_FLOATING; }
 toggle                          { return TOK_TOGGLE; }
 mode_toggle                     { return TOK_MODE_TOGGLE; }
 workspace                       { WS_STRING; return TOK_WORKSPACE; }
+output                          { WS_STRING; return TOK_OUTPUT; }
 focus                           { return TOK_FOCUS; }
 move                            { return TOK_MOVE; }
 open                            { return TOK_OPEN; }
@@ -154,6 +161,7 @@ no                              { return TOK_DISABLE; }
 
 class                           { BEGIN(WANT_QSTRING); return TOK_CLASS; }
 instance                        { BEGIN(WANT_QSTRING); return TOK_INSTANCE; }
+window_role                     { BEGIN(WANT_QSTRING); return TOK_WINDOW_ROLE; }
 id                              { BEGIN(WANT_QSTRING); return TOK_ID; }
 con_id                          { BEGIN(WANT_QSTRING); return TOK_CON_ID; }
 con_mark                        { BEGIN(WANT_QSTRING); return TOK_MARK; }
index bf2fde961c46876bf92135da72bbf6b4cb4e8869..e413f21ba8c5f4f5f3f3ea137be566e1357a7bea 100644 (file)
@@ -7,7 +7,6 @@
  *
  * cmdparse.y: the parser for commands you send to i3 (or bind on keys)
  *
-
  */
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -90,8 +89,8 @@ char *parse_cmd(const char *new) {
     context->filename = "cmd";
     if (cmdyyparse() != 0) {
         fprintf(stderr, "Could not parse command\n");
-        asprintf(&json_output, "{\"success\":false, \"error\":\"%s at position %d\"}",
-                 context->compact_error, context->first_column);
+        sasprintf(&json_output, "{\"success\":false, \"error\":\"%s at position %d\"}",
+                  context->compact_error, context->first_column);
         FREE(context->line_copy);
         FREE(context->compact_error);
         free(context);
@@ -149,6 +148,7 @@ bool definitelyGreaterThan(float a, float b, float epsilon) {
 %token              TOK_ENABLE          "enable"
 %token              TOK_DISABLE         "disable"
 %token              TOK_WORKSPACE       "workspace"
+%token              TOK_OUTPUT          "output"
 %token              TOK_TOGGLE          "toggle"
 %token              TOK_FOCUS           "focus"
 %token              TOK_MOVE            "move"
@@ -173,9 +173,12 @@ bool definitelyGreaterThan(float a, float b, float epsilon) {
 %token              TOK_OR              "or"
 %token              TOK_PPT             "ppt"
 %token              TOK_NOP             "nop"
+%token              TOK_BACK_AND_FORTH  "back_and_forth"
+%token              TOK_NO_STARTUP_ID   "--no-startup-id"
 
 %token              TOK_CLASS           "class"
 %token              TOK_INSTANCE        "instance"
+%token              TOK_WINDOW_ROLE     "window_role"
 %token              TOK_ID              "id"
 %token              TOK_CON_ID          "con_id"
 %token              TOK_TITLE           "title"
@@ -195,6 +198,7 @@ bool definitelyGreaterThan(float a, float b, float epsilon) {
 %type   <number>    resize_way
 %type   <number>    resize_tiling
 %type   <number>    optional_kill_mode
+%type   <number>    optional_no_startup_id
 
 %%
 
@@ -266,10 +270,9 @@ matchend:
 
                 }
             } else if (current_match.mark != NULL && current->con->mark != NULL &&
-                    strcasecmp(current_match.mark, current->con->mark) == 0) {
+                       regex_matches(current_match.mark, current->con->mark)) {
                 printf("match by mark\n");
-                    TAILQ_INSERT_TAIL(&owindows, current, owindows);
-
+                TAILQ_INSERT_TAIL(&owindows, current, owindows);
             } else {
                 if (current->con->window == NULL)
                     continue;
@@ -299,12 +302,20 @@ criterion:
     TOK_CLASS '=' STR
     {
         printf("criteria: class = %s\n", $3);
-        current_match.class = $3;
+        current_match.class = regex_new($3);
+        free($3);
     }
     | TOK_INSTANCE '=' STR
     {
         printf("criteria: instance = %s\n", $3);
-        current_match.instance = $3;
+        current_match.instance = regex_new($3);
+        free($3);
+    }
+    | TOK_WINDOW_ROLE '=' STR
+    {
+        printf("criteria: window_role = %s\n", $3);
+        current_match.role = regex_new($3);
+        free($3);
     }
     | TOK_CON_ID '=' STR
     {
@@ -339,12 +350,14 @@ criterion:
     | TOK_MARK '=' STR
     {
         printf("criteria: mark = %s\n", $3);
-        current_match.mark = $3;
+        current_match.mark = regex_new($3);
+        free($3);
     }
     | TOK_TITLE '=' STR
     {
         printf("criteria: title = %s\n", $3);
-        current_match.title = $3;
+        current_match.title = regex_new($3);
+        free($3);
     }
     ;
 
@@ -376,14 +389,22 @@ operation:
     ;
 
 exec:
-    TOK_EXEC STR
+    TOK_EXEC optional_no_startup_id STR
     {
-        printf("should execute %s\n", $2);
-        start_application($2);
-        free($2);
+        char *command = $3;
+        bool no_startup_id = $2;
+
+        printf("should execute %s, no_startup_id = %d\n", command, no_startup_id);
+        start_application(command, no_startup_id);
+        free($3);
     }
     ;
 
+optional_no_startup_id:
+    /* empty */ { $$ = false; }
+    | TOK_NO_STARTUP_ID  { $$ = true; }
+    ;
+
 exit:
     TOK_EXIT
     {
@@ -421,15 +442,33 @@ focus:
             ELOG("You have to specify which window/container should be focused.\n");
             ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
 
-            asprintf(&json_output, "{\"success\":false, \"error\":\"You have to "
-                     "specify which window/container should be focused\"}");
+            sasprintf(&json_output, "{\"success\":false, \"error\":\"You have to "
+                      "specify which window/container should be focused\"}");
             break;
         }
 
         int count = 0;
         TAILQ_FOREACH(current, &owindows, owindows) {
             Con *ws = con_get_workspace(current->con);
-            workspace_show(ws->name);
+
+            /* If the container is not on the current workspace,
+             * workspace_show() will switch to a different workspace and (if
+             * enabled) trigger a mouse pointer warp to the currently focused
+             * container (!) on the target workspace.
+             *
+             * Therefore, before calling workspace_show(), we make sure that
+             * 'current' will be focused on the workspace. However, we cannot
+             * just con_focus(current) because then the pointer will not be
+             * warped at all (the code thinks we are already there).
+             *
+             * So we focus 'current' to make it the currently focused window of
+             * the target workspace, then revert focus. */
+            Con *currently_focused = focused;
+            con_focus(current->con);
+            con_focus(currently_focused);
+
+            /* Now switch to the workspace, then focus */
+            workspace_show(ws);
             LOG("focusing %p / %s\n", current->con, current->con->name);
             con_focus(current->con);
             count++;
@@ -550,18 +589,37 @@ optional_kill_mode:
 workspace:
     TOK_WORKSPACE TOK_NEXT
     {
-        workspace_next();
+        workspace_show(workspace_next());
         tree_render();
     }
     | TOK_WORKSPACE TOK_PREV
     {
-        workspace_prev();
+        workspace_show(workspace_prev());
+        tree_render();
+    }
+    | TOK_WORKSPACE TOK_BACK_AND_FORTH
+    {
+        workspace_back_and_forth();
         tree_render();
     }
     | TOK_WORKSPACE STR
     {
         printf("should switch to workspace %s\n", $2);
-        workspace_show($2);
+
+        Con *ws = con_get_workspace(focused);
+
+        /* Check if the command wants to switch to the current workspace */
+        if (strcmp(ws->name, $2) == 0) {
+            printf("This workspace is already focused.\n");
+            if (config.workspace_auto_back_and_forth) {
+                workspace_back_and_forth();
+                free($2);
+                tree_render();
+            }
+            break;
+        }
+
+        workspace_show_by_name($2);
         free($2);
 
         tree_render();
@@ -574,7 +632,7 @@ open:
         printf("opening new container\n");
         Con *con = tree_open_con(NULL, NULL);
         con_focus(con);
-        asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con);
+        sasprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con);
 
         tree_render();
     }
@@ -679,10 +737,27 @@ border_style:
     ;
 
 move:
-    TOK_MOVE direction
+    TOK_MOVE direction resize_px
     {
-        printf("moving in direction %d\n", $2);
-        tree_move($2);
+        int direction = $2;
+        int px = $3;
+
+        /* TODO: make 'move' work with criteria. */
+        printf("moving in direction %d\n", direction);
+        if (con_is_floating(focused)) {
+            printf("floating move with %d pixels\n", px);
+            if (direction == TOK_LEFT) {
+                focused->parent->rect.x -= px;
+            } else if (direction == TOK_RIGHT) {
+                focused->parent->rect.x += px;
+            } else if (direction == TOK_UP) {
+                focused->parent->rect.y -= px;
+            } else if (direction == TOK_DOWN) {
+                focused->parent->rect.y += px;
+            }
+        } else {
+            tree_move(direction);
+        }
 
         tree_render();
     }
@@ -690,6 +765,11 @@ move:
     {
         owindow *current;
 
+        /* Error out early to not create a non-existing workspace (in
+         * workspace_get()) if we are not actually able to move anything. */
+        if (match_is_empty(&current_match) && focused->type == CT_WORKSPACE)
+            break;
+
         printf("should move window to workspace %s\n", $3);
         /* get the workspace */
         Con *ws = workspace_get($3, NULL);
@@ -704,6 +784,83 @@ move:
 
         tree_render();
     }
+    | TOK_MOVE TOK_WORKSPACE TOK_NEXT
+    {
+        owindow *current;
+
+        /* get the workspace */
+        Con *ws = workspace_next();
+
+        HANDLE_EMPTY_MATCH;
+
+        TAILQ_FOREACH(current, &owindows, owindows) {
+            printf("matching: %p / %s\n", current->con, current->con->name);
+            con_move_to_workspace(current->con, ws, true, false);
+        }
+
+        tree_render();
+    }
+    | TOK_MOVE TOK_WORKSPACE TOK_PREV
+    {
+        owindow *current;
+
+        /* get the workspace */
+        Con *ws = workspace_prev();
+
+        HANDLE_EMPTY_MATCH;
+
+        TAILQ_FOREACH(current, &owindows, owindows) {
+            printf("matching: %p / %s\n", current->con, current->con->name);
+            con_move_to_workspace(current->con, ws, true, false);
+        }
+
+        tree_render();
+    }
+    | TOK_MOVE TOK_OUTPUT STR
+    {
+        owindow *current;
+
+        printf("should move window to output %s", $3);
+
+        HANDLE_EMPTY_MATCH;
+
+        /* get the output */
+        Output *current_output = NULL;
+        Output *output;
+
+        TAILQ_FOREACH(current, &owindows, owindows)
+            current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
+
+        assert(current_output != NULL);
+
+        if (strcasecmp($3, "up") == 0)
+            output = get_output_next(D_UP, current_output);
+        else if (strcasecmp($3, "down") == 0)
+            output = get_output_next(D_DOWN, current_output);
+        else if (strcasecmp($3, "left") == 0)
+            output = get_output_next(D_LEFT, current_output);
+        else if (strcasecmp($3, "right") == 0)
+            output = get_output_next(D_RIGHT, current_output);
+        else
+            output = get_output_by_name($3);
+        free($3);
+
+        if (!output)
+            break;
+
+        /* get visible workspace on output */
+        Con *ws = NULL;
+        GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
+        if (!ws)
+            break;
+
+        TAILQ_FOREACH(current, &owindows, owindows) {
+            printf("matching: %p / %s\n", current->con, current->con->name);
+            con_move_to_workspace(current->con, ws, true, false);
+        }
+
+        tree_render();
+    }
     ;
 
 append_layout:
@@ -811,6 +968,17 @@ resize:
             double percentage = 1.0 / children;
             LOG("default percentage = %f\n", percentage);
 
+            orientation_t orientation = current->parent->orientation;
+
+            if ((orientation == HORIZ &&
+                 (direction == TOK_UP || direction == TOK_DOWN)) ||
+                (orientation == VERT &&
+                 (direction == TOK_LEFT || direction == TOK_RIGHT))) {
+                LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
+                    (orientation == HORIZ ? "horizontal" : "vertical"));
+                break;
+            }
+
             if (direction == TOK_UP || direction == TOK_LEFT) {
                 other = TAILQ_PREV(current, nodes_head, nodes);
             } else {
@@ -818,7 +986,7 @@ resize:
             }
             if (other == TAILQ_END(workspaces)) {
                 LOG("No other container in this direction found, cannot resize.\n");
-                return 0;
+                break;
             }
             LOG("other->percent = %f\n", other->percent);
             LOG("current->percent before = %f\n", current->percent);
index 8fbedd3d608561e9346942184335256a65d2a0c1..ee84467b82da4dea7b2becdc5c061d9e8d0e7093 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -2,11 +2,11 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * con.c contains all functions which deal with containers directly (creating
- * containers, searching containers, getting specific properties from
- * containers, …).
+ * con.c: Functions which deal with containers directly (creating containers,
+ *        searching containers, getting specific properties from containers,
+ *        …).
  *
  */
 #include "all.h"
@@ -129,7 +129,9 @@ void con_attach(Con *con, Con *parent, bool ignore_focus) {
          * workspace or a new split container with the configured
          * workspace_layout).
          */
-        if (con->window != NULL && parent->type == CT_WORKSPACE) {
+        if (con->window != NULL &&
+            parent->type == CT_WORKSPACE &&
+            config.default_layout != L_DEFAULT) {
             DLOG("Parent is a workspace. Applying default layout...\n");
             Con *target = workspace_attach_to(parent);
 
@@ -193,7 +195,6 @@ void con_focus(Con *con) {
         con->urgent = false;
         workspace_update_urgent_flag(con_get_workspace(con));
     }
-    DLOG("con_focus done = %p\n", con);
 }
 
 /*
@@ -404,8 +405,8 @@ Con *con_by_frame_id(xcb_window_t frame) {
 Con *con_for_window(Con *con, i3Window *window, Match **store_match) {
     Con *child;
     Match *match;
-    DLOG("searching con for window %p starting at con %p\n", window, con);
-    DLOG("class == %s\n", window->class_class);
+    //DLOG("searching con for window %p starting at con %p\n", window, con);
+    //DLOG("class == %s\n", window->class_class);
 
     TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
         TAILQ_FOREACH(match, &(child->swallow_head), matches) {
@@ -632,7 +633,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
          * focused. Must do before attaching because workspace_show checks to see
          * if focused container is in its area. */
         if (workspace_is_visible(workspace)) {
-            workspace_show(workspace->name);
+            workspace_show(workspace);
 
             /* Don’t warp if told so (when dragging floating windows with the
              * mouse for example) */
@@ -668,7 +669,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
         /* Descend focus stack in case focus_next is a workspace which can
          * occur if we move to the same workspace.  Also show current workspace
          * to ensure it is focused. */
-        workspace_show(con_get_workspace(focus_next)->name);
+        workspace_show(con_get_workspace(focus_next));
         con_focus(con_descend_focused(focus_next));
     }
 
@@ -1008,14 +1009,25 @@ static void con_on_remove_child(Con *con) {
 
     /* Every container 'above' (in the hierarchy) the workspace content should
      * not be closed when the last child was removed */
-    if (con->type == CT_WORKSPACE ||
-        con->type == CT_OUTPUT ||
+    if (con->type == CT_OUTPUT ||
         con->type == CT_ROOT ||
         con->type == CT_DOCKAREA) {
         DLOG("not handling, type = %d\n", con->type);
         return;
     }
 
+    /* For workspaces, close them only if they're not visible anymore */
+    if (con->type == CT_WORKSPACE) {
+        int children = con_num_children(con);
+        if (children == 0 && !workspace_is_visible(con)) {
+            LOG("Closing old workspace (%p / %s), it is empty\n", con, con->name);
+            tree_close(con, DONT_KILL_WINDOW, false, false);
+            ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
+            ewmh_update_workarea();
+        }
+        return;
+    }
+
     /* TODO: check if this container would swallow any other client and
      * don’t close it automatically. */
     int children = con_num_children(con);
index 14fc6e025922c924f9c11f8f0f80493e44b1ad37..8efb491ece9657ec22465da7d873323d0ef38092 100644 (file)
@@ -2,25 +2,21 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * src/config.c: Contains all functions handling the configuration file (calling
- * the parser (src/cfgparse.y) with the correct path, switching key bindings
- * mode).
+ * config.c: Configuration file (calling the parser (src/cfgparse.y) with the
+ *           correct path, switching key bindings mode).
  *
  */
+#include "all.h"
 
 /* We need Xlib for XStringToKeysym */
 #include <X11/Xlib.h>
 
-#include "all.h"
-
 char *current_configpath = NULL;
 Config config;
 struct modes_head modes;
+struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);
 
 /**
  * Ungrabs all keys, to be called before re-grabbing the keys because of a
@@ -200,8 +196,7 @@ static char *get_config_path(const char *override_configpath) {
         xdg_config_home = "~/.config";
 
     xdg_config_home = resolve_tilde(xdg_config_home);
-    if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1)
-        die("asprintf() failed");
+    sasprintf(&config_path, "%s/i3/config", xdg_config_home);
     free(xdg_config_home);
 
     if (path_exists(config_path))
@@ -221,8 +216,7 @@ static char *get_config_path(const char *override_configpath) {
     char *tok = strtok(buf, ":");
     while (tok != NULL) {
         tok = resolve_tilde(tok);
-        if (asprintf(&config_path, "%s/i3/config", tok) == -1)
-            die("asprintf() failed");
+        sasprintf(&config_path, "%s/i3/config", tok);
         free(tok);
         if (path_exists(config_path)) {
             free(buf);
@@ -257,111 +251,142 @@ static void parse_configuration(const char *override_configpath) {
  *
  */
 void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) {
-        if (reload) {
-                /* First ungrab the keys */
-                ungrab_all_keys(conn);
+    if (reload) {
+        /* First ungrab the keys */
+        ungrab_all_keys(conn);
 
-                struct Mode *mode;
-                Binding *bind;
-                while (!SLIST_EMPTY(&modes)) {
-                        mode = SLIST_FIRST(&modes);
-                        FREE(mode->name);
-
-                        /* Clear the old binding list */
-                        bindings = mode->bindings;
-                        while (!TAILQ_EMPTY(bindings)) {
-                                bind = TAILQ_FIRST(bindings);
-                                TAILQ_REMOVE(bindings, bind, bindings);
-                                FREE(bind->translated_to);
-                                FREE(bind->command);
-                                FREE(bind);
-                        }
-                        FREE(bindings);
-                        SLIST_REMOVE(&modes, mode, Mode, modes);
-                }
+        struct Mode *mode;
+        Binding *bind;
+        while (!SLIST_EMPTY(&modes)) {
+            mode = SLIST_FIRST(&modes);
+            FREE(mode->name);
+
+            /* Clear the old binding list */
+            bindings = mode->bindings;
+            while (!TAILQ_EMPTY(bindings)) {
+                bind = TAILQ_FIRST(bindings);
+                TAILQ_REMOVE(bindings, bind, bindings);
+                FREE(bind->translated_to);
+                FREE(bind->command);
+                FREE(bind);
+            }
+            FREE(bindings);
+            SLIST_REMOVE(&modes, mode, Mode, modes);
+        }
 
-#if 0
-                struct Assignment *assign;
-                while (!TAILQ_EMPTY(&assignments)) {
-                        assign = TAILQ_FIRST(&assignments);
-                        FREE(assign->windowclass_title);
-                        TAILQ_REMOVE(&assignments, assign, assignments);
-                        FREE(assign);
-                }
-#endif
+        struct Assignment *assign;
+        while (!TAILQ_EMPTY(&assignments)) {
+            assign = TAILQ_FIRST(&assignments);
+            if (assign->type == A_TO_WORKSPACE)
+                FREE(assign->dest.workspace);
+            else if (assign->type == A_TO_OUTPUT)
+                FREE(assign->dest.output);
+            else if (assign->type == A_COMMAND)
+                FREE(assign->dest.command);
+            match_free(&(assign->match));
+            TAILQ_REMOVE(&assignments, assign, assignments);
+            FREE(assign);
+        }
+
+        /* Clear bar configs */
+        Barconfig *barconfig;
+        while (!TAILQ_EMPTY(&barconfigs)) {
+            barconfig = TAILQ_FIRST(&barconfigs);
+            FREE(barconfig->id);
+            for (int c = 0; c < barconfig->num_outputs; c++)
+                free(barconfig->outputs[c]);
+            FREE(barconfig->outputs);
+            FREE(barconfig->tray_output);
+            FREE(barconfig->socket_path);
+            FREE(barconfig->status_command);
+            FREE(barconfig->font);
+            FREE(barconfig->colors.background);
+            FREE(barconfig->colors.statusline);
+            FREE(barconfig->colors.focused_workspace_text);
+            FREE(barconfig->colors.focused_workspace_bg);
+            FREE(barconfig->colors.active_workspace_text);
+            FREE(barconfig->colors.active_workspace_bg);
+            FREE(barconfig->colors.inactive_workspace_text);
+            FREE(barconfig->colors.inactive_workspace_bg);
+            FREE(barconfig->colors.urgent_workspace_text);
+            FREE(barconfig->colors.urgent_workspace_bg);
+            TAILQ_REMOVE(&barconfigs, barconfig, configs);
+            FREE(barconfig);
+        }
 
-                /* Clear workspace names */
+        /* Clear workspace names */
 #if 0
-                Workspace *ws;
-                TAILQ_FOREACH(ws, workspaces, workspaces)
-                        workspace_set_name(ws, NULL);
+        Workspace *ws;
+        TAILQ_FOREACH(ws, workspaces, workspaces)
+            workspace_set_name(ws, NULL);
 #endif
-        }
+    }
 
-        SLIST_INIT(&modes);
+    SLIST_INIT(&modes);
 
-        struct Mode *default_mode = scalloc(sizeof(struct Mode));
-        default_mode->name = sstrdup("default");
-        default_mode->bindings = scalloc(sizeof(struct bindings_head));
-        TAILQ_INIT(default_mode->bindings);
-        SLIST_INSERT_HEAD(&modes, default_mode, modes);
+    struct Mode *default_mode = scalloc(sizeof(struct Mode));
+    default_mode->name = sstrdup("default");
+    default_mode->bindings = scalloc(sizeof(struct bindings_head));
+    TAILQ_INIT(default_mode->bindings);
+    SLIST_INSERT_HEAD(&modes, default_mode, modes);
 
-        bindings = default_mode->bindings;
+    bindings = default_mode->bindings;
 
 #define REQUIRED_OPTION(name) \
-        if (config.name == NULL) \
-                die("You did not specify required configuration option " #name "\n");
+    if (config.name == NULL) \
+        die("You did not specify required configuration option " #name "\n");
 
-        /* Clear the old config or initialize the data structure */
-        memset(&config, 0, sizeof(config));
+    /* Clear the old config or initialize the data structure */
+    memset(&config, 0, sizeof(config));
 
-        /* Initialize default colors */
+    /* Initialize default colors */
 #define INIT_COLOR(x, cborder, cbackground, ctext) \
-        do { \
-                x.border = get_colorpixel(cborder); \
-                x.background = get_colorpixel(cbackground); \
-                x.text = get_colorpixel(ctext); \
-        } while (0)
-
-        config.client.background = get_colorpixel("#000000");
-        INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff");
-        INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff");
-        INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888");
-        INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff");
-        INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff");
-        INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888");
-        INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff");
-
-        config.default_border = BS_NORMAL;
-        /* Set default_orientation to NO_ORIENTATION for auto orientation. */
-        config.default_orientation = NO_ORIENTATION;
-
-        parse_configuration(override_configpath);
-
-        if (reload) {
-                translate_keysyms();
-                grab_all_keys(conn, false);
-        }
+    do { \
+        x.border = get_colorpixel(cborder); \
+        x.background = get_colorpixel(cbackground); \
+        x.text = get_colorpixel(ctext); \
+    } while (0)
+
+    config.client.background = get_colorpixel("#000000");
+    INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff");
+    INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff");
+    INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888");
+    INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff");
+    INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff");
+    INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888");
+    INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff");
+
+    config.default_border = BS_NORMAL;
+    config.default_floating_border = BS_NORMAL;
+    /* Set default_orientation to NO_ORIENTATION for auto orientation. */
+    config.default_orientation = NO_ORIENTATION;
+
+    parse_configuration(override_configpath);
+
+    if (reload) {
+        translate_keysyms();
+        grab_all_keys(conn, false);
+    }
 
-        if (config.font.id == 0) {
-                ELOG("You did not specify required configuration option \"font\"\n");
-                config.font = load_font("fixed", true);
-        }
+    if (config.font.id == 0) {
+        ELOG("You did not specify required configuration option \"font\"\n");
+        config.font = load_font("fixed", true);
+    }
 
 #if 0
-        /* Set an empty name for every workspace which got no name */
-        Workspace *ws;
-        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                if (ws->name != NULL) {
-                        /* If the font was not specified when the workspace name
-                         * was loaded, we need to predict the text width now */
-                        if (ws->text_width == 0)
-                                ws->text_width = predict_text_width(global_conn,
-                                                config.font, ws->name, ws->name_len);
-                        continue;
-                }
-
-                workspace_set_name(ws, NULL);
-        }
+    /* Set an empty name for every workspace which got no name */
+    Workspace *ws;
+    TAILQ_FOREACH(ws, workspaces, workspaces) {
+            if (ws->name != NULL) {
+                    /* If the font was not specified when the workspace name
+                     * was loaded, we need to predict the text width now */
+                    if (ws->text_width == 0)
+                            ws->text_width = predict_text_width(global_conn,
+                                            config.font, ws->name, ws->name_len);
+                    continue;
+            }
+
+            workspace_set_name(ws, NULL);
+    }
 #endif
 }
index 1e0f828df76bc027d63736de610bad320662401c..30822353db2ab69a4e48e801759e12209f4a011c 100644 (file)
@@ -1,14 +1,11 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * debug.c: Contains debugging functions, especially FormatEvent, which prints unhandled events.
- *          This code is from xcb-util.
+ * debug.c: Debugging functions, especially FormatEvent, which prints unhandled
+ *          events.  This code is from xcb-util.
  *
  */
 #include <stdio.h>
index 0c1b6941e1a4008d787812bbd0adcf95bfc134e0..85daa433714a190033dee15aa94540c41b6ded79 100644 (file)
@@ -2,15 +2,11 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * ewmh.c: Functions to get/set certain EWMH properties easily.
+ * ewmh.c: Get/set certain EWMH properties easily.
  *
  */
-
 #include "all.h"
 
 /*
@@ -116,7 +112,6 @@ void ewmh_update_workarea() {
  *
  */
 void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows) {
-    DLOG("Updating _NET_CLIENT_LIST_STACKING\n");
     xcb_change_property(
         conn,
         XCB_PROP_MODE_REPLACE,
index b898d7bc6c901823191dc6a676f5681545aa48d1..90e5024e8dc11773cd5711ef902b001368c3e497 100644 (file)
@@ -2,16 +2,11 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * src/floating.c: contains all functions for handling floating clients
+ * floating.c: Floating windows.
  *
  */
-
-
 #include "all.h"
 
 extern xcb_connection_t *conn;
@@ -95,7 +90,7 @@ void floating_enable(Con *con, bool automatic) {
     }
 
     char *name;
-    asprintf(&name, "[i3 con] floatingcon around %p", con);
+    sasprintf(&name, "[i3 con] floatingcon around %p", con);
     x_set_name(nc, name);
     free(name);
 
@@ -173,6 +168,10 @@ void floating_enable(Con *con, bool automatic) {
     con->percent = 1.0;
     con->floating = FLOATING_USER_ON;
 
+    /* 4: set the border style as specified with new_float */
+    if (automatic)
+        con->border_style = config.default_floating_border;
+
     TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes);
     TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused);
 
@@ -297,7 +296,7 @@ bool floating_maybe_reassign_ws(Con *con) {
 }
 
 DRAGGING_CB(drag_window_callback) {
-    struct xcb_button_press_event_t *event = extra;
+    const struct xcb_button_press_event_t *event = extra;
 
     /* Reposition the client correctly while moving */
     con->rect.x = old_rect->x + (new_x - event->root_x);
@@ -318,7 +317,7 @@ DRAGGING_CB(drag_window_callback) {
  * Calls the drag_pointer function with the drag_window callback
  *
  */
-void floating_drag_window(Con *con, xcb_button_press_event_t *event) {
+void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
     DLOG("floating_drag_window\n");
 
     /* Push changes before dragging, so that the window gets raised now and not
@@ -338,14 +337,14 @@ void floating_drag_window(Con *con, xcb_button_press_event_t *event) {
  *
  */
 struct resize_window_callback_params {
-    border_t corner;
-    bool proportional;
-    xcb_button_press_event_t *event;
+    const border_t corner;
+    const bool proportional;
+    const xcb_button_press_event_t *event;
 };
 
 DRAGGING_CB(resize_window_callback) {
-    struct resize_window_callback_params *params = extra;
-    xcb_button_press_event_t *event = params->event;
+    const struct resize_window_callback_params *params = extra;
+    const xcb_button_press_event_t *event = params->event;
     border_t corner = params->corner;
 
     int32_t dest_x = con->rect.x;
@@ -398,8 +397,8 @@ DRAGGING_CB(resize_window_callback) {
  * Calls the drag_pointer function with the resize_window callback
  *
  */
-void floating_resize_window(Con *con, bool proportional,
-                            xcb_button_press_event_t *event) {
+void floating_resize_window(Con *con, const bool proportional,
+                            const xcb_button_press_event_t *event) {
     DLOG("floating_resize_window\n");
 
     /* corner saves the nearest corner to the original click. It contains
@@ -427,8 +426,8 @@ void floating_resize_window(Con *con, bool proportional,
  * the event and the new coordinates (x, y).
  *
  */
-void drag_pointer(Con *con, xcb_button_press_event_t *event, xcb_window_t
-                confine_to, border_t border, callback_t callback, void *extra)
+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)
 {
     uint32_t new_x, new_y;
     Rect old_rect;
@@ -516,31 +515,6 @@ void drag_pointer(Con *con, xcb_button_press_event_t *event, xcb_window_t
 }
 
 #if 0
-/*
- * Changes focus in the given direction for floating clients.
- *
- * Changing to the left/right means going to the previous/next floating client,
- * changing to top/bottom means cycling through the Z-index.
- *
- */
-void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
-        DLOG("floating focus\n");
-
-        if (direction == D_LEFT || direction == D_RIGHT) {
-                /* Go to the next/previous floating client */
-                Client *client;
-
-                while ((client = (direction == D_LEFT ? TAILQ_PREV(currently_focused, floating_clients_head, floating_clients) :
-                                                        TAILQ_NEXT(currently_focused, floating_clients))) !=
-                       TAILQ_END(&(currently_focused->workspace->floating_clients))) {
-                        if (!client->floating)
-                                continue;
-                        set_focus(conn, client, true);
-                        return;
-                }
-        }
-}
-
 /*
  * Moves the client 10px to the specified direction.
  *
index 336a491f67f6b27300c6cff50226136e9ea3abc6..5e628bdf334ad512d231234d313ec689411f9168 100644 (file)
@@ -2,16 +2,19 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * handlers.c: Small handlers for various events (keypresses, focus changes,
+ *             …).
  *
  */
-#include <time.h>
+#include "all.h"
 
+#include <time.h>
 #include <xcb/randr.h>
-
 #include <X11/XKBlib.h>
-
-#include "all.h"
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-monitor.h>
 
 int randr_base = -1;
 
@@ -80,6 +83,9 @@ bool event_is_ignored(const int sequence, const int response_type) {
  *
  */
 static int handle_key_press(xcb_key_press_event_t *event) {
+
+    last_timestamp = event->time;
+
     DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
 
     /* Remove the numlock bit, all other bits are modifiers we can bind to */
@@ -142,7 +148,11 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
 
     /* Focus the output on which the user moved his cursor */
     Con *old_focused = focused;
-    con_focus(con_descend_focused(output_get_content(output->con)));
+    Con *next = con_descend_focused(output_get_content(output->con));
+    /* Since we are switching outputs, this *must* be a different workspace, so
+     * call workspace_show() */
+    workspace_show(con_get_workspace(next));
+    con_focus(next);
 
     /* If the focus changed, we re-render to get updated decorations */
     if (old_focused != focused)
@@ -156,6 +166,8 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
 static int handle_enter_notify(xcb_enter_notify_event_t *event) {
     Con *con;
 
+    last_timestamp = event->time;
+
     DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n",
          event->event, event->mode, event->detail, event->sequence);
     DLOG("coordinates %d, %d\n", event->event_x, event->event_y);
@@ -214,6 +226,13 @@ static int handle_enter_notify(xcb_enter_notify_event_t *event) {
     if (config.disable_focus_follows_mouse)
         return 1;
 
+    /* Get the currently focused workspace to check if the focus change also
+     * involves changing workspaces. If so, we need to call workspace_show() to
+     * correctly update state and send the IPC event. */
+    Con *ws = con_get_workspace(con);
+    if (ws != con_get_workspace(focused))
+        workspace_show(ws);
+
     con_focus(con_descend_focused(con));
     tree_render();
 
@@ -227,6 +246,9 @@ static int handle_enter_notify(xcb_enter_notify_event_t *event) {
  *
  */
 static int handle_motion_notify(xcb_motion_notify_event_t *event) {
+
+    last_timestamp = event->time;
+
     /* Skip events where the pointer was over a child window, we are only
      * interested in events on the root window. */
     if (event->child != 0)
@@ -275,7 +297,7 @@ static int handle_mapping_notify(xcb_mapping_notify_event_t *event) {
     DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
     xcb_refresh_keyboard_mapping(keysyms, event);
 
-    xcb_get_numlock_mask(conn);
+    xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
 
     ungrab_all_keys(conn);
     translate_keysyms();
@@ -557,6 +579,21 @@ static bool handle_windowname_change_legacy(void *data, xcb_connection_t *conn,
     return true;
 }
 
+/*
+ * Called when a window changes its WM_WINDOW_ROLE.
+ *
+ */
+static bool handle_windowrole_change(void *data, xcb_connection_t *conn, uint8_t state,
+                                     xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
+    Con *con;
+    if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
+        return false;
+
+    window_update_role(con->window, prop, false);
+
+    return true;
+}
+
 #if 0
 /*
  * Updates the client’s WM_CLASS property
@@ -604,20 +641,25 @@ static int handle_expose_event(xcb_expose_event_t *event) {
  * Handle client messages (EWMH)
  *
  */
-static int handle_client_message(xcb_client_message_event_t *event) {
+static void handle_client_message(xcb_client_message_event_t *event) {
+    /* If this is a startup notification ClientMessage, the library will handle
+     * it and call our monitor_event() callback. */
+    if (sn_xcb_display_process_event(sndisplay, (xcb_generic_event_t*)event))
+        return;
+
     LOG("ClientMessage for window 0x%08x\n", event->window);
     if (event->type == A__NET_WM_STATE) {
         if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) {
             DLOG("atom in clientmessage is %d, fullscreen is %d\n",
                     event->data.data32[1], A__NET_WM_STATE_FULLSCREEN);
             DLOG("not about fullscreen atom\n");
-            return 0;
+            return;
         }
 
         Con *con = con_by_window_id(event->window);
         if (con == NULL) {
             DLOG("Could not get window for client message\n");
-            return 0;
+            return;
         }
 
         /* Check if the fullscreen state should be toggled */
@@ -633,12 +675,29 @@ static int handle_client_message(xcb_client_message_event_t *event) {
 
         tree_render();
         x_push_changes(croot);
+    } else if (event->type == A_I3_SYNC) {
+        DLOG("i3 sync, yay\n");
+        xcb_window_t window = event->data.data32[0];
+        uint32_t rnd = event->data.data32[1];
+        DLOG("Sending random value %d back to X11 window 0x%08x\n", rnd, window);
+
+        void *reply = scalloc(32);
+        xcb_client_message_event_t *ev = reply;
+
+        ev->response_type = XCB_CLIENT_MESSAGE;
+        ev->window = window;
+        ev->type = A_I3_SYNC;
+        ev->format = 32;
+        ev->data.data32[0] = window;
+        ev->data.data32[1] = rnd;
+
+        xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)ev);
+        xcb_flush(conn);
+        free(reply);
     } else {
-        ELOG("unhandled clientmessage\n");
-        return 0;
+        DLOG("unhandled clientmessage\n");
+        return;
     }
-
-    return 1;
 }
 
 #if 0
@@ -910,6 +969,14 @@ static int handle_focus_in(xcb_focus_in_event_t *event) {
     }
 
     DLOG("focus is different, updating decorations\n");
+
+    /* Get the currently focused workspace to check if the focus change also
+     * involves changing workspaces. If so, we need to call workspace_show() to
+     * correctly update state and send the IPC event. */
+    Con *ws = con_get_workspace(con);
+    if (ws != con_get_workspace(focused))
+        workspace_show(ws);
+
     con_focus(con);
     /* We update focused_id because we don’t need to set focus again */
     focused_id = event->event;
@@ -933,7 +1000,8 @@ static struct property_handler_t property_handlers[] = {
     { 0, 128, handle_windowname_change_legacy },
     { 0, UINT_MAX, handle_normal_hints },
     { 0, UINT_MAX, handle_clientleader_change },
-    { 0, UINT_MAX, handle_transient_for }
+    { 0, UINT_MAX, handle_transient_for },
+    { 0, 128, handle_windowrole_change }
 };
 #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
 
@@ -943,12 +1011,16 @@ static struct property_handler_t property_handlers[] = {
  *
  */
 void property_handlers_init() {
+
+    sn_monitor_context_new(sndisplay, conn_screen, startup_monitor_event, NULL, NULL);
+
     property_handlers[0].atom = A__NET_WM_NAME;
     property_handlers[1].atom = XCB_ATOM_WM_HINTS;
     property_handlers[2].atom = XCB_ATOM_WM_NAME;
     property_handlers[3].atom = XCB_ATOM_WM_NORMAL_HINTS;
     property_handlers[4].atom = A_WM_CLIENT_LEADER;
     property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR;
+    property_handlers[6].atom = A_WM_WINDOW_ROLE;
 }
 
 static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
@@ -964,7 +1036,7 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom)
     }
 
     if (handler == NULL) {
-        DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom);
+        //DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom);
         return;
     }
 
@@ -1045,14 +1117,15 @@ void handle_event(int type, xcb_generic_event_t *event) {
             handle_focus_in((xcb_focus_in_event_t*)event);
             break;
 
-        case XCB_PROPERTY_NOTIFY:
-            DLOG("Property notify\n");
+        case XCB_PROPERTY_NOTIFY: {
             xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event;
+            last_timestamp = e->time;
             property_notify(e->state, e->window, e->atom);
             break;
+        }
 
         default:
-            DLOG("Unhandled event of type %d\n", type);
+            //DLOG("Unhandled event of type %d\n", type);
             break;
     }
 }
index e03bdcbd07ccdc712a6c5e90459cf58b810a90b1..0a738427eb101f26c669611f1646ab6145f6ee8a 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -2,14 +2,13 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * ipc.c: Everything about the UNIX domain sockets for IPC
+ * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).
  *
  */
+#include "all.h"
+
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <fcntl.h>
@@ -19,8 +18,6 @@
 #include <yajl/yajl_parse.h>
 #include <yajl/yajl_version.h>
 
-#include "all.h"
-
 char *current_socketpath = NULL;
 
 /* Shorter names for all those yajl_gen_* functions */
@@ -72,35 +69,6 @@ static bool mkdirp(const char *path) {
     return result;
 }
 
-static void ipc_send_message(int fd, const unsigned char *payload,
-                             int message_type, int message_size) {
-    int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) +
-                      sizeof(uint32_t) + message_size;
-    char msg[buffer_size];
-    char *walk = msg;
-
-    strncpy(walk, "i3-ipc", buffer_size - 1);
-    walk += strlen("i3-ipc");
-    memcpy(walk, &message_size, sizeof(uint32_t));
-    walk += sizeof(uint32_t);
-    memcpy(walk, &message_type, sizeof(uint32_t));
-    walk += sizeof(uint32_t);
-    memcpy(walk, payload, message_size);
-
-    int sent_bytes = 0;
-    int bytes_to_go = buffer_size;
-    while (sent_bytes < bytes_to_go) {
-        int n = write(fd, msg + sent_bytes, bytes_to_go);
-        if (n == -1) {
-            DLOG("write() failed: %s\n", strerror(errno));
-            return;
-        }
-
-        sent_bytes += n;
-        bytes_to_go -= n;
-    }
-}
-
 /*
  * Sends the specified event to all IPC clients which are currently connected
  * and subscribed to this kind of event.
@@ -120,8 +88,7 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa
         if (!interested)
             continue;
 
-        ipc_send_message(current->fd, (const unsigned char*)payload,
-                         message_type, strlen(payload));
+        ipc_send_message(current->fd, strlen(payload), message_type, (const uint8_t*)payload);
     }
 }
 
@@ -132,9 +99,12 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa
  */
 void ipc_shutdown() {
     ipc_client *current;
-    TAILQ_FOREACH(current, &all_clients, clients) {
+    while (!TAILQ_EMPTY(&all_clients)) {
+        current = TAILQ_FIRST(&all_clients);
         shutdown(current->fd, SHUT_RDWR);
         close(current->fd);
+        TAILQ_REMOVE(&all_clients, current, clients);
+        free(current);
     }
 }
 
@@ -156,8 +126,7 @@ IPC_HANDLER(command) {
     /* If no reply was provided, we just use the default success message */
     if (reply == NULL)
         reply = "{\"success\":true}";
-    ipc_send_message(fd, (const unsigned char*)reply,
-                     I3_IPC_REPLY_TYPE_COMMAND, strlen(reply));
+    ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_COMMAND, (const uint8_t*)reply);
 
     FREE(save_reply);
 }
@@ -205,6 +174,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     ystr("urgent");
     y(bool, con->urgent);
 
+    if (con->mark != NULL) {
+        ystr("mark");
+        ystr(con->mark);
+    }
+
     ystr("focused");
     y(bool, (con == focused));
 
@@ -334,10 +308,11 @@ IPC_HANDLER(tree) {
 #endif
     y(get_buf, &payload, &length);
 
-    ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_TREE, length);
+    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_TREE, payload);
     y(free);
 }
 
+
 /*
  * Formats the reply message for a GET_WORKSPACES request and sends it to the
  * client
@@ -406,7 +381,7 @@ IPC_HANDLER(get_workspaces) {
 #endif
     y(get_buf, &payload, &length);
 
-    ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length);
+    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_WORKSPACES, payload);
     y(free);
 }
 
@@ -464,7 +439,176 @@ IPC_HANDLER(get_outputs) {
 #endif
     y(get_buf, &payload, &length);
 
-    ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length);
+    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_OUTPUTS, payload);
+    y(free);
+}
+
+/*
+ * Formats the reply message for a GET_MARKS request and sends it to the
+ * client
+ *
+ */
+IPC_HANDLER(get_marks) {
+#if YAJL_MAJOR >= 2
+    yajl_gen gen = yajl_gen_alloc(NULL);
+#else
+    yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+#endif
+    y(array_open);
+
+    Con *con;
+    TAILQ_FOREACH(con, &all_cons, all_cons)
+        if (con->mark != NULL)
+            ystr(con->mark);
+
+    y(array_close);
+
+    const unsigned char *payload;
+#if YAJL_MAJOR >= 2
+    size_t length;
+#else
+    unsigned int length;
+#endif
+    y(get_buf, &payload, &length);
+
+    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_MARKS, payload);
+    y(free);
+}
+
+/*
+ * Formats the reply message for a GET_BAR_CONFIG request and sends it to the
+ * client.
+ *
+ */
+IPC_HANDLER(get_bar_config) {
+#if YAJL_MAJOR >= 2
+    yajl_gen gen = yajl_gen_alloc(NULL);
+#else
+    yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+#endif
+
+    /* If no ID was passed, we return a JSON array with all IDs */
+    if (message_size == 0) {
+        y(array_open);
+        Barconfig *current;
+        TAILQ_FOREACH(current, &barconfigs, configs) {
+            ystr(current->id);
+        }
+        y(array_close);
+
+        const unsigned char *payload;
+#if YAJL_MAJOR >= 2
+        size_t length;
+#else
+        unsigned int length;
+#endif
+        y(get_buf, &payload, &length);
+
+        ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload);
+        y(free);
+        return;
+    }
+
+    /* To get a properly terminated buffer, we copy
+     * message_size bytes out of the buffer */
+    char *bar_id = scalloc(message_size + 1);
+    strncpy(bar_id, (const char*)message, message_size);
+    LOG("IPC: looking for config for bar ID \"%s\"\n", bar_id);
+    Barconfig *current, *config = NULL;
+    TAILQ_FOREACH(current, &barconfigs, configs) {
+        if (strcmp(current->id, bar_id) != 0)
+            continue;
+
+        config = current;
+        break;
+    }
+
+    y(map_open);
+
+    if (!config) {
+        /* If we did not find a config for the given ID, the reply will contain
+         * a null 'id' field. */
+        ystr("id");
+        y(null);
+    } else {
+        ystr("id");
+        ystr(config->id);
+
+        if (config->num_outputs > 0) {
+            ystr("outputs");
+            y(array_open);
+            for (int c = 0; c < config->num_outputs; c++)
+                ystr(config->outputs[c]);
+            y(array_close);
+        }
+
+#define YSTR_IF_SET(name) \
+        do { \
+            if (config->name) { \
+                ystr( # name); \
+                ystr(config->name); \
+            } \
+        } while (0)
+
+        YSTR_IF_SET(tray_output);
+        YSTR_IF_SET(socket_path);
+
+        ystr("mode");
+        if (config->mode == M_HIDE)
+            ystr("hide");
+        else ystr("dock");
+
+        ystr("position");
+        if (config->position == P_BOTTOM)
+            ystr("bottom");
+        else ystr("top");
+
+        YSTR_IF_SET(status_command);
+        YSTR_IF_SET(font);
+
+        ystr("workspace_buttons");
+        y(bool, !config->hide_workspace_buttons);
+
+        ystr("verbose");
+        y(bool, config->verbose);
+
+#undef YSTR_IF_SET
+#define YSTR_IF_SET(name) \
+        do { \
+            if (config->colors.name) { \
+                ystr( # name); \
+                ystr(config->colors.name); \
+            } \
+        } while (0)
+
+        ystr("colors");
+        y(map_open);
+        YSTR_IF_SET(background);
+        YSTR_IF_SET(statusline);
+        YSTR_IF_SET(focused_workspace_text);
+        YSTR_IF_SET(focused_workspace_bg);
+        YSTR_IF_SET(active_workspace_text);
+        YSTR_IF_SET(active_workspace_bg);
+        YSTR_IF_SET(inactive_workspace_text);
+        YSTR_IF_SET(inactive_workspace_bg);
+        YSTR_IF_SET(urgent_workspace_text);
+        YSTR_IF_SET(urgent_workspace_bg);
+        y(map_close);
+
+#undef YSTR_IF_SET
+    }
+
+    y(map_close);
+
+    const unsigned char *payload;
+#if YAJL_MAJOR >= 2
+    size_t length;
+#else
+    unsigned int length;
+#endif
+    y(get_buf, &payload, &length);
+
+    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload);
     y(free);
 }
 
@@ -542,25 +686,25 @@ IPC_HANDLER(subscribe) {
         yajl_free_error(p, err);
 
         const char *reply = "{\"success\":false}";
-        ipc_send_message(fd, (const unsigned char*)reply,
-                         I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
+        ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t*)reply);
         yajl_free(p);
         return;
     }
     yajl_free(p);
     const char *reply = "{\"success\":true}";
-    ipc_send_message(fd, (const unsigned char*)reply,
-                     I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
+    ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t*)reply);
 }
 
 /* The index of each callback function corresponds to the numeric
  * value of the message type (see include/i3/ipc.h) */
-handler_t handlers[5] = {
+handler_t handlers[7] = {
     handle_command,
     handle_get_workspaces,
     handle_subscribe,
     handle_get_outputs,
-    handle_tree
+    handle_tree,
+    handle_get_marks,
+    handle_get_bar_config
 };
 
 /*
@@ -602,10 +746,12 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
             /* We can call TAILQ_REMOVE because we break out of the
              * TAILQ_FOREACH afterwards */
             TAILQ_REMOVE(&all_clients, current, clients);
+            free(current);
             break;
         }
 
         ev_io_stop(EV_A_ w);
+        free(w);
 
         DLOG("IPC: client disconnected\n");
         return;
@@ -683,7 +829,7 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
     ev_io_init(package, ipc_receive_message, client, EV_READ);
     ev_io_start(EV_A_ package);
 
-    DLOG("IPC: new client connected\n");
+    DLOG("IPC: new client connected on fd %d\n", w->fd);
 
     ipc_client *new = scalloc(sizeof(ipc_client));
     new->fd = client;
index b61a0e5c490210a72d30255ebef30ed576469c4c..ef787fd1a8356df2812c25665b2b270bb7eafb75 100644 (file)
@@ -1,14 +1,20 @@
 /*
  * vim:ts=4:sw=4:expandtab
  *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * load_layout.c: Restore (parts of) the layout, for example after an inplace
+ *                restart.
+ *
  */
+#include "all.h"
+
 #include <yajl/yajl_common.h>
 #include <yajl/yajl_gen.h>
 #include <yajl/yajl_parse.h>
 #include <yajl/yajl_version.h>
 
-#include "all.h"
-
 /* TODO: refactor the whole parsing thing */
 
 static char *last_key;
@@ -111,7 +117,7 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
             LOG("sticky_group of this container is %s\n", json_node->sticky_group);
         } else if (strcasecmp(last_key, "orientation") == 0) {
             char *buf = NULL;
-            asprintf(&buf, "%.*s", (int)len, val);
+            sasprintf(&buf, "%.*s", (int)len, val);
             if (strcasecmp(buf, "none") == 0)
                 json_node->orientation = NO_ORIENTATION;
             else if (strcasecmp(buf, "horizontal") == 0)
@@ -122,7 +128,7 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
             free(buf);
         } else if (strcasecmp(last_key, "border") == 0) {
             char *buf = NULL;
-            asprintf(&buf, "%.*s", (int)len, val);
+            sasprintf(&buf, "%.*s", (int)len, val);
             if (strcasecmp(buf, "none") == 0)
                 json_node->border_style = BS_NONE;
             else if (strcasecmp(buf, "1pixel") == 0)
@@ -133,7 +139,7 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
             free(buf);
         } else if (strcasecmp(last_key, "layout") == 0) {
             char *buf = NULL;
-            asprintf(&buf, "%.*s", (int)len, val);
+            sasprintf(&buf, "%.*s", (int)len, val);
             if (strcasecmp(buf, "default") == 0)
                 json_node->layout = L_DEFAULT;
             else if (strcasecmp(buf, "stacked") == 0)
@@ -146,6 +152,10 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
                 json_node->layout = L_OUTPUT;
             else LOG("Unhandled \"layout\": %s\n", buf);
             free(buf);
+        } else if (strcasecmp(last_key, "mark") == 0) {
+            char *buf = NULL;
+            sasprintf(&buf, "%.*s", (int)len, val);
+            json_node->mark = buf;
         }
     }
     return 1;
index 5e1c35eb455c42d1ff8112c824c8bc3362066c70..a615978065eaf8570f96e970c706cf95ffcfa518 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -2,12 +2,9 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * src/log.c: handles the setting of loglevels, contains the logging functions.
+ * log.c: Setting of loglevels, logging functions.
  *
  */
 #include <stdarg.h>
@@ -87,12 +84,13 @@ void add_loglevel(const char *level) {
  *
  */
 void vlog(char *fmt, va_list args) {
-    char timebuf[64];
+    static char timebuf[64];
+    static struct tm result;
 
     /* Get current time */
     time_t t = time(NULL);
     /* Convert time to local time (determined by the locale) */
-    struct tm *tmp = localtime(&t);
+    struct tm *tmp = localtime_r(&t, &result);
     /* Generate time prefix */
     strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp);
 #ifdef DEBUG_TIMING
index 77e295fcfbae74647ba5d7fbc02619e988975898..062a48626d8765d1a99bd803847fe952b8fa424f 100644 (file)
@@ -1,10 +1,28 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * main.c: Initialization, main loop
+ *
  */
 #include <ev.h>
 #include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/time.h>
+#include <sys/resource.h>
 #include "all.h"
 
+#include "sd-daemon.h"
+
+/* The original value of RLIMIT_CORE when i3 was started. We need to restore
+ * this before starting any other process, since we set RLIMIT_CORE to
+ * RLIM_INFINITY for i3 debugging versions. */
+struct rlimit original_rlimit_core;
+
 static int xkb_event_base;
 
 int xkb_current_group;
@@ -14,6 +32,16 @@ extern Con *focused;
 char **start_argv;
 
 xcb_connection_t *conn;
+/* The screen (0 when you are using DISPLAY=:0) of the connection 'conn' */
+int conn_screen;
+
+/* Display handle for libstartup-notification */
+SnDisplay *sndisplay;
+
+/* The last timestamp we got from X11 (timestamps are included in some events
+ * and are used for some things, like determining a unique ID in startup
+ * notification). */
+xcb_timestamp_t last_timestamp = XCB_CURRENT_TIME;
 
 xcb_screen_t *root_screen;
 xcb_window_t root;
@@ -46,6 +74,11 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment
 bool xcursor_supported = true;
 bool xkb_supported = true;
 
+/* This will be set to true when -C is used so that functions can behave
+ * slightly differently. We don’t want i3-nagbar to be started when validating
+ * the config, for example. */
+bool only_check_config = false;
+
 /*
  * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
  * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
@@ -152,7 +185,7 @@ static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
     xcb_key_symbols_free(keysyms);
     keysyms = xcb_key_symbols_alloc(conn);
 
-    xcb_get_numlock_mask(conn);
+    xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
 
     ungrab_all_keys(conn);
     DLOG("Re-grabbing...\n");
@@ -161,14 +194,24 @@ static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
     DLOG("Done\n");
 }
 
+/*
+ * Exit handler which destroys the main_loop. Will trigger cleanup handlers.
+ *
+ */
+static void i3_exit() {
+/* We need ev >= 4 for the following code. Since it is not *that* important (it
+ * only makes sure that there are no i3-nagbar instances left behind) we still
+ * support old systems with libev 3. */
+#if EV_VERSION_MAJOR >= 4
+    ev_loop_destroy(main_loop);
+#endif
+}
+
 int main(int argc, char *argv[]) {
-    //parse_cmd("[ foo ] attach, attach ; focus");
-    int screens;
     char *override_configpath = NULL;
     bool autostart = true;
     char *layout_path = NULL;
     bool delete_layout_path = false;
-    bool only_check_config = false;
     bool force_xinerama = false;
     bool disable_signalhandler = false;
     static struct option long_options[] = {
@@ -179,17 +222,26 @@ int main(int argc, char *argv[]) {
         {"layout", required_argument, 0, 'L'},
         {"restart", required_argument, 0, 0},
         {"force-xinerama", no_argument, 0, 0},
+        {"force_xinerama", no_argument, 0, 0},
         {"disable-signalhandler", no_argument, 0, 0},
+        {"get-socketpath", no_argument, 0, 0},
+        {"get_socketpath", no_argument, 0, 0},
         {0, 0, 0, 0}
     };
     int option_index = 0, opt;
 
     setlocale(LC_ALL, "");
 
+    /* Get the RLIMIT_CORE limit at startup time to restore this before
+     * starting processes. */
+    getrlimit(RLIMIT_CORE, &original_rlimit_core);
+
     /* Disable output buffering to make redirects in .xsession actually useful for debugging */
     if (!isatty(fileno(stdout)))
         setbuf(stdout, NULL);
 
+    srand(time(NULL));
+
     init_logging();
 
     start_argv = argv;
@@ -227,7 +279,8 @@ int main(int argc, char *argv[]) {
                 /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
                 break;
             case 0:
-                if (strcmp(long_options[option_index].name, "force-xinerama") == 0) {
+                if (strcmp(long_options[option_index].name, "force-xinerama") == 0 ||
+                    strcmp(long_options[option_index].name, "force_xinerama") == 0) {
                     force_xinerama = true;
                     ELOG("Using Xinerama instead of RandR. This option should be "
                          "avoided at all cost because it does not refresh the list "
@@ -238,6 +291,15 @@ int main(int argc, char *argv[]) {
                 } else if (strcmp(long_options[option_index].name, "disable-signalhandler") == 0) {
                     disable_signalhandler = true;
                     break;
+                } else if (strcmp(long_options[option_index].name, "get-socketpath") == 0 ||
+                           strcmp(long_options[option_index].name, "get_socketpath") == 0) {
+                    char *socket_path = socket_path_from_x11();
+                    if (socket_path) {
+                        printf("%s\n", socket_path);
+                        return 0;
+                    }
+
+                    return 1;
                 } else if (strcmp(long_options[option_index].name, "restart") == 0) {
                     FREE(layout_path);
                     layout_path = sstrdup(optarg);
@@ -248,26 +310,125 @@ int main(int argc, char *argv[]) {
             default:
                 fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]);
                 fprintf(stderr, "\n");
-                fprintf(stderr, "-a: disable autostart\n");
-                fprintf(stderr, "-L <layoutfile>: load the layout from <layoutfile>\n");
-                fprintf(stderr, "-v: display version and exit\n");
-                fprintf(stderr, "-V: enable verbose mode\n");
-                fprintf(stderr, "-d <loglevel>: enable debug loglevel <loglevel>\n");
-                fprintf(stderr, "-c <configfile>: use the provided configfile instead\n");
-                fprintf(stderr, "-C: check configuration file and exit\n");
-                fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This "
-                                "option should only be used if you are stuck with the "
-                                "nvidia closed source driver which does not support RandR.\n");
+                fprintf(stderr, "\t-a          disable autostart ('exec' lines in config)\n");
+                fprintf(stderr, "\t-c <file>   use the provided configfile instead\n");
+                fprintf(stderr, "\t-C          validate configuration file and exit\n");
+                fprintf(stderr, "\t-d <level>  enable debug output with the specified loglevel\n");
+                fprintf(stderr, "\t-L <file>   path to the serialized layout during restarts\n");
+                fprintf(stderr, "\t-v          display version and exit\n");
+                fprintf(stderr, "\t-V          enable verbose mode\n");
+                fprintf(stderr, "\n");
+                fprintf(stderr, "\t--force-xinerama\n"
+                                "\tUse Xinerama instead of RandR.\n"
+                                "\tThis option should only be used if you are stuck with the\n"
+                                "\tnvidia closed source driver which does not support RandR.\n");
+                fprintf(stderr, "\n");
+                fprintf(stderr, "\t--get-socketpath\n"
+                                "\tRetrieve the i3 IPC socket path from X11, print it, then exit.\n");
+                fprintf(stderr, "\n");
+                fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n"
+                                "to send to a currently running i3 (like i3-msg). This allows you to\n"
+                                "use nice and logical commands, such as:\n"
+                                "\n"
+                                "\ti3 border none\n"
+                                "\ti3 floating toggle\n"
+                                "\ti3 kill window\n"
+                                "\n");
                 exit(EXIT_FAILURE);
         }
     }
 
+    /* If the user passes more arguments, we act like i3-msg would: Just send
+     * the arguments as an IPC message to i3. This allows for nice semantic
+     * commands such as 'i3 border none'. */
+    if (optind < argc) {
+        /* We enable verbose mode so that the user knows what’s going on.
+         * This should make it easier to find mistakes when the user passes
+         * arguments by mistake. */
+        set_verbosity(true);
+
+        LOG("Additional arguments passed. Sending them as a command to i3.\n");
+        char *payload = NULL;
+        while (optind < argc) {
+            if (!payload) {
+                payload = sstrdup(argv[optind]);
+            } else {
+                char *both;
+                sasprintf(&both, "%s %s", payload, argv[optind]);
+                free(payload);
+                payload = both;
+            }
+            optind++;
+        }
+        LOG("Command is: %s (%d bytes)\n", payload, strlen(payload));
+        char *socket_path = socket_path_from_x11();
+        if (!socket_path) {
+            ELOG("Could not get i3 IPC socket path\n");
+            return 1;
+        }
+
+        int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+        if (sockfd == -1)
+            err(EXIT_FAILURE, "Could not create socket");
+
+        struct sockaddr_un addr;
+        memset(&addr, 0, sizeof(struct sockaddr_un));
+        addr.sun_family = AF_LOCAL;
+        strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
+        if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
+            err(EXIT_FAILURE, "Could not connect to i3");
+
+        if (ipc_send_message(sockfd, strlen(payload), I3_IPC_MESSAGE_TYPE_COMMAND,
+                             (uint8_t*)payload) == -1)
+            err(EXIT_FAILURE, "IPC: write()");
+
+        uint32_t reply_length;
+        uint8_t *reply;
+        int ret;
+        if ((ret = ipc_recv_message(sockfd, I3_IPC_MESSAGE_TYPE_COMMAND,
+                                    &reply_length, &reply)) != 0) {
+            if (ret == -1)
+                err(EXIT_FAILURE, "IPC: read()");
+            return 1;
+        }
+        printf("%.*s\n", reply_length, reply);
+        return 0;
+    }
+
+    /* I3_VERSION contains either something like this:
+     *     "4.0.2 (2011-11-11, branch "release")".
+     * or: "4.0.2-123-gCOFFEEBABE (2011-11-11, branch "next")".
+     *
+     * So we check for the offset of the first opening round bracket to
+     * determine whether this is a git version or a release version. */
+    if ((strchr(I3_VERSION, '(') - I3_VERSION) > 10) {
+        struct rlimit limit = { RLIM_INFINITY, RLIM_INFINITY };
+        setrlimit(RLIMIT_CORE, &limit);
+
+        /* The following code is helpful, but not required. We thus don’t pay
+         * much attention to error handling, non-linux or other edge cases. */
+        char cwd[PATH_MAX];
+        LOG("CORE DUMPS: You are running a development version of i3, so coredumps were automatically enabled (ulimit -c unlimited).\n");
+        if (getcwd(cwd, sizeof(cwd)) != NULL)
+            LOG("CORE DUMPS: Your current working directory is \"%s\".\n", cwd);
+        int patternfd;
+        if ((patternfd = open("/proc/sys/kernel/core_pattern", O_RDONLY)) >= 0) {
+            memset(cwd, '\0', sizeof(cwd));
+            if (read(patternfd, cwd, sizeof(cwd)) > 0)
+                /* a trailing newline is included in cwd */
+                LOG("CORE DUMPS: Your core_pattern is: %s", cwd);
+            close(patternfd);
+        }
+    }
+
     LOG("i3 (tree) version " I3_VERSION " starting\n");
 
-    conn = xcb_connect(NULL, &screens);
+    conn = xcb_connect(NULL, &conn_screen);
     if (xcb_connection_has_error(conn))
         errx(EXIT_FAILURE, "Cannot open display\n");
 
+    sndisplay = sn_xcb_display_new(conn, NULL, NULL);
+
     /* Initialize the libev event loop. This needs to be done before loading
      * the config file because the parser will install an ev_child watcher
      * for the nagbar when config errors are found. */
@@ -275,7 +436,7 @@ int main(int argc, char *argv[]) {
     if (main_loop == NULL)
             die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
 
-    root_screen = xcb_aux_get_screen(conn, screens);
+    root_screen = xcb_aux_get_screen(conn, conn_screen);
     root = root_screen->root;
     root_depth = root_screen->root_depth;
     xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
@@ -338,17 +499,9 @@ int main(int argc, char *argv[]) {
 
     /* Set a cursor for the root window (otherwise the root window will show no
        cursor until the first client is launched). */
-    if (xcursor_supported) {
-        xcursor_set_root_cursor();
-    } else {
-        xcb_cursor_t cursor_id = xcb_generate_id(conn);
-        i3Font cursor_font = load_font("cursor", false);
-        int xcb_cursor = xcursor_get_xcb_cursor(XCURSOR_CURSOR_POINTER);
-        xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id,
-                xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535);
-        xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, &cursor_id);
-        xcb_free_cursor(conn, cursor_id);
-    }
+    if (xcursor_supported)
+        xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER);
+    else xcb_set_root_cursor(XCURSOR_CURSOR_POINTER);
 
     if (xkb_supported) {
         int errBase,
@@ -404,7 +557,7 @@ int main(int argc, char *argv[]) {
 
     keysyms = xcb_key_symbols_alloc(conn);
 
-    xcb_get_numlock_mask(conn);
+    xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
 
     translate_keysyms();
     grab_all_keys(conn, false);
@@ -422,7 +575,10 @@ int main(int argc, char *argv[]) {
 
     free(greply);
 
-    if (force_xinerama) {
+    /* Force Xinerama (for drivers which don't support RandR yet, esp. the
+     * nVidia binary graphics driver), when specified either in the config
+     * file or on command-line */
+    if (force_xinerama || config.force_xinerama) {
         xinerama_init();
     } else {
         DLOG("Checking for XRandR...\n");
@@ -459,6 +615,21 @@ int main(int argc, char *argv[]) {
         ev_io_start(main_loop, ipc_io);
     }
 
+    /* Also handle the UNIX domain sockets passed via socket activation */
+    int fds = sd_listen_fds(1);
+    if (fds < 0)
+        ELOG("socket activation: Error in sd_listen_fds\n");
+    else if (fds == 0)
+        DLOG("socket activation: no sockets passed\n");
+    else {
+        for (int fd = SD_LISTEN_FDS_START; fd < (SD_LISTEN_FDS_START + fds); fd++) {
+            DLOG("socket activation: also listening on fd %d\n", fd);
+            struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
+            ev_io_init(ipc_io, ipc_new_client, fd, EV_READ);
+            ev_io_start(main_loop, ipc_io);
+        }
+    }
+
     /* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */
     x_set_i3_atoms();
 
@@ -501,7 +672,7 @@ int main(int argc, char *argv[]) {
         struct Autostart *exec;
         TAILQ_FOREACH(exec, &autostarts, autostarts) {
             LOG("auto-starting %s\n", exec->command);
-            start_application(exec->command);
+            start_application(exec->command, exec->no_startup_id);
         }
     }
 
@@ -509,8 +680,23 @@ int main(int argc, char *argv[]) {
     struct Autostart *exec_always;
     TAILQ_FOREACH(exec_always, &autostarts_always, autostarts_always) {
         LOG("auto-starting (always!) %s\n", exec_always->command);
-        start_application(exec_always->command);
+        start_application(exec_always->command, exec_always->no_startup_id);
+    }
+
+    /* Start i3bar processes for all configured bars */
+    Barconfig *barconfig;
+    TAILQ_FOREACH(barconfig, &barconfigs, configs) {
+        char *command = NULL;
+        sasprintf(&command, "i3bar --bar_id=%s --socket=\"%s\"",
+                  barconfig->id, current_socketpath);
+        LOG("Starting bar process: %s\n", command);
+        start_application(command, true);
+        free(command);
     }
 
+    /* Make sure to destroy the event loop to invoke the cleeanup callbacks
+     * when calling exit() */
+    atexit(i3_exit);
+
     ev_loop(main_loop, 0);
 }
index 3dcdbac87626e049c28a8b66af9236596336fbec..ee1b3d6c30bd77ad147014877655ba7af9afebd0 100644 (file)
@@ -4,11 +4,9 @@
  * i3 - an improved dynamic tiling window manager
  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * manage.c: Contains all functions for initially managing new windows
- *           (or existing ones on restart).
+ * manage.c: Initially managing new windows (or existing ones on restart).
  *
  */
-
 #include "all.h"
 
 /*
@@ -79,11 +77,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     xcb_get_geometry_reply_t *geom;
     xcb_get_window_attributes_reply_t *attr = NULL;
 
-    DLOG("---> looking at window 0x%08x\n", window);
-
     xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
                               utf8_title_cookie, title_cookie,
-                              class_cookie, leader_cookie, transient_cookie;
+                              class_cookie, leader_cookie, transient_cookie,
+                              role_cookie, startup_id_cookie;
 
 
     geomc = xcb_get_geometry(conn, d);
@@ -101,13 +98,11 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     }
 
     if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) {
-        DLOG("map_state unviewable\n");
         FREE_GEOMETRY();
         goto out;
     }
 
     /* Don’t manage clients with the override_redirect flag */
-    DLOG("override_redirect is %d\n", attr->override_redirect);
     if (attr->override_redirect) {
         FREE_GEOMETRY();
         goto out;
@@ -145,9 +140,11 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     transient_cookie = GET_PROPERTY(XCB_ATOM_WM_TRANSIENT_FOR, UINT32_MAX);
     title_cookie = GET_PROPERTY(XCB_ATOM_WM_NAME, 128);
     class_cookie = GET_PROPERTY(XCB_ATOM_WM_CLASS, 128);
+    role_cookie = GET_PROPERTY(A_WM_WINDOW_ROLE, 128);
+    startup_id_cookie = GET_PROPERTY(A__NET_STARTUP_ID, 512);
     /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */
 
-    DLOG("reparenting!\n");
+    DLOG("Managing window 0x%08x\n", window);
 
     i3Window *cwindow = scalloc(sizeof(i3Window));
     cwindow->id = window;
@@ -171,6 +168,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
     window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
     window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
+    window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL), true);
+
+    xcb_get_property_reply_t *startup_id_reply;
+    startup_id_reply = xcb_get_property_reply(conn, startup_id_cookie, NULL);
+    char *startup_ws = startup_workspace_for_window(cwindow, startup_id_reply);
+    DLOG("startup workspace = %s\n", startup_ws);
 
     /* check if the window needs WM_TAKE_FOCUS */
     cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS);
@@ -230,6 +233,15 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
                 else nc = tree_open_con(nc->parent, cwindow);
             }
         /* TODO: handle assignments with type == A_TO_OUTPUT */
+        } else if (startup_ws) {
+            /* If it’s not assigned, but was started on a specific workspace,
+             * we want to open it there */
+            DLOG("Using workspace on which this application was started (%s)\n", startup_ws);
+            nc = con_descend_tiling_focused(workspace_get(startup_ws, NULL));
+            DLOG("focused on ws %s: %p / %s\n", startup_ws, nc, nc->name);
+            if (nc->type == CT_WORKSPACE)
+                nc = tree_open_con(nc, cwindow);
+            else nc = tree_open_con(nc->parent, cwindow);
         } else {
             /* If not, insert it at the currently focused position */
             if (focused->type == CT_CON && con_accepts_window(focused)) {
@@ -253,7 +265,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     nc->border_width = geom->border_width;
 
     char *name;
-    asprintf(&name, "[i3 con] container around %p", cwindow);
+    sasprintf(&name, "[i3 con] container around %p", cwindow);
     x_set_name(nc, name);
     free(name);
 
@@ -265,9 +277,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     if (fs == NULL) {
         DLOG("Not in fullscreen mode, focusing\n");
         if (!cwindow->dock) {
-            /* Check that the workspace is visible. If the window was assigned
-             * to an invisible workspace, we should not steal focus. */
-            if (workspace_is_visible(ws)) {
+            /* Check that the workspace is visible and on the same output as
+             * the current focused container. If the window was assigned to an
+             * invisible workspace, we should not steal focus. */
+            Con *current_output = con_get_output(focused);
+            Con *target_output = con_get_output(ws);
+
+            if (workspace_is_visible(ws) && current_output == target_output) {
                 con_focus(nc);
             } else DLOG("workspace not visible, not focusing\n");
         } else DLOG("dock, not focusing\n");
@@ -325,7 +341,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
     if (want_floating) {
         DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
-        floating_enable(nc, false);
+        floating_enable(nc, true);
     }
 
     /* to avoid getting an UnmapNotify event due to reparenting, we temporarily
index 3a346117340a9c880d66a70f99fcd91bee242df4..c2773acc4cedeba9e37b2630b9ef132717be2848 100644 (file)
@@ -11,7 +11,6 @@
  * match_matches_window() to find the windows affected by this command.
  *
  */
-
 #include "all.h"
 
 /*
@@ -39,6 +38,7 @@ bool match_is_empty(Match *match) {
             match->application == NULL &&
             match->class == NULL &&
             match->instance == NULL &&
+            match->role == NULL &&
             match->id == XCB_NONE &&
             match->con_id == NULL &&
             match->dock == -1 &&
@@ -52,16 +52,20 @@ bool match_is_empty(Match *match) {
 void match_copy(Match *dest, Match *src) {
     memcpy(dest, src, sizeof(Match));
 
-#define STRDUP(field) do { \
+/* The DUPLICATE_REGEX macro creates a new regular expression from the
+ * ->pattern of the old one. It therefore does use a little more memory then
+ *  with a refcounting system, but it’s easier this way. */
+#define DUPLICATE_REGEX(field) do { \
     if (src->field != NULL) \
-        dest->field = sstrdup(src->field); \
+        dest->field = regex_new(src->field->pattern); \
 } while (0)
 
-    STRDUP(title);
-    STRDUP(mark);
-    STRDUP(application);
-    STRDUP(class);
-    STRDUP(instance);
+    DUPLICATE_REGEX(title);
+    DUPLICATE_REGEX(mark);
+    DUPLICATE_REGEX(application);
+    DUPLICATE_REGEX(class);
+    DUPLICATE_REGEX(instance);
+    DUPLICATE_REGEX(role);
 }
 
 /*
@@ -69,23 +73,22 @@ void match_copy(Match *dest, Match *src) {
  *
  */
 bool match_matches_window(Match *match, i3Window *window) {
-    LOG("checking window %d (%s)\n", window->id, window->class_class);
+    LOG("Checking window 0x%08x (class %s)\n", window->id, window->class_class);
 
-    /* TODO: pcre, full matching, … */
     if (match->class != NULL) {
-        if (window->class_class != NULL && strcasecmp(match->class, window->class_class) == 0) {
+        if (window->class_class != NULL &&
+            regex_matches(match->class, window->class_class)) {
             LOG("window class matches (%s)\n", window->class_class);
         } else {
-            LOG("window class does not match\n");
             return false;
         }
     }
 
     if (match->instance != NULL) {
-        if (window->class_instance != NULL && strcasecmp(match->instance, window->class_instance) == 0) {
+        if (window->class_instance != NULL &&
+            regex_matches(match->instance, window->class_instance)) {
             LOG("window instance matches (%s)\n", window->class_instance);
         } else {
-            LOG("window instance does not match\n");
             return false;
         }
     }
@@ -99,18 +102,25 @@ bool match_matches_window(Match *match, i3Window *window) {
         }
     }
 
-    /* TODO: pcre match */
     if (match->title != NULL) {
-        if (window->name_json != NULL && strcasecmp(match->title, window->name_json) == 0) {
+        if (window->name_json != NULL &&
+            regex_matches(match->title, window->name_json)) {
             LOG("title matches (%s)\n", window->name_json);
         } else {
-            LOG("title does not match\n");
+            return false;
+        }
+    }
+
+    if (match->role != NULL) {
+        if (window->role != NULL &&
+            regex_matches(match->role, window->role)) {
+            LOG("window_role matches (%s)\n", window->role);
+        } else {
             return false;
         }
     }
 
     if (match->dock != -1) {
-        LOG("match->dock = %d, window->dock = %d\n", match->dock, window->dock);
         if ((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) ||
          (window->dock == W_DOCK_BOTTOM && match->dock == M_DOCK_BOTTOM) ||
          ((window->dock == W_DOCK_TOP || window->dock == W_DOCK_BOTTOM) &&
@@ -132,3 +142,25 @@ bool match_matches_window(Match *match, i3Window *window) {
 
     return true;
 }
+
+/*
+ * Frees the given match. It must not be used afterwards!
+ *
+ */
+void match_free(Match *match) {
+    /* First step: free the regex fields / patterns */
+    regex_free(match->title);
+    regex_free(match->application);
+    regex_free(match->class);
+    regex_free(match->instance);
+    regex_free(match->mark);
+    regex_free(match->role);
+
+    /* Second step: free the regex helper struct itself */
+    FREE(match->title);
+    FREE(match->application);
+    FREE(match->class);
+    FREE(match->instance);
+    FREE(match->mark);
+    FREE(match->role);
+}
index 37fc0d34d7b3ac9082a88a08be9f4a65577837f3..00bcf2d9c4d3bd8e44be67845cbc08a2b954536f 100644 (file)
@@ -1,8 +1,14 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * move.c: Moving containers into some direction.
+ *
  */
-
 #include "all.h"
+
 #include "cmdparse.tab.h"
 
 typedef enum { BEFORE, AFTER } position_t;
index 684879240bad55366a261bdc231384d42ab0c059..a54cb6f35fb54ace23ff77faf2011083eb77343e 100644 (file)
@@ -1,7 +1,12 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * output.c: Output (monitor) related functions.
+ *
  */
-
 #include "all.h"
 
 /*
index 6da90070589d18b33e2aacbd79b7b8b71a3935ce..b8907b956e71698482b258407b772f8d7d622d39 100644 (file)
@@ -2,26 +2,21 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * For more information on RandR, please see the X.org RandR specification at
  * http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
  * (take your time to read it completely, it answers all questions).
  *
  */
-#include <time.h>
+#include "all.h"
 
+#include <time.h>
 #include <xcb/randr.h>
 
-#include "all.h"
-
 /* While a clean namespace is usually a pretty good thing, we really need
  * to use shorter names than the whole xcb_randr_* default names. */
 typedef xcb_randr_get_crtc_info_reply_t crtc_info;
-typedef xcb_randr_mode_info_t mode_info;
 typedef xcb_randr_get_screen_resources_current_reply_t resources_reply;
 
 /* Pointer to the result of the query for primary output */
@@ -248,7 +243,7 @@ void output_init_con(Output *output) {
     output->con = con;
 
     char *name;
-    asprintf(&name, "[i3 con] output %s", con->name);
+    sasprintf(&name, "[i3 con] output %s", con->name);
     x_set_name(con, name);
     FREE(name);
 
@@ -272,7 +267,7 @@ void output_init_con(Output *output) {
     FREE(topdock->name);
     topdock->name = sstrdup("topdock");
 
-    asprintf(&name, "[i3 con] top dockarea %s", con->name);
+    sasprintf(&name, "[i3 con] top dockarea %s", con->name);
     x_set_name(topdock, name);
     FREE(name);
     DLOG("attaching\n");
@@ -286,7 +281,7 @@ void output_init_con(Output *output) {
     FREE(content->name);
     content->name = sstrdup("content");
 
-    asprintf(&name, "[i3 con] content %s", con->name);
+    sasprintf(&name, "[i3 con] content %s", con->name);
     x_set_name(content, name);
     FREE(name);
     con_attach(content, con, false);
@@ -306,7 +301,7 @@ void output_init_con(Output *output) {
     FREE(bottomdock->name);
     bottomdock->name = sstrdup("bottomdock");
 
-    asprintf(&name, "[i3 con] bottom dockarea %s", con->name);
+    sasprintf(&name, "[i3 con] bottom dockarea %s", con->name);
     x_set_name(bottomdock, name);
     FREE(name);
     DLOG("attaching\n");
@@ -363,7 +358,7 @@ void init_ws_for_output(Output *output, Con *content) {
         if (visible && (previous = TAILQ_NEXT(workspace, focused))) {
             LOG("Switching to previously used workspace \"%s\" on output \"%s\"\n",
                 previous->name, workspace_out->name);
-            workspace_show(previous->name);
+            workspace_show(previous);
         }
 
         con_detach(workspace);
@@ -390,7 +385,7 @@ void init_ws_for_output(Output *output, Con *content) {
         if (!visible) {
             visible = TAILQ_FIRST(&(content->nodes_head));
             focused = content;
-            workspace_show(visible->name);
+            workspace_show(visible);
         }
         return;
     }
@@ -403,7 +398,7 @@ void init_ws_for_output(Output *output, Con *content) {
         LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
             assignment->name, assignment->output);
         focused = content;
-        workspace_show(assignment->name);
+        workspace_show_by_name(assignment->name);
         return;
     }
 
@@ -447,10 +442,12 @@ void init_ws_for_output(Output *output, Con *content) {
         if (!exists) {
             /* Set ->num to the number of the workspace, if the name actually
              * is a number or starts with a number */
-            long parsed_num = strtol(ws->name, NULL, 10);
+            char *endptr = NULL;
+            long parsed_num = strtol(ws->name, &endptr, 10);
             if (parsed_num == LONG_MIN ||
                 parsed_num == LONG_MAX ||
-                parsed_num <= 0)
+                parsed_num < 0 ||
+                endptr == ws->name)
                 ws->num = -1;
             else ws->num = parsed_num;
             LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
@@ -467,7 +464,7 @@ void init_ws_for_output(Output *output, Con *content) {
             c++;
 
             FREE(ws->name);
-            asprintf(&(ws->name), "%d", c);
+            sasprintf(&(ws->name), "%d", c);
 
             current = NULL;
             TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
@@ -480,7 +477,7 @@ void init_ws_for_output(Output *output, Con *content) {
     }
     con_attach(ws, content, false);
 
-    asprintf(&name, "[i3 con] workspace %s", ws->name);
+    sasprintf(&name, "[i3 con] workspace %s", ws->name);
     x_set_name(ws, name);
     free(name);
 
@@ -565,7 +562,7 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
     new->id = id;
     new->primary = (primary && primary->output == id);
     FREE(new->name);
-    asprintf(&new->name, "%.*s",
+    sasprintf(&new->name, "%.*s",
             xcb_randr_get_output_info_name_length(output),
             xcb_randr_get_output_info_name(output));
 
@@ -746,7 +743,7 @@ void randr_query_outputs() {
                 Con *next = NULL;
                 if (TAILQ_FIRST(&(croot->focus_head)) == output->con) {
                     DLOG("This output (%p) was focused! Getting next\n", output->con);
-                    next = con_next_focused(output->con);
+                    next = focused;
                     DLOG("next = %p\n", next);
                 }
 
@@ -766,6 +763,7 @@ void randr_query_outputs() {
                 if (next) {
                     DLOG("now focusing next = %p\n", next);
                     con_focus(next);
+                    workspace_show(con_get_workspace(next));
                 }
 
                 /* 3: move the dock clients to the first output */
diff --git a/src/regex.c b/src/regex.c
new file mode 100644 (file)
index 0000000..a0b51f6
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * regex.c: Interface to libPCRE (perl compatible regular expressions).
+ *
+ */
+#include "all.h"
+
+/*
+ * Creates a new 'regex' struct containing the given pattern and a PCRE
+ * compiled regular expression. Also, calls pcre_study because this regex will
+ * most likely be used often (like for every new window and on every relevant
+ * property change of existing windows).
+ *
+ * Returns NULL if the pattern could not be compiled into a regular expression
+ * (and ELOGs an appropriate error message).
+ *
+ */
+struct regex *regex_new(const char *pattern) {
+    const char *error;
+    int errorcode, offset;
+
+    struct regex *re = scalloc(sizeof(struct regex));
+    re->pattern = sstrdup(pattern);
+    int options = PCRE_UTF8;
+#ifdef PCRE_HAS_UCP
+    /* We use PCRE_UCP so that \B, \b, \D, \d, \S, \s, \W, \w and some POSIX
+     * character classes play nicely with Unicode */
+    options |= PCRE_UCP;
+#endif
+    while (!(re->regex = pcre_compile2(pattern, options, &errorcode, &error, &offset, NULL))) {
+        /* If the error is that PCRE was not compiled with UTF-8 support we
+         * disable it and try again */
+        if (errorcode == 32) {
+            options &= ~PCRE_UTF8;
+            continue;
+        }
+        ELOG("PCRE regular expression compilation failed at %d: %s\n",
+             offset, error);
+        return NULL;
+    }
+    re->extra = pcre_study(re->regex, 0, &error);
+    /* If an error happened, we print the error message, but continue.
+     * Studying the regular expression leads to faster matching, but it’s not
+     * absolutely necessary. */
+    if (error) {
+        ELOG("PCRE regular expression studying failed: %s\n", error);
+    }
+    return re;
+}
+
+/*
+ * Frees the given regular expression. It must not be used afterwards!
+ *
+ */
+void regex_free(struct regex *regex) {
+    if (!regex)
+        return;
+    FREE(regex->pattern);
+    FREE(regex->regex);
+    FREE(regex->extra);
+}
+
+/*
+ * Checks if the given regular expression matches the given input and returns
+ * true if it does. In either case, it logs the outcome using LOG(), so it will
+ * be visible without any debug loglevel.
+ *
+ */
+bool regex_matches(struct regex *regex, const char *input) {
+    int rc;
+
+    /* We use strlen() because pcre_exec() expects the length of the input
+     * string in bytes */
+    if ((rc = pcre_exec(regex->regex, regex->extra, input, strlen(input), 0, 0, NULL, 0)) == 0) {
+        LOG("Regular expression \"%s\" matches \"%s\"\n",
+            regex->pattern, input);
+        return true;
+    }
+
+    if (rc == PCRE_ERROR_NOMATCH) {
+        LOG("Regular expression \"%s\" does not match \"%s\"\n",
+            regex->pattern, input);
+        return false;
+    }
+
+    ELOG("PCRE error %d while trying to use regular expression \"%s\" on input \"%s\", see pcreapi(3)\n",
+         rc, regex->pattern, input);
+    return false;
+}
index a7a9be56ed90baf077dad26e1d43ddaeae58925e..d85d16a74878f151716c3e148a5aed806f2eef9a 100644 (file)
@@ -1,7 +1,13 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * render.c: Renders (determines position/sizes) the layout tree, updating the
+ *           various rects. Needs to be pushed to X11 (see x.c) to be visible.
+ *
  */
-
 #include "all.h"
 
 /* change this to 'true' if you want to have additional borders around every
@@ -19,7 +25,6 @@ static void render_l_output(Con *con) {
     int x = con->rect.x;
     int y = con->rect.y;
     int height = con->rect.height;
-    DLOG("Available height: %d\n", height);
 
     /* Find the content container and ensure that there is exactly one. Also
      * check for any non-CT_DOCKAREA clients. */
@@ -45,7 +50,6 @@ static void render_l_output(Con *con) {
     Con *ws = con_get_fullscreen_con(content, CF_OUTPUT);
     Con *fullscreen = con_get_fullscreen_con(ws, CF_OUTPUT);
     if (fullscreen) {
-        DLOG("got fs node: %p\n", fullscreen);
         fullscreen->rect = con->rect;
         x_raise_con(fullscreen);
         render_con(fullscreen, true);
@@ -61,13 +65,10 @@ static void render_l_output(Con *con) {
         child->rect.height = 0;
         TAILQ_FOREACH(dockchild, &(child->nodes_head), nodes)
             child->rect.height += dockchild->geometry.height;
-        DLOG("This dockarea's height: %d\n", child->rect.height);
 
         height -= child->rect.height;
     }
 
-    DLOG("Remaining: %d\n", height);
-
     /* Second pass: Set the widths/heights */
     TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
         if (child->type == CT_CON) {
@@ -90,7 +91,6 @@ static void render_l_output(Con *con) {
 
         DLOG("child at (%d, %d) with (%d x %d)\n",
                 child->rect.x, child->rect.y, child->rect.width, child->rect.height);
-        DLOG("x now %d, y now %d\n", x, y);
         x_raise_con(child);
         render_con(child, false);
     }
@@ -105,10 +105,10 @@ static void render_l_output(Con *con) {
  *
  */
 void render_con(Con *con, bool render_fullscreen) {
-    DLOG("currently rendering node %p / %s / layout %d\n",
-            con, con->name, con->layout);
     int children = con_num_children(con);
-    DLOG("children: %d, orientation = %d\n", children, con->orientation);
+    DLOG("Rendering %snode %p / %s / layout %d / children %d / orient %d\n",
+         (render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout,
+         children, con->orientation);
 
     /* Copy container rect, subtract container border */
     /* This is the actually usable space inside this container for clients */
@@ -139,16 +139,13 @@ void render_con(Con *con, bool render_fullscreen) {
         if (!render_fullscreen)
             *inset = rect_add(*inset, con_border_style_rect(con));
 
-        DLOG("Starting with inset = (%d, %d) %d x %d\n", inset->x, inset->y, inset->width, inset->height);
         /* Obey x11 border */
-        DLOG("X11 border: %d\n", con->border_width);
         inset->width -= (2 * con->border_width);
         inset->height -= (2 * con->border_width);
 
         /* Obey the aspect ratio, if any */
         if (con->proportional_height != 0 &&
             con->proportional_width != 0) {
-            DLOG("proportional height = %d, width = %d\n", con->proportional_height, con->proportional_width);
             double new_height = inset->height + 1;
             int new_width = inset->width;
 
@@ -164,7 +161,6 @@ void render_con(Con *con, bool render_fullscreen) {
 
             inset->height = new_height;
             inset->width = new_width;
-            DLOG("new_height = %f, new_width = %d\n", new_height, new_width);
         }
 
         if (con->height_increment > 1) {
@@ -190,7 +186,6 @@ void render_con(Con *con, bool render_fullscreen) {
         fullscreen = con_get_fullscreen_con(con, (con->type == CT_ROOT ? CF_GLOBAL : CF_OUTPUT));
     }
     if (fullscreen) {
-        DLOG("got fs node: %p\n", fullscreen);
         fullscreen->rect = rect;
         x_raise_con(fullscreen);
         render_con(fullscreen, true);
@@ -226,11 +221,29 @@ void render_con(Con *con, bool render_fullscreen) {
     if (con->layout == L_OUTPUT) {
         render_l_output(con);
     } else if (con->type == CT_ROOT) {
-        DLOG("Root node, rendering outputs\n");
-        Con *child;
-        TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
-            render_con(child, false);
+        Con *output;
+        TAILQ_FOREACH(output, &(con->nodes_head), nodes) {
+            render_con(output, false);
+        }
+
+        /* We need to render floating windows after rendering all outputs’
+         * tiling windows because they need to be on top of *every* output at
+         * all times. This is important when the user places floating
+         * windows/containers so that they overlap on another output. */
+        DLOG("Rendering floating windows:\n");
+        TAILQ_FOREACH(output, &(con->nodes_head), nodes) {
+            /* Get the active workspace of that output */
+            Con *content = output_get_content(output);
+            Con *workspace = TAILQ_FIRST(&(content->focus_head));
+
+            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);
+                x_raise_con(child);
+                render_con(child, false);
+            }
         }
+
     } else {
 
         /* FIXME: refactor this into separate functions: */
@@ -256,7 +269,6 @@ void render_con(Con *con, bool render_fullscreen) {
 
             /* first we have the decoration, if this is a leaf node */
             if (con_is_leaf(child) && child->border_style == BS_NORMAL) {
-                DLOG("that child is a leaf node, subtracting deco\n");
                 /* TODO: make a function for relative coords? */
                 child->deco_rect.x = child->rect.x - con->rect.x;
                 child->deco_rect.y = child->rect.y - con->rect.y;
@@ -271,7 +283,6 @@ void render_con(Con *con, bool render_fullscreen) {
 
         /* stacked layout */
         else if (con->layout == L_STACKED) {
-            DLOG("stacked con\n");
             child->rect.x = x;
             child->rect.y = y;
             child->rect.width = rect.width;
@@ -290,7 +301,6 @@ void render_con(Con *con, bool render_fullscreen) {
 
         /* tabbed layout */
         else if (con->layout == L_TABBED) {
-            DLOG("tabbed con\n");
             child->rect.x = x;
             child->rect.y = y;
             child->rect.width = rect.width;
@@ -309,7 +319,6 @@ void render_con(Con *con, bool render_fullscreen) {
 
         /* dockarea layout */
         else if (con->layout == L_DOCKAREA) {
-            DLOG("dockarea con\n");
             child->rect.x = x;
             child->rect.y = y;
             child->rect.width = rect.width;
@@ -324,7 +333,6 @@ void render_con(Con *con, bool render_fullscreen) {
 
         DLOG("child at (%d, %d) with (%d x %d)\n",
                 child->rect.x, child->rect.y, child->rect.width, child->rect.height);
-        DLOG("x now %d, y now %d\n", x, y);
         x_raise_con(child);
         render_con(child, false);
         i++;
@@ -332,12 +340,9 @@ void render_con(Con *con, bool render_fullscreen) {
 
     /* in a stacking or tabbed container, we ensure the focused client is raised */
     if (con->layout == L_STACKED || con->layout == L_TABBED) {
-        DLOG("stacked/tabbed, raising focused reverse\n");
         TAILQ_FOREACH_REVERSE(child, &(con->focus_head), focus_head, focused)
             x_raise_con(child);
-        DLOG("done\n");
         if ((child = TAILQ_FIRST(&(con->focus_head)))) {
-            DLOG("con %p is stacking, raising %p\n", con, child);
             /* By rendering the stacked container again, we handle the case
              * that we have a non-leaf-container inside the stack. In that
              * case, the children of the non-leaf-container need to be raised
@@ -353,14 +358,4 @@ void render_con(Con *con, bool render_fullscreen) {
             x_raise_con(con);
     }
     }
-
-    Con *child;
-    TAILQ_FOREACH(child, &(con->floating_head), floating_windows) {
-        DLOG("render floating:\n");
-        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);
-    }
-
-    DLOG("-- level up\n");
 }
index 9c465e183ea812700ab46236ad25306a49aab46c..a4e4dfa0b83066dc461f638bce5bf62741ed2354 100644 (file)
@@ -1,5 +1,11 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * resize.c: Interactive resizing.
+ *
  */
 #include "all.h"
 
@@ -20,7 +26,7 @@ struct callback_params {
 };
 
 DRAGGING_CB(resize_callback) {
-    struct callback_params *params = extra;
+    const struct callback_params *params = extra;
     Con *output = params->output;
     DLOG("new x = %d, y = %d\n", new_x, new_y);
     if (params->orientation == HORIZ) {
@@ -43,7 +49,7 @@ DRAGGING_CB(resize_callback) {
     xcb_flush(conn);
 }
 
-int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, xcb_button_press_event_t *event) {
+int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
     DLOG("resize handler\n");
 
     uint32_t new_position;
@@ -92,7 +98,7 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
 
     xcb_flush(conn);
 
-    struct callback_params params = { orientation, output, helpwin, &new_position };
+    const struct callback_params params = { orientation, output, helpwin, &new_position };
 
     drag_pointer(NULL, event, grabwin, BORDER_TOP, resize_callback, &params);
 
diff --git a/src/sd-daemon.c b/src/sd-daemon.c
new file mode 100644 (file)
index 0000000..6d1eebf
--- /dev/null
@@ -0,0 +1,436 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  Copyright 2010 Lennart Poettering
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+***/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/fcntl.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stddef.h>
+
+#include "sd-daemon.h"
+
+int sd_listen_fds(int unset_environment) {
+
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+        return 0;
+#else
+        int r, fd;
+        const char *e;
+        char *p = NULL;
+        unsigned long l;
+
+        if (!(e = getenv("LISTEN_PID"))) {
+                r = 0;
+                goto finish;
+        }
+
+        errno = 0;
+        l = strtoul(e, &p, 10);
+
+        if (errno != 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (!p || *p || l <= 0) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        /* Is this for us? */
+        if (getpid() != (pid_t) l) {
+                r = 0;
+                goto finish;
+        }
+
+        if (!(e = getenv("LISTEN_FDS"))) {
+                r = 0;
+                goto finish;
+        }
+
+        errno = 0;
+        l = strtoul(e, &p, 10);
+
+        if (errno != 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (!p || *p) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) {
+                int flags;
+
+                if ((flags = fcntl(fd, F_GETFD)) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (flags & FD_CLOEXEC)
+                        continue;
+
+                if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
+        r = (int) l;
+
+finish:
+        if (unset_environment) {
+                unsetenv("LISTEN_PID");
+                unsetenv("LISTEN_FDS");
+        }
+
+        return r;
+#endif
+}
+
+int sd_is_fifo(int fd, const char *path) {
+        struct stat st_fd;
+
+        if (fd < 0)
+                return -EINVAL;
+
+        memset(&st_fd, 0, sizeof(st_fd));
+        if (fstat(fd, &st_fd) < 0)
+                return -errno;
+
+        if (!S_ISFIFO(st_fd.st_mode))
+                return 0;
+
+        if (path) {
+                struct stat st_path;
+
+                memset(&st_path, 0, sizeof(st_path));
+                if (stat(path, &st_path) < 0) {
+
+                        if (errno == ENOENT || errno == ENOTDIR)
+                                return 0;
+
+                        return -errno;
+                }
+
+                return
+                        st_path.st_dev == st_fd.st_dev &&
+                        st_path.st_ino == st_fd.st_ino;
+        }
+
+        return 1;
+}
+
+static int sd_is_socket_internal(int fd, int type, int listening) {
+        struct stat st_fd;
+
+        if (fd < 0 || type < 0)
+                return -EINVAL;
+
+        if (fstat(fd, &st_fd) < 0)
+                return -errno;
+
+        if (!S_ISSOCK(st_fd.st_mode))
+                return 0;
+
+        if (type != 0) {
+                int other_type = 0;
+                socklen_t l = sizeof(other_type);
+
+                if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0)
+                        return -errno;
+
+                if (l != sizeof(other_type))
+                        return -EINVAL;
+
+                if (other_type != type)
+                        return 0;
+        }
+
+        if (listening >= 0) {
+                int accepting = 0;
+                socklen_t l = sizeof(accepting);
+
+                if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0)
+                        return -errno;
+
+                if (l != sizeof(accepting))
+                        return -EINVAL;
+
+                if (!accepting != !listening)
+                        return 0;
+        }
+
+        return 1;
+}
+
+union sockaddr_union {
+        struct sockaddr sa;
+        struct sockaddr_in in4;
+        struct sockaddr_in6 in6;
+        struct sockaddr_un un;
+        struct sockaddr_storage storage;
+};
+
+int sd_is_socket(int fd, int family, int type, int listening) {
+        int r;
+
+        if (family < 0)
+                return -EINVAL;
+
+        if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+                return r;
+
+        if (family > 0) {
+                union sockaddr_union sockaddr;
+                socklen_t l;
+
+                memset(&sockaddr, 0, sizeof(sockaddr));
+                l = sizeof(sockaddr);
+
+                if (getsockname(fd, &sockaddr.sa, &l) < 0)
+                        return -errno;
+
+                if (l < sizeof(sa_family_t))
+                        return -EINVAL;
+
+                return sockaddr.sa.sa_family == family;
+        }
+
+        return 1;
+}
+
+int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) {
+        union sockaddr_union sockaddr;
+        socklen_t l;
+        int r;
+
+        if (family != 0 && family != AF_INET && family != AF_INET6)
+                return -EINVAL;
+
+        if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+                return r;
+
+        memset(&sockaddr, 0, sizeof(sockaddr));
+        l = sizeof(sockaddr);
+
+        if (getsockname(fd, &sockaddr.sa, &l) < 0)
+                return -errno;
+
+        if (l < sizeof(sa_family_t))
+                return -EINVAL;
+
+        if (sockaddr.sa.sa_family != AF_INET &&
+            sockaddr.sa.sa_family != AF_INET6)
+                return 0;
+
+        if (family > 0)
+                if (sockaddr.sa.sa_family != family)
+                        return 0;
+
+        if (port > 0) {
+                if (sockaddr.sa.sa_family == AF_INET) {
+                        if (l < sizeof(struct sockaddr_in))
+                                return -EINVAL;
+
+                        return htons(port) == sockaddr.in4.sin_port;
+                } else {
+                        if (l < sizeof(struct sockaddr_in6))
+                                return -EINVAL;
+
+                        return htons(port) == sockaddr.in6.sin6_port;
+                }
+        }
+
+        return 1;
+}
+
+int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) {
+        union sockaddr_union sockaddr;
+        socklen_t l;
+        int r;
+
+        if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+                return r;
+
+        memset(&sockaddr, 0, sizeof(sockaddr));
+        l = sizeof(sockaddr);
+
+        if (getsockname(fd, &sockaddr.sa, &l) < 0)
+                return -errno;
+
+        if (l < sizeof(sa_family_t))
+                return -EINVAL;
+
+        if (sockaddr.sa.sa_family != AF_UNIX)
+                return 0;
+
+        if (path) {
+                if (length <= 0)
+                        length = strlen(path);
+
+                if (length <= 0)
+                        /* Unnamed socket */
+                        return l == offsetof(struct sockaddr_un, sun_path);
+
+                if (path[0])
+                        /* Normal path socket */
+                        return
+                                (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) &&
+                                memcmp(path, sockaddr.un.sun_path, length+1) == 0;
+                else
+                        /* Abstract namespace socket */
+                        return
+                                (l == offsetof(struct sockaddr_un, sun_path) + length) &&
+                                memcmp(path, sockaddr.un.sun_path, length) == 0;
+        }
+
+        return 1;
+}
+
+int sd_notify(int unset_environment, const char *state) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__) || !defined(SOCK_CLOEXEC)
+        return 0;
+#else
+        int fd = -1, r;
+        struct msghdr msghdr;
+        struct iovec iovec;
+        union sockaddr_union sockaddr;
+        const char *e;
+
+        if (!state) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        if (!(e = getenv("NOTIFY_SOCKET")))
+                return 0;
+
+        /* Must be an abstract socket, or an absolute path */
+        if ((e[0] != '@' && e[0] != '/') || e[1] == 0) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        memset(&sockaddr, 0, sizeof(sockaddr));
+        sockaddr.sa.sa_family = AF_UNIX;
+        strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path));
+
+        if (sockaddr.un.sun_path[0] == '@')
+                sockaddr.un.sun_path[0] = 0;
+
+        memset(&iovec, 0, sizeof(iovec));
+        iovec.iov_base = (char*) state;
+        iovec.iov_len = strlen(state);
+
+        memset(&msghdr, 0, sizeof(msghdr));
+        msghdr.msg_name = &sockaddr;
+        msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e);
+
+        if (msghdr.msg_namelen > sizeof(struct sockaddr_un))
+                msghdr.msg_namelen = sizeof(struct sockaddr_un);
+
+        msghdr.msg_iov = &iovec;
+        msghdr.msg_iovlen = 1;
+
+        if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        r = 1;
+
+finish:
+        if (unset_environment)
+                unsetenv("NOTIFY_SOCKET");
+
+        if (fd >= 0)
+                close(fd);
+
+        return r;
+#endif
+}
+
+int sd_notifyf(int unset_environment, const char *format, ...) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+        return 0;
+#else
+        va_list ap;
+        char *p = NULL;
+        int r;
+
+        va_start(ap, format);
+        r = vasprintf(&p, format, ap);
+        va_end(ap);
+
+        if (r < 0 || !p)
+                return -ENOMEM;
+
+        r = sd_notify(unset_environment, p);
+        free(p);
+
+        return r;
+#endif
+}
+
+int sd_booted(void) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+        return 0;
+#else
+
+        struct stat a, b;
+
+        /* We simply test whether the systemd cgroup hierarchy is
+         * mounted */
+
+        if (lstat("/sys/fs/cgroup", &a) < 0)
+                return 0;
+
+        if (lstat("/sys/fs/cgroup/systemd", &b) < 0)
+                return 0;
+
+        return a.st_dev != b.st_dev;
+#endif
+}
index 1d3e4ab719979832be0066b2314509e2aa3ec58b..a029422bf778645f1396773b49122ad585f132ad 100644 (file)
@@ -2,32 +2,23 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  * © 2009-2010 Jan-Erik Rediger
  *
- * See file LICENSE for license information.
- *
- * sighandler.c: contains all functions for signal handling
+ * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
+ *               to restart inplace).
  *
  */
+#include "all.h"
+
 #include <ev.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
 #include <iconv.h>
 #include <signal.h>
 
-#include <xcb/xcb.h>
-#include <xcb/xcb_aux.h>
 #include <xcb/xcb_event.h>
-#include <xcb/xcb_keysyms.h>
 
 #include <X11/keysym.h>
 
-#include "all.h"
-
 static xcb_gcontext_t pixmap_gc;
 static xcb_pixmap_t pixmap;
 static int raised_signal;
@@ -50,13 +41,13 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei
     /* re-draw the background */
     xcb_rectangle_t border = { 0, 0, width, height},
                     inner = { 2, 2, width - 4, height - 4};
-    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#FF0000"));
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
-    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#000000"));
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#000000") });
     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
 
     /* restore font color */
-    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#FFFFFF"));
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
 
     for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) {
         int text_len = strlen(crash_text[i]);
@@ -179,7 +170,7 @@ void handle_signal(int sig, siginfo_t *info, void *data) {
         xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
 
         /* Create graphics context */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, config.font.id);
+        xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ config.font.id });
 
         /* Grab the keyboard to get all input */
         xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
diff --git a/src/startup.c b/src/startup.c
new file mode 100644 (file)
index 0000000..86e66ea
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * startup.c: Startup notification code. Ensures a startup notification context
+ *            is setup when launching applications. We store the current
+ *            workspace to open windows in that startup notification context on
+ *            the appropriate workspace.
+ *
+ */
+#include "all.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-launcher.h>
+
+static TAILQ_HEAD(startup_sequence_head, Startup_Sequence) startup_sequences =
+    TAILQ_HEAD_INITIALIZER(startup_sequences);
+
+/*
+ * After 60 seconds, a timeout will be triggered for each startup sequence.
+ *
+ * The timeout will just trigger completion of the sequence, so the normal
+ * completion process takes place (startup_monitor_event will free it).
+ *
+ */
+static void startup_timeout(EV_P_ ev_timer *w, int revents) {
+    const char *id = sn_launcher_context_get_startup_id(w->data);
+    DLOG("Timeout for startup sequence %s\n", id);
+
+    struct Startup_Sequence *current, *sequence = NULL;
+    TAILQ_FOREACH(current, &startup_sequences, sequences) {
+        if (strcmp(current->id, id) != 0)
+            continue;
+
+        sequence = current;
+        break;
+    }
+
+    /* Unref the context (for the timeout itself, see start_application) */
+    sn_launcher_context_unref(w->data);
+
+    if (!sequence) {
+        DLOG("Sequence already deleted, nevermind.\n");
+        return;
+    }
+
+    /* Complete the startup sequence, will trigger its deletion. */
+    sn_launcher_context_complete(w->data);
+    free(w);
+}
+
+/*
+ * 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),
+ * the application is reparented to init (process-id 1), which correctly handles
+ * childs, so we don’t have to do it :-).
+ *
+ * The shell is determined by looking for the SHELL environment variable. If it
+ * does not exist, /bin/sh is used.
+ *
+ * The no_startup_id flag determines whether a startup notification context
+ * (and ID) should be created, which is the default and encouraged behavior.
+ *
+ */
+void start_application(const char *command, bool no_startup_id) {
+    SnLauncherContext *context;
+
+    if (!no_startup_id) {
+        /* Create a startup notification context to monitor the progress of this
+         * startup. */
+        context = sn_launcher_context_new(sndisplay, conn_screen);
+        sn_launcher_context_set_name(context, "i3");
+        sn_launcher_context_set_description(context, "exec command in i3");
+        /* Chop off everything starting from the first space (if there are any
+         * spaces in the command), since we don’t want the parameters. */
+        char *first_word = sstrdup(command);
+        char *space = strchr(first_word, ' ');
+        if (space)
+            *space = '\0';
+        sn_launcher_context_initiate(context, "i3", first_word, last_timestamp);
+        free(first_word);
+
+        /* Trigger a timeout after 60 seconds */
+        struct ev_timer *timeout = scalloc(sizeof(struct ev_timer));
+        ev_timer_init(timeout, startup_timeout, 60.0, 0.);
+        timeout->data = context;
+        ev_timer_start(main_loop, timeout);
+
+        LOG("startup id = %s\n", sn_launcher_context_get_startup_id(context));
+
+        /* Save the ID and current workspace in our internal list of startup
+         * sequences */
+        Con *ws = con_get_workspace(focused);
+        struct Startup_Sequence *sequence = scalloc(sizeof(struct Startup_Sequence));
+        sequence->id = sstrdup(sn_launcher_context_get_startup_id(context));
+        sequence->workspace = sstrdup(ws->name);
+        sequence->context = context;
+        TAILQ_INSERT_TAIL(&startup_sequences, sequence, sequences);
+
+        /* Increase the refcount once (it starts with 1, so it will be 2 now) for
+         * the timeout. Even if the sequence gets completed, the timeout still
+         * needs the context (but will unref it then) */
+        sn_launcher_context_ref(context);
+    }
+
+    LOG("executing: %s\n", command);
+    if (fork() == 0) {
+        /* Child process */
+        setsid();
+        setrlimit(RLIMIT_CORE, &original_rlimit_core);
+        if (fork() == 0) {
+            /* Setup the environment variable(s) */
+            if (!no_startup_id)
+                sn_launcher_context_setup_child_process(context);
+
+            /* Stores the path of the shell */
+            static const char *shell = NULL;
+
+            if (shell == NULL)
+                if ((shell = getenv("SHELL")) == NULL)
+                    shell = "/bin/sh";
+
+            /* This is the child */
+            execl(shell, shell, "-c", command, (void*)NULL);
+            /* not reached */
+        }
+        _exit(0);
+    }
+    wait(0);
+
+    if (!no_startup_id) {
+        /* Change the pointer of the root window to indicate progress */
+        if (xcursor_supported)
+            xcursor_set_root_cursor(XCURSOR_CURSOR_WATCH);
+        else xcb_set_root_cursor(XCURSOR_CURSOR_WATCH);
+    }
+}
+
+/*
+ * Called by libstartup-notification when something happens
+ *
+ */
+void startup_monitor_event(SnMonitorEvent *event, void *userdata) {
+    SnStartupSequence *snsequence;
+
+    snsequence = sn_monitor_event_get_startup_sequence(event);
+
+    /* Get the corresponding internal startup sequence */
+    const char *id = sn_startup_sequence_get_id(snsequence);
+    struct Startup_Sequence *current, *sequence = NULL;
+    TAILQ_FOREACH(current, &startup_sequences, sequences) {
+        if (strcmp(current->id, id) != 0)
+            continue;
+
+        sequence = current;
+        break;
+    }
+
+    if (!sequence) {
+        DLOG("Got event for startup sequence that we did not initiate (ID = %s). Ignoring.\n", id);
+        return;
+    }
+
+    switch (sn_monitor_event_get_type(event)) {
+        case SN_MONITOR_EVENT_COMPLETED:
+            DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(snsequence));
+
+            /* Unref the context, will be free()d */
+            sn_launcher_context_unref(sequence->context);
+
+            /* Delete our internal sequence */
+            TAILQ_REMOVE(&startup_sequences, sequence, sequences);
+
+            if (TAILQ_EMPTY(&startup_sequences)) {
+                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)
+                    xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER);
+                else xcb_set_root_cursor(XCURSOR_CURSOR_POINTER);
+            }
+            break;
+        default:
+            /* ignore */
+            break;
+    }
+}
+
+/*
+ * 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) {
+    /* 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");
+        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);
+        DLOG("Checking leader window 0x%08x\n", cwindow->leader);
+        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");
+            FREE(startup_id_reply);
+            return NULL;
+        }
+    }
+
+    char *startup_id;
+    if (asprintf(&startup_id, "%.*s", xcb_get_property_value_length(startup_id_reply),
+                 (char*)xcb_get_property_value(startup_id_reply)) == -1) {
+        perror("asprintf()");
+        DLOG("Could not get _NET_STARTUP_ID\n");
+        free(startup_id_reply);
+        return NULL;
+    }
+
+    struct Startup_Sequence *current, *sequence = NULL;
+    TAILQ_FOREACH(current, &startup_sequences, sequences) {
+        if (strcmp(current->id, startup_id) != 0)
+            continue;
+
+        sequence = current;
+        break;
+    }
+
+    free(startup_id);
+    free(startup_id_reply);
+
+    if (!sequence) {
+        DLOG("WARNING: This sequence (ID %s) was not found\n", startup_id);
+        return NULL;
+    }
+
+    return sequence->workspace;
+}
index 76530d604bb1f3546abe89695ab141cbf5abfa6e..53993b998164362ad4a64911d42c58cfbed415f0 100644 (file)
@@ -1,7 +1,12 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * tree.c: Everything that primarily modifies the layout tree data structure.
+ *
  */
-
 #include "all.h"
 
 struct Con *croot;
@@ -420,7 +425,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
         if (!workspace)
             return false;
 
-        workspace_show(workspace->name);
+        workspace_show(workspace);
         Con *focus = con_descend_direction(workspace, direction);
         if (focus) {
             con_focus(focus);
@@ -429,12 +434,35 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
         return true;
     }
 
+    Con *parent = con->parent;
+
     if (con->type == CT_FLOATING_CON) {
-        /* TODO: implement focus for floating windows */
-        return false;
-    }
+        /* left/right focuses the previous/next floating container */
+        if (orientation == HORIZ) {
+            Con *next;
+            if (way == 'n')
+                next = TAILQ_NEXT(con, floating_windows);
+            else next = TAILQ_PREV(con, floating_head, floating_windows);
+
+            /* If there is no next/previous container, wrap */
+            if (!next) {
+                if (way == 'n')
+                    next = TAILQ_FIRST(&(parent->floating_head));
+                else next = TAILQ_LAST(&(parent->floating_head), floating_head);
+            }
 
-    Con *parent = con->parent;
+            /* Still no next/previous container? bail out */
+            if (!next)
+                return false;
+
+            con_focus(con_descend_focused(next));
+            return true;
+        } else {
+            /* up/down cycles through the Z-index */
+            /* TODO: implement cycling through the z-index */
+            return false;
+        }
+    }
 
     /* If the orientation does not match or there is no other con to focus, we
      * need to go higher in the hierarchy */
index 2d6c3e147138d147de2f80dacb5bc1d74b950a67..72146bff7aa7c1769a1d1d341255a738f1dfc62f 100644 (file)
@@ -2,14 +2,14 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * util.c: Utility functions, which can be useful everywhere.
+ * util.c: Utility functions, which can be useful everywhere within i3 (see
+ *         also libi3).
  *
  */
+#include "all.h"
+
 #include <sys/wait.h>
 #include <stdarg.h>
 #include <iconv.h>
@@ -21,7 +21,8 @@
 #include <yajl/yajl_version.h>
 #include <libgen.h>
 
-#include "all.h"
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-launcher.h>
 
 static iconv_t conversion_descriptor = 0;
 
@@ -58,68 +59,6 @@ bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
     return ((*destination = new_value) != old_value);
 }
 
-/*
- * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of
- * the called functions returns NULL, meaning that there is no more memory available
- *
- */
-void *smalloc(size_t size) {
-    void *result = malloc(size);
-    exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size);
-    return result;
-}
-
-void *scalloc(size_t size) {
-    void *result = calloc(size, 1);
-    exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size);
-    return result;
-}
-
-void *srealloc(void *ptr, size_t size) {
-    void *result = realloc(ptr, size);
-    if (result == NULL && size > 0)
-        die("Error: out memory (realloc(%zd))\n", size);
-    return result;
-}
-
-char *sstrdup(const char *str) {
-    char *result = strdup(str);
-    exit_if_null(result, "Error: out of memory (strdup())\n");
-    return result;
-}
-
-/*
- * 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),
- * the application is reparented to init (process-id 1), which correctly handles
- * childs, so we don’t have to do it :-).
- *
- * The shell is determined by looking for the SHELL environment variable. If it
- * does not exist, /bin/sh is used.
- *
- */
-void start_application(const char *command) {
-    LOG("executing: %s\n", command);
-    if (fork() == 0) {
-        /* Child process */
-        setsid();
-        if (fork() == 0) {
-            /* Stores the path of the shell */
-            static const char *shell = NULL;
-
-            if (shell == NULL)
-                if ((shell = getenv("SHELL")) == NULL)
-                    shell = "/bin/sh";
-
-            /* This is the child */
-            execl(shell, shell, "-c", command, (void*)NULL);
-            /* not reached */
-        }
-        exit(0);
-    }
-    wait(0);
-}
-
 /*
  * exec()s an i3 utility, for example the config file migration script or
  * i3-nagbar. This function first searches $PATH for the given utility named,
@@ -146,7 +85,7 @@ void exec_i3_utility(char *name, char *argv[]) {
      * argv[0]’s dirname */
     char *pathbuf = strdup(start_argv[0]);
     char *dir = dirname(pathbuf);
-    asprintf(&migratepath, "%s/%s", dir, name);
+    sasprintf(&migratepath, "%s/%s", dir, name);
     argv[0] = migratepath;
     execvp(migratepath, argv);
 
@@ -158,7 +97,7 @@ void exec_i3_utility(char *name, char *argv[]) {
         exit(1);
     }
     dir = dirname(buffer);
-    asprintf(&migratepath, "%s/%s", dir, name);
+    sasprintf(&migratepath, "%s/%s", dir, name);
     argv[0] = migratepath;
     execvp(migratepath, argv);
 #endif
@@ -214,6 +153,7 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
     int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
     if (rc == (size_t)-1) {
         perror("Converting to UCS-2 failed");
+        FREE(buffer);
         if (real_strlen != NULL)
             *real_strlen = 0;
         return NULL;
@@ -298,16 +238,10 @@ char *get_process_filename(const char *prefix) {
     if (dir == NULL) {
         struct passwd *pw = getpwuid(getuid());
         const char *username = pw ? pw->pw_name : "unknown";
-        if (asprintf(&dir, "/tmp/i3-%s", username) == -1) {
-            perror("asprintf()");
-            return NULL;
-        }
+        sasprintf(&dir, "/tmp/i3-%s", username);
     } else {
         char *tmp;
-        if (asprintf(&tmp, "%s/i3", dir) == -1) {
-            perror("asprintf()");
-            return NULL;
-        }
+        sasprintf(&tmp, "%s/i3", dir);
         dir = tmp;
     }
     if (!path_exists(dir)) {
@@ -317,11 +251,7 @@ char *get_process_filename(const char *prefix) {
         }
     }
     char *filename;
-    if (asprintf(&filename, "%s/%s.%d", dir, prefix, getpid()) == -1) {
-        perror("asprintf()");
-        filename = NULL;
-    }
-
+    sasprintf(&filename, "%s/%s.%d", dir, prefix, getpid());
     free(dir);
     return filename;
 }
@@ -486,27 +416,3 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) {
 }
 
 #endif
-
-#if defined(__APPLE__)
-
-/*
- * Taken from FreeBSD
- * Returns a pointer to a new string which is a duplicate of the
- * string, but only copies at most n characters.
- *
- */
-char *strndup(const char *str, size_t n) {
-    size_t len;
-    char *copy;
-
-    for (len = 0; len < n && str[len]; len++)
-        continue;
-
-    if ((copy = malloc(len + 1)) == NULL)
-        return (NULL);
-    memcpy(copy, str, len);
-    copy[len] = '\0';
-    return (copy);
-}
-
-#endif
index 3dd6645630757ed5566b1bdc9e763d9d4ed79c0e..30957a4b81678e3f629d12acb6303b17812fa8f2 100644 (file)
@@ -4,6 +4,8 @@
  * i3 - an improved dynamic tiling window manager
  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
+ * window.c: Updates window attributes (X11 hints/properties).
+ *
  */
 #include "all.h"
 
@@ -14,7 +16,7 @@
  */
 void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
-        DLOG("empty property, not updating\n");
+        DLOG("WM_CLASS not set.\n");
         FREE(prop);
         return;
     }
@@ -103,7 +105,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
  */
 void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
-        DLOG("prop == NULL\n");
+        DLOG("WM_NAME not set (_NET_WM_NAME is what you want anyways).\n");
         FREE(prop);
         return;
     }
@@ -123,6 +125,7 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo
         return;
     }
 
+    LOG("WM_NAME changed to \"%s\"\n", new_name);
     LOG("Using legacy window title. Note that in order to get Unicode window "
         "titles in i3, the application has to set _NET_WM_NAME (UTF-8)\n");
 
@@ -149,7 +152,7 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo
  */
 void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) {
     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
-        DLOG("prop == NULL\n");
+        DLOG("CLIENT_LEADER not set.\n");
         FREE(prop);
         return;
     }
@@ -173,7 +176,7 @@ void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) {
  */
 void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop) {
     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
-        DLOG("prop == NULL\n");
+        DLOG("TRANSIENT_FOR not set.\n");
         FREE(prop);
         return;
     }
@@ -197,7 +200,7 @@ void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop)
  */
 void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop) {
     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
-        DLOG("prop == NULL\n");
+        DLOG("_NET_WM_STRUT_PARTIAL not set.\n");
         FREE(prop);
         return;
     }
@@ -215,3 +218,36 @@ void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop)
 
     free(prop);
 }
+
+/*
+ * Updates the WM_WINDOW_ROLE
+ *
+ */
+void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
+    if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+        DLOG("WM_WINDOW_ROLE not set.\n");
+        FREE(prop);
+        return;
+    }
+
+    char *new_role;
+    if (asprintf(&new_role, "%.*s", xcb_get_property_value_length(prop),
+                 (char*)xcb_get_property_value(prop)) == -1) {
+        perror("asprintf()");
+        DLOG("Could not get WM_WINDOW_ROLE\n");
+        free(prop);
+        return;
+    }
+    FREE(win->role);
+    win->role = new_role;
+    LOG("WM_WINDOW_ROLE changed to \"%s\"\n", win->role);
+
+    if (before_mgmt) {
+        free(prop);
+        return;
+    }
+
+    run_assignments(win);
+
+    free(prop);
+}
index a7a5a7aad6b551b717c3408861d4f0aa0d5a190f..6bfc7a6dfcc19b9e36463c58e56a52c56c47ddd5 100644 (file)
@@ -2,13 +2,18 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * workspace.c: Functions for modifying workspaces
+ * workspace.c: Modifying workspaces, accessing them, moving containers to
+ *              workspaces.
  *
  */
 #include "all.h"
 
+/* Stores a copy of the name of the last used workspace for the workspace
+ * back-and-forth switching. */
+static char *previous_workspace_name = NULL;
+
 /*
  * Returns a pointer to the workspace with the given number (starting at 0),
  * creating the workspace if necessary (by allocating the necessary amount of
@@ -41,7 +46,7 @@ Con *workspace_get(const char *num, bool *created) {
          * will handle CT_WORKSPACEs differently */
         workspace = con_new(NULL, NULL);
         char *name;
-        asprintf(&name, "[i3 con] workspace %s", num);
+        sasprintf(&name, "[i3 con] workspace %s", num);
         x_set_name(workspace, name);
         free(name);
         workspace->type = CT_WORKSPACE;
@@ -49,12 +54,12 @@ Con *workspace_get(const char *num, bool *created) {
         workspace->name = sstrdup(num);
         /* We set ->num to the number if this workspace’s name consists only of
          * a positive number. Otherwise it’s a named ws and num will be -1. */
-        char *end;
-        long parsed_num = strtol(num, &end, 10);
+        char *endptr = NULL;
+        long parsed_num = strtol(num, &endptr, 10);
         if (parsed_num == LONG_MIN ||
             parsed_num == LONG_MAX ||
             parsed_num < 0 ||
-            (end && *end != '\0'))
+            endptr == num)
             workspace->num = -1;
         else workspace->num = parsed_num;
         LOG("num = %d\n", workspace->num);
@@ -176,15 +181,9 @@ static void workspace_reassign_sticky(Con *con) {
         workspace_reassign_sticky(current);
 }
 
-/*
- * Switches to the given workspace
- *
- */
-void workspace_show(const char *num) {
-    Con *workspace, *current, *old = NULL;
 
-    bool changed_num_workspaces;
-    workspace = workspace_get(num, &changed_num_workspaces);
+static void _workspace_show(Con *workspace, bool changed_num_workspaces) {
+    Con *current, *old = NULL;
 
     /* disable fullscreen for the other workspaces and get the workspace we are
      * currently on. */
@@ -197,11 +196,21 @@ void workspace_show(const char *num) {
     /* enable fullscreen for the target workspace. If it happens to be the
      * same one we are currently on anyways, we can stop here. */
     workspace->fullscreen_mode = CF_OUTPUT;
-    if (workspace == con_get_workspace(focused)) {
+    current = con_get_workspace(focused);
+    if (workspace == current) {
         DLOG("Not switching, already there.\n");
         return;
     }
 
+    /* Remember currently focused workspace for switching back to it later with
+     * the 'workspace back_and_forth' command.
+     * NOTE: We have to duplicate the name as the original will be freed when
+     * the corresponding workspace is cleaned up. */
+
+    FREE(previous_workspace_name);
+    if (current)
+        previous_workspace_name = sstrdup(current->name);
+
     workspace_reassign_sticky(workspace);
 
     LOG("switching to %p\n", workspace);
@@ -238,11 +247,30 @@ void workspace_show(const char *num) {
     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
 }
 
+/*
+ * Switches to the given workspace
+ *
+ */
+void workspace_show(Con *workspace) {
+    _workspace_show(workspace, false);
+}
+
+/*
+ * Looks up the workspace by name and switches to it.
+ *
+ */
+void workspace_show_by_name(const char *num) {
+    Con *workspace;
+    bool changed_num_workspaces;
+    workspace = workspace_get(num, &changed_num_workspaces);
+    _workspace_show(workspace, changed_num_workspaces);
+}
+
 /*
  * Focuses the next workspace.
  *
  */
-void workspace_next() {
+Con* workspace_next() {
     Con *current = con_get_workspace(focused);
     Con *next = NULL;
     Con *output;
@@ -277,7 +305,7 @@ void workspace_next() {
                     found_current = 1;
                 } else if (child->num == -1 && (current->num != -1 || found_current)) {
                     next = child;
-                    goto workspace_next_show;
+                    goto workspace_next_end;
                 }
             }
     }
@@ -292,16 +320,15 @@ void workspace_next() {
                     next = child;
             }
     }
-
-workspace_next_show:
-    workspace_show(next->name);
+workspace_next_end:
+    return next;
 }
 
 /*
  * Focuses the previous workspace.
  *
  */
-void workspace_prev() {
+Con* workspace_prev() {
     Con *current = con_get_workspace(focused);
     Con *prev = NULL;
     Con *output;
@@ -333,10 +360,10 @@ void workspace_prev() {
                 if (child->type != CT_WORKSPACE)
                     continue;
                 if (child == current) {
-                    found_current = 1;
+                    found_current = true;
                 } else if (child->num == -1 && (current->num != -1 || found_current)) {
                     prev = child;
-                    goto workspace_prev_show;
+                    goto workspace_prev_end;
                 }
             }
     }
@@ -352,8 +379,21 @@ void workspace_prev() {
             }
     }
 
-workspace_prev_show:
-    workspace_show(prev->name);
+workspace_prev_end:
+    return prev;
+}
+
+/*
+ * Focuses the previously focused workspace.
+ *
+ */
+void workspace_back_and_forth() {
+    if (!previous_workspace_name) {
+        DLOG("No previous workspace name set. Not switching.");
+        return;
+    }
+
+    workspace_show_by_name(previous_workspace_name);
 }
 
 static bool get_urgency_flag(Con *con) {
diff --git a/src/x.c b/src/x.c
index c2daca4c2f8538b18e6720fa06194e9cf2438639..61824d58bc57145a644988c007bcd18279b093ff 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -1,7 +1,13 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * x.c: Interface to X11, transfers our in-memory state to X11 (see also
+ *      render.c). Basically a big state machine.
+ *
  */
-
 #include "all.h"
 
 /* Stores the X11 window ID of the currently focused window */
@@ -268,21 +274,16 @@ void x_draw_decoration(Con *con) {
          parent->layout != L_TABBED) ||
         con->type == CT_FLOATING_CON)
         return;
-    DLOG("decoration should be rendered for con %p\n", con);
 
     /* Skip containers whose height is 0 (for example empty dockareas) */
-    if (con->rect.height == 0) {
-        DLOG("height == 0, not rendering\n");
+    if (con->rect.height == 0)
         return;
-    }
 
     /* Skip containers whose pixmap has not yet been created (can happen when
      * decoration rendering happens recursively for a window for which
      * x_push_node() was not yet called) */
-    if (leaf && con->pixmap == XCB_NONE) {
-        DLOG("pixmap not yet created, not rendering\n");
+    if (leaf && con->pixmap == XCB_NONE)
         return;
-    }
 
     /* 1: build deco_params and compare with cache */
     struct deco_render_params *p = scalloc(sizeof(struct deco_render_params));
@@ -313,15 +314,12 @@ void x_draw_decoration(Con *con) {
         !parent->pixmap_recreated &&
         !con->pixmap_recreated &&
         memcmp(p, con->deco_render_params, sizeof(struct deco_render_params)) == 0) {
-        DLOG("CACHE HIT, copying existing pixmaps\n");
         free(p);
         goto copy_pixmaps;
     }
 
-    DLOG("CACHE MISS\n");
     Con *next = con;
     while ((next = TAILQ_NEXT(next, nodes))) {
-        DLOG("Also invalidating cache of %p\n", next);
         FREE(next->deco_render_params);
     }
 
@@ -356,7 +354,7 @@ void x_draw_decoration(Con *con) {
                 );
 #endif
 
-        xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, config.client.background);
+        xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) { config.client.background });
         xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, sizeof(background) / sizeof(xcb_rectangle_t), background);
     }
 
@@ -373,7 +371,7 @@ void x_draw_decoration(Con *con) {
          * (left, bottom and right part). We don’t just fill the whole
          * rectangle because some childs are not freely resizable and we want
          * their background color to "shine through". */
-        xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, p->color->background);
+        xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background });
         xcb_rectangle_t borders[] = {
             { 0, 0, br.x, r->height },
             { 0, r->height + br.height + br.y, r->width, r->height },
@@ -389,18 +387,16 @@ void x_draw_decoration(Con *con) {
 
     /* if this is a borderless/1pixel window, we don’t * need to render the
      * decoration. */
-    if (p->border_style != BS_NORMAL) {
-        DLOG("border style not BS_NORMAL, aborting rendering of decoration\n");
+    if (p->border_style != BS_NORMAL)
         goto copy_pixmaps;
-    }
 
     /* 4: paint the bar */
-    xcb_change_gc_single(conn, parent->pm_gc, XCB_GC_FOREGROUND, p->color->background);
+    xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background });
     xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height };
     xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect);
 
     /* 5: draw two unconnected lines in border color */
-    xcb_change_gc_single(conn, parent->pm_gc, XCB_GC_FOREGROUND, p->color->border);
+    xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
     Rect *dr = &(con->deco_rect);
     xcb_segment_t segments[] = {
         { dr->x,                 dr->y,
@@ -439,7 +435,7 @@ void x_draw_decoration(Con *con) {
     Con *il_parent = parent;
     if (il_parent->layout != L_STACKED) {
         while (1) {
-            DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout);
+            //DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout);
             if (il_parent->layout == L_STACKED)
                 indent_level++;
             if (il_parent->type == CT_WORKSPACE || il_parent->type == CT_DOCKAREA || il_parent->type == CT_OUTPUT)
@@ -448,7 +444,7 @@ void x_draw_decoration(Con *con) {
             indent_mult++;
         }
     }
-    DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
+    //DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
     int indent_px = (indent_level * 5) * indent_mult;
 
     if (win->uses_net_wm_name)
@@ -538,10 +534,8 @@ void x_push_node(Con *con) {
             }
         }
         rect.height = max_y + max_height;
-        if (rect.height == 0) {
-            DLOG("Unmapping container %p because it does not contain anything.\n", con);
+        if (rect.height == 0)
             con->mapped = false;
-        }
     }
 
     /* reparent the child window (when the window was moved due to a sticky
@@ -585,9 +579,6 @@ void x_push_node(Con *con) {
          * (height == 0). */
         if ((state->rect.width != rect.width ||
             state->rect.height != rect.height)) {
-            DLOG("CACHE: creating new pixmap for con %p (old: %d x %d, new: %d x %d)\n",
-                    con, state->rect.width, state->rect.height,
-                    rect.width, rect.height);
             if (con->pixmap == 0) {
                 con->pixmap = xcb_generate_id(conn);
                 con->pm_gc = xcb_generate_id(conn);
@@ -803,7 +794,7 @@ void x_push_changes(Con *con) {
             order_changed = true;
         if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) {
             stacking_changed = true;
-            DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id);
+            //DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id);
             uint32_t mask = 0;
             mask |= XCB_CONFIG_WINDOW_SIBLING;
             mask |= XCB_CONFIG_WINDOW_STACK_MODE;
@@ -819,9 +810,25 @@ void x_push_changes(Con *con) {
     if (stacking_changed)
         ewmh_update_client_list_stacking(btt_stack, btt_stack_num);
 
-    DLOG("\n\n PUSHING CHANGES\n\n");
+    DLOG("PUSHING CHANGES\n");
     x_push_node(con);
 
+    if (warp_to) {
+        xcb_query_pointer_reply_t *pointerreply = xcb_query_pointer_reply(conn, pointercookie, NULL);
+        if (!pointerreply) {
+            ELOG("Could not query pointer position, not warping pointer\n");
+        } else {
+            int mid_x = warp_to->x + (warp_to->width / 2);
+            int mid_y = warp_to->y + (warp_to->height / 2);
+
+            Output *current = get_output_containing(pointerreply->root_x, pointerreply->root_y);
+            Output *target = get_output_containing(mid_x, mid_y);
+            if (current != target)
+                xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
+        }
+        warp_to = NULL;
+    }
+
     //DLOG("Re-enabling EnterNotify\n");
     values[0] = FRAME_EVENT_MASK;
     CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {
@@ -836,7 +843,6 @@ void x_push_changes(Con *con) {
     if (focused->window != NULL)
         to_focus = focused->window->id;
 
-    DLOG("focused_id = 0x%08x, to_focus = 0x%08x\n", focused_id, to_focus);
     if (focused_id != to_focus) {
         if (!focused->mapped) {
             DLOG("Not updating focus (to %p / %s), focused window is not mapped.\n", focused, focused->name);
@@ -873,24 +879,8 @@ void x_push_changes(Con *con) {
         focused_id = root;
     }
 
-    if (warp_to) {
-        xcb_query_pointer_reply_t *pointerreply = xcb_query_pointer_reply(conn, pointercookie, NULL);
-        if (!pointerreply) {
-            ELOG("Could not query pointer position, not warping pointer\n");
-        } else {
-            int mid_x = warp_to->x + (warp_to->width / 2);
-            int mid_y = warp_to->y + (warp_to->height / 2);
-
-            Output *current = get_output_containing(pointerreply->root_x, pointerreply->root_y);
-            Output *target = get_output_containing(mid_x, mid_y);
-            if (current != target)
-                xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
-        }
-        warp_to = NULL;
-    }
-
     xcb_flush(conn);
-    DLOG("\n\n ENDING CHANGES\n\n");
+    DLOG("ENDING CHANGES\n");
 
     /* Disable EnterWindow events for windows which will be unmapped in
      * x_push_node_unmaps() now. Unmapping windows happens when switching
index 09f0fb2568336f5e094ebc734f018f1b8c012295..aa761cac74679873f32f0347d3675cce708de934 100644 (file)
--- a/src/xcb.c
+++ b/src/xcb.c
@@ -2,91 +2,15 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * xcb.c: Helper functions for easier usage of XCB
  *
  */
-
 #include "all.h"
 
-TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
 unsigned int xcb_numlock_mask;
 
-/*
- * Loads a font for usage, also getting its height. If fallback is true,
- * i3 loads 'fixed' or '-misc-*' if the font cannot be found instead of
- * exiting.
- *
- */
-i3Font load_font(const char *pattern, bool fallback) {
-    i3Font new;
-    xcb_void_cookie_t font_cookie;
-    xcb_list_fonts_with_info_cookie_t info_cookie;
-
-    /* Send all our requests first */
-    new.id = xcb_generate_id(conn);
-    font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern);
-    info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
-    /* Check for errors. If errors, fall back to default font. */
-    xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
-
-    /* If we fail to open font, fall back to 'fixed'. If opening 'fixed' fails fall back to '-misc-*' */
-    if (error != NULL) {
-        ELOG("Could not open font %s (X error %d). Reverting to backup font.\n", pattern, error->error_code);
-        pattern = "fixed";
-        font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern);
-        info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
-        /* Check if we managed to open 'fixed' */
-        xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
-
-        /* Fall back to '-misc-*' if opening 'fixed' fails. */
-        if (error != NULL) {
-            ELOG("Could not open fallback font '%s', trying with '-misc-*'\n",pattern);
-            pattern = "-misc-*";
-            font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern);
-            info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
-            check_error(conn, font_cookie, "Could open neither requested font nor fallback (fixed or -misc-*");
-        }
-    }
-
-    /* Get information (height/name) for this font */
-    xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
-    exit_if_null(reply, "Could not load font \"%s\"\n", pattern);
-
-    new.height = reply->font_ascent + reply->font_descent;
-
-    free(reply);
-
-    return new;
-}
-
-/*
- * Returns the colorpixel to use for the given hex color (think of HTML).
- *
- * The hex_color has to start with #, for example #FF00FF.
- *
- * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
- * This has to be done by the caller.
- *
- */
-uint32_t get_colorpixel(char *hex) {
-    char strgroups[3][3] = {{hex[1], hex[2], '\0'},
-                            {hex[3], hex[4], '\0'},
-                            {hex[5], hex[6], '\0'}};
-    uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
-                         (strtol(strgroups[1], NULL, 16)),
-                         (strtol(strgroups[2], NULL, 16))};
-
-    return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
-}
-
 /*
  * Convenience wrapper around xcb_create_window which takes care of depth, generating an ID and checking
  * for errors.
@@ -132,23 +56,15 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
     return result;
 }
 
-/*
- * Changes a single value in the graphic context (so one doesn’t have to define an array of values)
- *
- */
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
-    xcb_change_gc(conn, gc, mask, &value);
-}
-
 /*
  * Draws a line from x,y to to_x,to_y using the given color
  *
  */
 void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
                    uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y) {
-    xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel);
-    xcb_point_t points[] = {{x, y}, {to_x, to_y}};
-    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2, points);
+    xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]){ colorpixel });
+    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2,
+                  (xcb_point_t[]) { {x, y}, {to_x, to_y} });
 }
 
 /*
@@ -157,50 +73,18 @@ void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext
  */
 void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
                    uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
-    xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel);
+    xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]){ colorpixel });
     xcb_rectangle_t rect = {x, y, width, height};
     xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
 }
 
-/*
- * Generates a configure_notify event and sends it to the given window
- * Applications need this to think they’ve configured themselves correctly.
- * The truth is, however, that we will manage them.
- *
- */
-void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window, int border_width) {
-    /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
-     * In order to properly initialize these bytes, we allocate 32 bytes even
-     * though we only need less for an xcb_configure_notify_event_t */
-    void *event = scalloc(32);
-    xcb_configure_notify_event_t *generated_event = event;
-
-    generated_event->event = window;
-    generated_event->window = window;
-    generated_event->response_type = XCB_CONFIGURE_NOTIFY;
-
-    generated_event->x = r.x;
-    generated_event->y = r.y;
-    generated_event->width = r.width;
-    generated_event->height = r.height;
-
-    generated_event->border_width = border_width;
-    generated_event->above_sibling = XCB_NONE;
-    generated_event->override_redirect = false;
-
-    xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)generated_event);
-    xcb_flush(conn);
-
-    free(event);
-}
-
 /*
  * Generates a configure_notify_event with absolute coordinates (relative to the X root
  * window, not to the client’s frame) for the given client.
  *
  */
 void fake_absolute_configure_notify(Con *con) {
-    Rect absolute;
+    xcb_rectangle_t absolute;
     if (con->window == NULL)
         return;
 
@@ -237,60 +121,6 @@ void send_take_focus(xcb_window_t window) {
     free(event);
 }
 
-/*
- * Finds out which modifier mask is the one for numlock, as the user may change this.
- *
- */
-void xcb_get_numlock_mask(xcb_connection_t *conn) {
-    xcb_key_symbols_t *keysyms;
-    xcb_get_modifier_mapping_cookie_t cookie;
-    xcb_get_modifier_mapping_reply_t *reply;
-    xcb_keycode_t *modmap;
-    int mask, i;
-    const int masks[8] = { XCB_MOD_MASK_SHIFT,
-                           XCB_MOD_MASK_LOCK,
-                           XCB_MOD_MASK_CONTROL,
-                           XCB_MOD_MASK_1,
-                           XCB_MOD_MASK_2,
-                           XCB_MOD_MASK_3,
-                           XCB_MOD_MASK_4,
-                           XCB_MOD_MASK_5 };
-
-    /* Request the modifier map */
-    cookie = xcb_get_modifier_mapping(conn);
-
-    /* Get the keysymbols */
-    keysyms = xcb_key_symbols_alloc(conn);
-
-    if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) {
-        xcb_key_symbols_free(keysyms);
-        return;
-    }
-
-    modmap = xcb_get_modifier_mapping_keycodes(reply);
-
-    /* Get the keycode for numlock */
-#ifdef OLD_XCB_KEYSYMS_API
-    xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
-#else
-    /* For now, we only use the first keysymbol. */
-    xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
-    if (numlock_syms == NULL)
-        return;
-    xcb_keycode_t numlock = *numlock_syms;
-    free(numlock_syms);
-#endif
-
-    /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
-    for (mask = 0; mask < 8; mask++)
-        for (i = 0; i < reply->keycodes_per_modifier; i++)
-            if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
-                xcb_numlock_mask = masks[mask];
-
-    xcb_key_symbols_free(keysyms);
-    free(reply);
-}
-
 /*
  * Raises the given window (typically client->frame) above all other windows
  *
@@ -373,3 +203,20 @@ void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect) {
     LOG("warp pointer to: %d %d\n", mid_x, mid_y);
     xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
 }
+
+/*
+ * Set the cursor of the root window to the given cursor id.
+ * This function should only be used if xcursor_supported == false.
+ * Otherwise, use xcursor_set_root_cursor().
+ *
+ */
+void xcb_set_root_cursor(int cursor) {
+    xcb_cursor_t cursor_id = xcb_generate_id(conn);
+    i3Font cursor_font = load_font("cursor", false);
+    int xcb_cursor = xcursor_get_xcb_cursor(cursor);
+    xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id,
+            xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535);
+    xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, &cursor_id);
+    xcb_free_cursor(conn, cursor_id);
+    xcb_flush(conn);
+}
index 69518c3073621451bb65cb8ba0bef9f0c69620be..f04a3959922612e766fdcd991ee9e839274abc70 100644 (file)
@@ -1,5 +1,11 @@
 /*
  * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * xcursor.c: libXcursor support for themed cursors.
+ *
  */
 #include <assert.h>
 #include <X11/Xcursor/Xcursor.h>
@@ -14,7 +20,8 @@ static Cursor cursors[XCURSOR_CURSOR_MAX];
 static const int xcb_cursors[XCURSOR_CURSOR_MAX] = {
     XCB_CURSOR_LEFT_PTR,
     XCB_CURSOR_SB_H_DOUBLE_ARROW,
-    XCB_CURSOR_SB_V_DOUBLE_ARROW
+    XCB_CURSOR_SB_V_DOUBLE_ARROW,
+    XCB_CURSOR_WATCH
 };
 
 static Cursor load_cursor(const char *name) {
@@ -28,6 +35,7 @@ void xcursor_load_cursors() {
     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");
 }
 
 /*
@@ -41,9 +49,9 @@ void xcursor_load_cursors() {
  * races might occur (even though we flush the Xlib connection).
  *
  */
-void xcursor_set_root_cursor() {
+void xcursor_set_root_cursor(int cursor_id) {
     XSetWindowAttributes attributes;
-    attributes.cursor = xcursor_get_cursor(XCURSOR_CURSOR_POINTER);
+    attributes.cursor = xcursor_get_cursor(cursor_id);
     XChangeWindowAttributes(xlibdpy, DefaultRootWindow(xlibdpy), CWCursor, &attributes);
     XFlush(xlibdpy);
 }
index c116deaa954fb0bd709a1e5ec44a23c0f1b5e7a9..c3ad97c3d7ed62c22ff71412abdc6cb1a5f62850 100644 (file)
@@ -2,20 +2,17 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * This is LEGACY code (we support RandR, which can do much more than
  * Xinerama), but necessary for the poor users of the nVidia binary
- * driver which does not support RandR in 2010 *sigh*.
+ * driver which does not support RandR in 2011 *sigh*.
  *
  */
+#include "all.h"
 
 #include <xcb/xinerama.h>
 
-#include "all.h"
 
 static int num_screens;
 
@@ -59,7 +56,7 @@ static void query_screens(xcb_connection_t *conn) {
             s->rect.height = min(s->rect.height, screen_info[screen].height);
         } else {
             s = scalloc(sizeof(Output));
-            asprintf(&(s->name), "xinerama-%d", num_screens);
+            sasprintf(&(s->name), "xinerama-%d", num_screens);
             DLOG("Created new Xinerama screen %s (%p)\n", s->name, s);
             s->active = true;
             s->rect.x = screen_info[screen].x_org;
diff --git a/testcases/Makefile b/testcases/Makefile
deleted file mode 100644 (file)
index edf5ee0..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-test:
-       PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/12*.t
-
-clean:
-       rm -rf testsuite-* latest
-
-all: test
-
-testfull:
-       PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/01* t/02* t/03* t/05* t/17* t/18* t/19* t/20*
diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL
new file mode 100755 (executable)
index 0000000..11385f7
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+use strict; use warnings;
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+    NAME => 'i3 testsuite',
+    MIN_PERL_VERSION => '5.010000', # 5.10.0
+    PREREQ_PM => {
+        'AnyEvent'     => 0,
+        'AnyEvent::I3' => '0.09',
+        'X11::XCB'     => '0.03',
+        'Test::Most'   => 0,
+        'Test::Deep'   => 0,
+       'EV'           => 0,
+       'Inline'       => 0,
+    },
+    # don't install any files from this directory
+    PM => {},
+    clean => {
+        FILES => 'testsuite-* latest'
+    }
+);
+# and don't run the tests while installing
+sub MY::test { }
index c05d6e5b4341dae8594e22fb95fd25eb5e7ef828..c43fbf07b49cc4553a879ef79eee768ee40aec68 100755 (executable)
@@ -1,59 +1,83 @@
 #!/usr/bin/env perl
 # vim:ts=4:sw=4:expandtab
-#
 # © 2010-2011 Michael Stapelberg and contributors
-#
-# syntax: ./complete-run.pl --display :1 --display :2
-# to run the test suite on the X11 displays :1 and :2
-# use 'Xdummy :1' and 'Xdummy :2' before to start two
-# headless X11 servers
-#
 
 use strict;
 use warnings;
-use EV;
-use AnyEvent;
-use IO::Scalar; # not in core :\
-use File::Temp qw(tempfile tempdir);
 use v5.10;
-use DateTime;
-use Data::Dumper;
+# the following are modules which ship with Perl (>= 5.10):
+use Pod::Usage;
 use Cwd qw(abs_path);
-use Proc::Background;
+use File::Basename qw(basename);
+use File::Temp qw(tempfile tempdir);
+use Getopt::Long;
+use IO::Socket::UNIX;
+use POSIX;
+use Time::HiRes qw(sleep gettimeofday tv_interval);
 use TAP::Harness;
 use TAP::Parser;
 use TAP::Parser::Aggregator;
-use File::Basename qw(basename);
+# these are shipped with the testsuite
+use lib qw(lib);
+use SocketActivation;
+use StartXDummy;
+use StatusLine;
+# the following modules are not shipped with Perl
+use AnyEvent;
+use AnyEvent::Handle;
 use AnyEvent::I3 qw(:all);
-use Try::Tiny;
-use Getopt::Long;
-use Time::HiRes qw(sleep);
-use X11::XCB::Connection;
+use X11::XCB;
+
+# We actually use AnyEvent to make sure it loads an event loop implementation.
+# Afterwards, we overwrite SIGCHLD:
+my $cv = AnyEvent->condvar;
 
-# install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV
-# XXX: we could maybe also use a different loop than the default loop in EV?
+# Install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent.
+# AnyEvent’s handler wait()s for every child which conflicts with TAP (TAP
+# needs to get the exit status to determine if a test is successful).
 $SIG{CHLD} = sub {
 };
 
 # reads in a whole file
 sub slurp {
-    open my $fh, '<', shift;
+    open(my $fh, '<', shift);
     local $/;
     <$fh>;
 }
 
+# convinience wrapper to write to the log file
+my $log;
+sub Log { say $log "@_" }
+
 my $coverage_testing = 0;
+my $valgrind = 0;
+my $help = 0;
+# Number of tests to run in parallel. Important to know how many Xdummy
+# instances we need to start (unless @displays are given). Defaults to
+# num_cores * 2.
+my $parallel = undef;
 my @displays = ();
+my @childpids = ();
 
 my $result = GetOptions(
     "coverage-testing" => \$coverage_testing,
+    "valgrind" => \$valgrind,
     "display=s" => \@displays,
+    "parallel=i" => \$parallel,
+    "help|?" => \$help,
 );
 
+pod2usage(-verbose => 2, -exitcode => 0) if $help;
+
 @displays = split(/,/, join(',', @displays));
 @displays = map { s/ //g; $_ } @displays;
 
-@displays = qw(:1) if @displays == 0;
+# No displays specified, let’s start some Xdummy instances.
+if (@displays == 0) {
+    my ($displays, $pids) = start_xdummy($parallel);
+    @displays = @$displays;
+    @childpids = @$pids;
+}
 
 # connect to all displays for two reasons:
 # 1: check if the display actually works
@@ -63,16 +87,18 @@ my $result = GetOptions(
 my @conns;
 my @wdisplays;
 for my $display (@displays) {
-    try {
-        my $x = X11::XCB::Connection->new(display => $display);
+    my $screen;
+    my $x = X11::XCB->new($display, $screen);
+    if ($x->has_error) {
+        Log "WARNING: Not using X11 display $display, could not connect";
+    } else {
         push @conns, $x;
         push @wdisplays, $display;
-    } catch {
-        say STDERR "WARNING: Not using X11 display $display, could not connect";
-    };
+    }
 }
 
-my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
+die "No usable displays found" if @wdisplays == 0;
+
 my $config = slurp('i3-test.config');
 
 # 1: get a list of all testcases
@@ -83,13 +109,17 @@ my @testfiles = @ARGV;
 
 # 2: create an output directory for this test-run
 my $outdir = "testsuite-";
-$outdir .= DateTime->now->strftime("%Y-%m-%d-%H-%M-%S-");
+$outdir .= POSIX::strftime("%Y-%m-%d-%H-%M-%S-", localtime());
 $outdir .= `git describe --tags`;
 chomp($outdir);
 mkdir($outdir) or die "Could not create $outdir";
 unlink("latest") if -e "latest";
 symlink("$outdir", "latest") or die "Could not symlink latest to $outdir";
 
+my $logfile = "$outdir/complete-run.log";
+open $log, '>', $logfile or die "Could not create '$logfile': $!";
+say "Writing logfile to '$logfile'...";
+
 # 3: run all tests
 my @done;
 my $num = @testfiles;
@@ -98,7 +128,7 @@ my $harness = TAP::Harness->new({ });
 my $aggregator = TAP::Parser::Aggregator->new();
 $aggregator->start();
 
-my $cv = AnyEvent->condvar;
+status_init(displays => \@wdisplays, tests => $num);
 
 # We start tests concurrently: For each display, one test gets started. Every
 # test starts another test after completing.
@@ -116,96 +146,224 @@ take_job($_) for @wdisplays;
 #
 sub take_job {
     my ($display) = @_;
-    my ($fh, $tmpfile) = tempfile();
-    say $fh $config;
-    say $fh "ipc-socket /tmp/nested-$display";
-    close($fh);
 
     my $test = shift @testfiles;
     return unless $test;
-    my $logpath = "$outdir/i3-log-for-" . basename($test);
-    my $cmd = "export DISPLAY=$display; exec $i3cmd -c $tmpfile >$logpath 2>&1";
+
     my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/);
+    my $basename = basename($test);
+    my $logpath = "$outdir/i3-log-for-$basename";
+
+    my ($fh, $tmpfile) = tempfile("i3-cfg-for-$basename.XXXXXX", UNLINK => 1);
+    say $fh $config;
+    say $fh "ipc-socket /tmp/nested-$display";
+    close($fh);
 
-    my $process = Proc::Background->new($cmd) unless $dont_start;
-    say "[$display] Running $test with logfile $logpath";
+    my $activate_cv = AnyEvent->condvar;
+    my $time_before_start = [gettimeofday];
+
+    my $pid;
+    if ($dont_start) {
+        $activate_cv->send(1);
+    } else {
+        $pid = activate_i3(
+            unix_socket_path => "/tmp/nested-$display-activation",
+            display => $display,
+            configfile => $tmpfile,
+            outdir => $outdir,
+            logpath => $logpath,
+            valgrind => $valgrind,
+            cv => $activate_cv
+        );
+
+        my $child_watcher;
+        $child_watcher = AnyEvent->child(pid => $pid, cb => sub {
+            Log status($display, "child died. pid = $pid");
+            undef $child_watcher;
+        });
+    }
 
-    sleep 0.5;
     my $kill_i3 = sub {
+        my $kill_cv = AnyEvent->condvar;
+
         # Don’t bother killing i3 when we haven’t started it
-        return if $dont_start;
+        if ($dont_start) {
+            $kill_cv->send();
+            return $kill_cv;
+        }
 
         # When measuring code coverage, try to exit i3 cleanly (otherwise, .gcda
         # files are not written) and fallback to killing it
-        if ($coverage_testing) {
+        if ($coverage_testing || $valgrind) {
             my $exited = 0;
-            try {
-                say "Exiting i3 cleanly...";
-                i3("/tmp/nested-$display")->command('exit')->recv;
-                $exited = 1;
-            };
-            return if $exited;
+            Log status($display, 'Exiting i3 cleanly...');
+            my $i3 = i3("/tmp/nested-$display");
+            $i3->connect->cb(sub {
+                if (!$_[0]->recv) {
+                    # Could not connect to i3, just kill -9 it
+                    kill(9, $pid) or die "Could not kill i3 using kill($pid)";
+                    $kill_cv->send();
+                } else {
+                    # Connected. Now send exit and continue once that’s acked.
+                    $i3->command('exit')->cb(sub {
+                        $kill_cv->send();
+                    });
+                }
+            });
+        } else {
+            Log status($display, 'killing i3');
+
+            # No coverage testing or valgrind? Just kill -9 i3.
+            kill(9, $pid) or die "Could not kill i3 using kill($pid)";
+            $kill_cv->send();
         }
 
-        say "[$display] killing i3";
-        kill(9, $process->pid) or die "could not kill i3";
+        return $kill_cv;
     };
 
-    my $output;
-    my $parser = TAP::Parser->new({
-        exec => [ 'sh', '-c', "DISPLAY=$display /usr/bin/perl -It/lib $test" ],
-        spool => IO::Scalar->new(\$output),
-        merge => 1,
-    });
-
-    my @watchers;
-    my ($stdout, $stderr) = $parser->get_select_handles;
-    for my $handle ($parser->get_select_handles) {
-        my $w;
-        $w = AnyEvent->io(
-            fh => $handle,
-            poll => 'r',
-            cb => sub {
-                # Ignore activity on stderr (unnecessary with merge => 1,
-                # but let’s keep it in here if we want to use merge => 0
-                # for some reason in the future).
-                return if defined($stderr) and $handle == $stderr;
-
-                my $result = $parser->next;
-                if (defined($result)) {
-                    # TODO: check if we should bail out
-                    return;
-                }
-
-                # $result is not defined, we are done parsing
-                say "[$display] $test finished";
-                close($parser->delete_spool);
-                $aggregator->add($test, $parser);
-                push @done, [ $test, $output ];
-
-                $kill_i3->();
+    # This will be called as soon as i3 is running and answered to our
+    # IPC request
+    $activate_cv->cb(sub {
+        my $time_activating = [gettimeofday];
+        my $start_duration = tv_interval($time_before_start, $time_activating);
+        my ($status) = $activate_cv->recv;
+        if ($dont_start) {
+            Log status($display, 'Not starting i3, testcase does that');
+        } else {
+            my $duration = sprintf("%.2f", $start_duration);
+            Log status($display, "i3 startup: took $duration sec, status = $status");
+        }
 
-                undef $_ for @watchers;
-                if (@done == $num) {
-                    $cv->send;
-                } else {
-                    take_job($display);
+        Log status($display, "Starting $test");
+
+        my $output;
+        open(my $spool, '>', \$output);
+        my $parser = TAP::Parser->new({
+            exec => [ 'sh', '-c', qq|DISPLAY=$display LOGPATH="$logpath" OUTDIR="$outdir" VALGRIND=$valgrind /usr/bin/perl -Ilib $test| ],
+            spool => $spool,
+            merge => 1,
+        });
+
+        my $tests_completed;
+
+        my @watchers;
+        my ($stdout, $stderr) = $parser->get_select_handles;
+        for my $handle ($parser->get_select_handles) {
+            my $w;
+            $w = AnyEvent->io(
+                fh => $handle,
+                poll => 'r',
+                cb => sub {
+                    # Ignore activity on stderr (unnecessary with merge => 1,
+                    # but let’s keep it in here if we want to use merge => 0
+                    # for some reason in the future).
+                    return if defined($stderr) and $handle == $stderr;
+
+                    my $result = $parser->next;
+                    if (defined($result)) {
+                        $tests_completed++;
+                        status($display, "Running $test: [$tests_completed/??]");
+                        # TODO: check if we should bail out
+                        return;
+                    }
+
+                    # $result is not defined, we are done parsing
+                    Log status($display, "$test finished");
+                    close($parser->delete_spool);
+                    $aggregator->add($test, $parser);
+                    push @done, [ $test, $output ];
+
+                    status_completed(scalar @done);
+
+                    my $exitcv = $kill_i3->();
+                    $exitcv->cb(sub {
+
+                        undef $_ for @watchers;
+                        if (@done == $num) {
+                            $cv->send;
+                        } else {
+                            take_job($display);
+                        }
+                    });
                 }
-            }
-        );
-        push @watchers, $w;
-    }
+            );
+            push @watchers, $w;
+        }
+    });
 }
 
 $cv->recv;
 
 $aggregator->stop();
 
+# print empty lines to seperate failed tests from statuslines
+print "\n\n";
+
 for (@done) {
     my ($test, $output) = @$_;
-    say "output for $test:";
-    say $output;
+    Log "output for $test:";
+    Log $output;
+    # print error messages of failed tests
+    say for $output =~ /^not ok.+\n+((?:^#.+\n)+)/mg
 }
 
 # 4: print summary
 $harness->summary($aggregator);
+
+close $log;
+
+kill(15, $_) for @childpids;
+
+__END__
+
+=head1 NAME
+
+complete-run.pl - Run the i3 testsuite
+
+=head1 SYNOPSIS
+
+complete-run.pl [files...]
+
+=head1 EXAMPLE
+
+To run the whole testsuite on a reasonable number of Xdummy instances (your
+running X11 will not be touched), run:
+  ./complete-run.pl
+
+To run only a specific test (useful when developing a new feature), run:
+  ./complete-run t/100-fullscreen.t
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<--display>
+
+Specifies which X11 display should be used. Can be specified multiple times and
+will parallelize the tests:
+
+  # Run tests on the second X server
+  ./complete-run.pl -d :1
+
+  # Run four tests in parallel on some Xdummy servers
+  ./complete-run.pl -d :1,:2,:3,:4
+
+Note that it is not necessary to specify this anymore. If omitted,
+complete-run.pl will start (num_cores * 2) Xdummy instances.
+
+=item B<--valgrind>
+
+Runs i3 under valgrind to find memory problems. The output will be available in
+C<latest/valgrind.log>.
+
+=item B<--coverage-testing>
+
+Exits i3 cleanly (instead of kill -9) to make coverage testing work properly.
+
+=item B<--parallel>
+
+Number of Xdummy instances to start (if you don’t want to start num_cores * 2
+instances for some reason).
+
+  # Run all tests on a single Xdummy instance
+  ./complete-run.pl -p 1
diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm
new file mode 100644 (file)
index 0000000..e0b08d7
--- /dev/null
@@ -0,0 +1,129 @@
+package SocketActivation;
+# vim:ts=4:sw=4:expandtab
+
+use strict;
+use warnings;
+use IO::Socket::UNIX; # core
+use Cwd qw(abs_path); # core
+use POSIX (); # core
+use AnyEvent::Handle; # not core
+use Exporter 'import';
+use v5.10;
+
+our @EXPORT = qw(activate_i3);
+
+#
+# Starts i3 using socket activation. Creates a listening socket (with bind +
+# listen) which is then passed to i3, who in turn calls accept and handles the
+# requests.
+#
+# Since the kernel buffers the connect, the parent process can connect to the
+# socket immediately after forking. It then sends a request and waits until it
+# gets an answer. Obviously, i3 has to be initialized to actually answer the
+# request.
+#
+# This way, we can wait *precisely* the amount of time which i3 waits to get
+# ready, which is a *HUGE* speed gain (and a lot more robust) in comparison to
+# using sleep() with a fixed amount of time.
+#
+# unix_socket_path: Location of the socket to use for the activation
+# display: X11 $ENV{DISPLAY}
+# configfile: path to the configuration file to use
+# logpath: path to the logfile to which i3 will append
+# cv: an AnyEvent->condvar which will be triggered once i3 is ready
+#
+sub activate_i3 {
+    my %args = @_;
+
+    # remove the old unix socket
+    unlink($args{unix_socket_path});
+
+    # pass all file descriptors up to three to the children.
+    # we need to set this flag before opening the socket.
+    open(my $fdtest, '<', '/dev/null');
+    $^F = fileno($fdtest);
+    close($fdtest);
+    my $socket = IO::Socket::UNIX->new(
+        Listen => 1,
+        Local => $args{unix_socket_path},
+    );
+
+    my $pid = fork;
+    if (!defined($pid)) {
+        die "could not fork()";
+    }
+    if ($pid == 0) {
+        $ENV{LISTEN_PID} = $$;
+        $ENV{LISTEN_FDS} = 1;
+        delete $ENV{DESKTOP_STARTUP_ID};
+        $ENV{DISPLAY} = $args{display};
+        $ENV{PATH} = join(':',
+            '../i3-nagbar',
+            '../i3-msg',
+            '../i3-config-wizard',
+            '../i3bar',
+            '..',
+            $ENV{PATH}
+        );
+        # Only pass file descriptors 0 (stdin), 1 (stdout), 2 (stderr) and
+        # 3 (socket) to the child.
+        $^F = 3;
+
+        # If the socket does not use file descriptor 3 by chance already, we
+        # close fd 3 and dup2() the socket to 3.
+        if (fileno($socket) != 3) {
+            POSIX::close(3);
+            POSIX::dup2(fileno($socket), 3);
+        }
+
+        # Construct the command to launch i3. Use maximum debug level, disable
+        # the interactive signalhandler to make it crash immediately instead.
+        my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
+
+        if ($args{valgrind}) {
+            $i3cmd =
+                qq|valgrind -v --log-file="$args{outdir}/valgrind.log" | .
+                qq|--leak-check=full --track-origins=yes --num-callers=20 | .
+                qq|--tool=memcheck -- $i3cmd|;
+        }
+
+        # Append to $args{logpath} instead of overwriting because i3 might be
+        # run multiple times in one testcase.
+        my $cmd = "exec $i3cmd -c $args{configfile} >>$args{logpath} 2>&1";
+
+        # We need to use the shell due to using output redirections.
+        exec '/bin/sh', '-c', $cmd;
+
+        # if we are still here, i3 could not be found or exec failed. bail out.
+        exit 1;
+    }
+
+    # close the socket, the child process should be the only one which keeps a file
+    # descriptor on the listening socket.
+    $socket->close;
+
+    # We now connect (will succeed immediately) and send a request afterwards.
+    # As soon as the reply is there, i3 is considered ready.
+    my $cl = IO::Socket::UNIX->new(Peer => $args{unix_socket_path});
+    my $hdl;
+    $hdl = AnyEvent::Handle->new(
+        fh => $cl,
+        on_error => sub {
+            $hdl->destroy;
+            $args{cv}->send(0);
+        });
+
+    # send a get_tree message without payload
+    $hdl->push_write('i3-ipc' . pack("LL", 0, 4));
+
+    # wait for the reply
+    $hdl->push_read(chunk => 1, => sub {
+        my ($h, $line) = @_;
+        $args{cv}->send(1);
+        undef $hdl;
+    });
+
+    return $pid;
+}
+
+1
diff --git a/testcases/lib/StartXDummy.pm b/testcases/lib/StartXDummy.pm
new file mode 100644 (file)
index 0000000..9dfdedd
--- /dev/null
@@ -0,0 +1,82 @@
+package StartXDummy;
+# vim:ts=4:sw=4:expandtab
+
+use strict;
+use warnings;
+use Exporter 'import';
+use Time::HiRes qw(sleep);
+use v5.10;
+
+our @EXPORT = qw(start_xdummy);
+
+# reads in a whole file
+sub slurp {
+    open(my $fh, '<', shift) or return '';
+    local $/;
+    <$fh>;
+}
+
+=head2 start_xdummy($parallel)
+
+Starts C<$parallel> (or number of cores * 2 if undef) Xdummy processes (see
+the file ./Xdummy) and returns two arrayrefs: a list of X11 display numbers to
+the Xdummy processes and a list of PIDs of the processes.
+
+=cut
+sub start_xdummy {
+    my ($parallel) = @_;
+
+    my @displays = ();
+    my @childpids = ();
+
+    # Yeah, I know it’s non-standard, but Perl’s POSIX module doesn’t have
+    # _SC_NPROCESSORS_CONF.
+    my $cpuinfo = slurp('/proc/cpuinfo');
+    my $num_cores = scalar grep { /model name/ } split("\n", $cpuinfo);
+    # If /proc/cpuinfo does not exist, we fall back to 2 cores.
+    $num_cores ||= 2;
+
+    $parallel ||= $num_cores * 2;
+
+    # First get the last used display number, then increment it by one.
+    # Effectively falls back to 1 if no X server is running.
+    my ($displaynum) = reverse ('0', sort </tmp/.X11-unix/X*>);
+    $displaynum =~ s/.*(\d)$/$1/;
+    $displaynum++;
+
+    say "Starting $parallel Xdummy instances, starting at :$displaynum...";
+
+    for my $idx (0 .. ($parallel-1)) {
+        my $pid = fork();
+        die "Could not fork: $!" unless defined($pid);
+        if ($pid == 0) {
+            # Child, close stdout/stderr, then start Xdummy.
+            close STDOUT;
+            close STDERR;
+            # We use -config /dev/null to prevent Xdummy from using the system
+            # Xorg configuration. The tests should be independant from the
+            # actual system X configuration.
+            exec './Xdummy', ":$displaynum", '-config', '/dev/null';
+            exit 1;
+        }
+        push(@childpids, $pid);
+        push(@displays, ":$displaynum");
+        $displaynum++;
+    }
+
+    # Wait until the X11 sockets actually appear. Pretty ugly solution, but as
+    # long as we can’t socket-activate X11…
+    my $sockets_ready;
+    do {
+        $sockets_ready = 1;
+        for (@displays) {
+            my $path = "/tmp/.X11-unix/X" . substr($_, 1);
+            $sockets_ready = 0 unless -S $path;
+        }
+        sleep 0.1;
+    } until $sockets_ready;
+
+    return \@displays, \@childpids;
+}
+
+1
diff --git a/testcases/lib/StatusLine.pm b/testcases/lib/StatusLine.pm
new file mode 100644 (file)
index 0000000..823c671
--- /dev/null
@@ -0,0 +1,64 @@
+package StatusLine;
+use strict; use warnings;
+
+# enable autoflush on STDOUT.
+# this is essential, because we print our statuslines without a newline
+$| = 1;
+
+use Exporter 'import';
+our @EXPORT = qw/status_init status status_completed/;
+
+my $ansi_clear_line = "\033[2K";
+my $ansi_save_cursor = "\0337";
+my $ansi_restore_cursor = "\0338";
+my %ansi_line_upwards;
+
+my $tests_total;
+
+# setup %ansi_line_upwards to map all working displays to the
+# specific movement commands and initialize all status lines
+sub status_init {
+    my %args = @_;
+    my $displays = $args{displays};
+    $tests_total = $args{tests};
+
+    for my $n (1 .. @$displays) {
+        # since we are moving upwards, get $display in reverse order
+        my $display = $displays->[-$n];
+
+        $ansi_line_upwards{$display} = "\033[$n\101";
+
+        # print an empty line for this status line
+        print "\n";
+    }
+
+    status_completed(0);
+}
+
+# generates the status text, prints it in the appropiate line
+# and returns it, so it can be used in conjuction with C<Log()>
+sub status {
+    my ($display, $msg) = @_;
+    my $status = "[$display] $msg";
+
+    print
+        $ansi_save_cursor,
+        $ansi_line_upwards{$display},
+        $ansi_clear_line,
+        $status,
+        $ansi_restore_cursor;
+
+    return $status;
+}
+
+sub status_completed {
+    my $num = shift;
+    print
+        $ansi_save_cursor,
+        $ansi_clear_line,
+        "completed $num of $tests_total tests",
+        $ansi_restore_cursor;
+}
+
+
+__PACKAGE__ __END__
diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm
new file mode 100644 (file)
index 0000000..7473b61
--- /dev/null
@@ -0,0 +1,451 @@
+package i3test;
+# vim:ts=4:sw=4:expandtab
+use strict; use warnings;
+
+use File::Temp qw(tmpnam tempfile tempdir);
+use Test::Builder;
+use X11::XCB::Rect;
+use X11::XCB::Window;
+use X11::XCB qw(:all);
+use AnyEvent::I3;
+use EV;
+use List::Util qw(first);
+use Time::HiRes qw(sleep);
+use Cwd qw(abs_path);
+use SocketActivation;
+
+use v5.10;
+
+use Exporter ();
+our @EXPORT = qw(
+    get_workspace_names
+    get_unused_workspace
+    fresh_workspace
+    get_ws_content
+    get_ws
+    get_focused
+    open_empty_con
+    open_window
+    open_floating_window
+    get_dock_clients
+    cmd
+    sync_with_i3
+    does_i3_live
+    exit_gracefully
+    workspace_exists
+    focused_ws
+    get_socket_path
+    launch_with_config
+    wait_for_event
+    wait_for_map
+    wait_for_unmap
+);
+
+my $tester = Test::Builder->new();
+my $_cached_socket_path = undef;
+my $_sync_window = undef;
+my $tmp_socket_path = undef;
+
+BEGIN {
+    my $window_count = 0;
+    sub counter_window {
+        return $window_count++;
+    }
+}
+
+sub import {
+    my $class = shift;
+    my $pkg = caller;
+    eval "package $pkg;
+use Test::Most" . (@_ > 0 ? " qw(@_)" : "") . ";
+use Data::Dumper;
+use AnyEvent::I3;
+use Time::HiRes qw(sleep);
+use Test::Deep qw(eq_deeply cmp_deeply cmp_set cmp_bag cmp_methods useclass noclass set bag subbagof superbagof subsetof supersetof superhashof subhashof bool str arraylength Isa ignore methods regexprefonly regexpmatches num regexponly scalref reftype hashkeysonly blessed array re hash regexpref hash_each shallow array_each code arrayelementsonly arraylengthonly scalarrefonly listmethods any hashkeys isa);
+use v5.10;
+use strict;
+use warnings;
+";
+    @_ = ($class);
+    goto \&Exporter::import;
+}
+
+#
+# Waits for the next event and calls the given callback for every event to
+# determine if this is the event we are waiting for.
+#
+# Can be used to wait until a window is mapped, until a ClientMessage is
+# received, etc.
+#
+# wait_for_event $x, 0.25, sub { $_[0]->{response_type} == MAP_NOTIFY };
+#
+sub wait_for_event {
+    my ($x, $timeout, $cb) = @_;
+
+    my $cv = AE::cv;
+
+    my $prep = EV::prepare sub {
+        $x->flush;
+    };
+
+    my $check = EV::check sub {
+        while (defined(my $event = $x->poll_for_event)) {
+            if ($cb->($event)) {
+                $cv->send(1);
+                last;
+            }
+        }
+    };
+
+    my $watcher = EV::io $x->get_file_descriptor, EV::READ, sub {
+        # do nothing, we only need this watcher so that EV picks up the events
+    };
+
+    # Trigger timeout after $timeout seconds (can be fractional)
+    my $t = AE::timer $timeout, 0, sub { warn "timeout ($timeout secs)"; $cv->send(0) };
+
+    my $result = $cv->recv;
+    undef $t;
+    return $result;
+}
+
+# thin wrapper around wait_for_event which waits for MAP_NOTIFY
+# make sure to include 'structure_notify' in the window’s event_mask attribute
+sub wait_for_map {
+    my ($x) = @_;
+    wait_for_event $x, 2, sub { $_[0]->{response_type} == MAP_NOTIFY };
+}
+
+# Wrapper around wait_for_event which waits for UNMAP_NOTIFY. Also calls
+# sync_with_i3 to make sure i3 also picked up and processed the UnmapNotify
+# event.
+sub wait_for_unmap {
+    my ($x) = @_;
+    wait_for_event $x, 2, sub { $_[0]->{response_type} == UNMAP_NOTIFY };
+    sync_with_i3($x);
+}
+
+#
+# Opens a new window (see X11::XCB::Window), maps it, waits until it got mapped
+# and synchronizes with i3.
+#
+# set dont_map to a true value to avoid mapping
+#
+# default values:
+#     class => WINDOW_CLASS_INPUT_OUTPUT
+#     rect => [ 0, 0, 30, 30 ]
+#     background_color => '#c0c0c0'
+#     event_mask => [ 'structure_notify' ]
+#     name => 'Window <n>'
+#
+sub open_window {
+    my ($x, $args) = @_;
+    my %args = ($args ? %$args : ());
+
+    my $dont_map = delete $args{dont_map};
+
+    $args{class} //= WINDOW_CLASS_INPUT_OUTPUT;
+    $args{rect} //= [ 0, 0, 30, 30 ];
+    $args{background_color} //= '#c0c0c0';
+    $args{event_mask} //= [ 'structure_notify' ];
+    $args{name} //= 'Window ' . counter_window();
+
+    my $window = $x->root->create_child(%args);
+
+    return $window if $dont_map;
+
+    $window->map;
+    wait_for_map($x);
+    # We sync with i3 here to make sure $x->input_focus is updated.
+    sync_with_i3($x);
+    return $window;
+}
+
+# Thin wrapper around open_window which sets window_type to
+# _NET_WM_WINDOW_TYPE_UTILITY to make the window floating.
+sub open_floating_window {
+    my ($x, $args) = @_;
+    my %args = ($args ? %$args : ());
+
+    $args{window_type} = $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY');
+
+    return open_window($x, \%args);
+}
+
+sub open_empty_con {
+    my ($i3) = @_;
+
+    my $reply = $i3->command('open')->recv;
+    return $reply->{id};
+}
+
+sub get_workspace_names {
+    my $i3 = i3(get_socket_path());
+    my $tree = $i3->get_tree->recv;
+    my @outputs = @{$tree->{nodes}};
+    my @cons;
+    for my $output (@outputs) {
+        # get the first CT_CON of each output
+        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+        @cons = (@cons, @{$content->{nodes}});
+    }
+    [ map { $_->{name} } @cons ]
+}
+
+sub get_unused_workspace {
+    my @names = get_workspace_names();
+    my $tmp;
+    do { $tmp = tmpnam() } while ($tmp ~~ @names);
+    $tmp
+}
+
+sub fresh_workspace {
+    my $unused = get_unused_workspace;
+    cmd("workspace $unused");
+    $unused
+}
+
+sub get_ws {
+    my ($name) = @_;
+    my $i3 = i3(get_socket_path());
+    my $tree = $i3->get_tree->recv;
+
+    my @outputs = @{$tree->{nodes}};
+    my @workspaces;
+    for my $output (@outputs) {
+        # get the first CT_CON of each output
+        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+        @workspaces = (@workspaces, @{$content->{nodes}});
+    }
+
+    # as there can only be one workspace with this name, we can safely
+    # return the first entry
+    return first { $_->{name} eq $name } @workspaces;
+}
+
+#
+# returns the content (== tree, starting from the node of a workspace)
+# of a workspace. If called in array context, also includes the focus
+# stack of the workspace
+#
+sub get_ws_content {
+    my ($name) = @_;
+    my $con = get_ws($name);
+    return wantarray ? ($con->{nodes}, $con->{focus}) : $con->{nodes};
+}
+
+sub get_focused {
+    my ($ws) = @_;
+    my $con = get_ws($ws);
+
+    my @focused = @{$con->{focus}};
+    my $lf;
+    while (@focused > 0) {
+        $lf = $focused[0];
+        last unless defined($con->{focus});
+        @focused = @{$con->{focus}};
+        my @cons = grep { $_->{id} == $lf } (@{$con->{nodes}}, @{$con->{'floating_nodes'}});
+        $con = $cons[0];
+    }
+
+    return $lf;
+}
+
+sub get_dock_clients {
+    my $which = shift;
+
+    my $tree = i3(get_socket_path())->get_tree->recv;
+    my @outputs = @{$tree->{nodes}};
+    # Children of all dockareas
+    my @docked;
+    for my $output (@outputs) {
+        if (!defined($which)) {
+            @docked = (@docked, map { @{$_->{nodes}} }
+                                grep { $_->{type} == 5 }
+                                @{$output->{nodes}});
+        } elsif ($which eq 'top') {
+            my $first = first { $_->{type} == 5 } @{$output->{nodes}};
+            @docked = (@docked, @{$first->{nodes}});
+        } elsif ($which eq 'bottom') {
+            my @matching = grep { $_->{type} == 5 } @{$output->{nodes}};
+            my $last = $matching[-1];
+            @docked = (@docked, @{$last->{nodes}});
+        }
+    }
+    return @docked;
+}
+
+sub cmd {
+    i3(get_socket_path())->command(@_)->recv
+}
+
+sub workspace_exists {
+    my ($name) = @_;
+    ($name ~~ @{get_workspace_names()})
+}
+
+sub focused_ws {
+    my $i3 = i3(get_socket_path());
+    my $tree = $i3->get_tree->recv;
+    my @outputs = @{$tree->{nodes}};
+    my @cons;
+    for my $output (@outputs) {
+        # get the first CT_CON of each output
+        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+        my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
+        return $first->{name}
+    }
+}
+
+#
+# Sends an I3_SYNC ClientMessage with a random value to the root window.
+# i3 will reply with the same value, but, due to the order of events it
+# processes, only after all other events are done.
+#
+# This can be used to ensure the results of a cmd 'focus left' are pushed to
+# X11 and that $x->input_focus returns the correct value afterwards.
+#
+# See also docs/testsuite for a long explanation
+#
+sub sync_with_i3 {
+    my ($x) = @_;
+
+    # Since we need a (mapped) window for receiving a ClientMessage, we create
+    # one on the first call of sync_with_i3. It will be re-used in all
+    # subsequent calls.
+    if (!defined($_sync_window)) {
+        $_sync_window = $x->root->create_child(
+            class => WINDOW_CLASS_INPUT_OUTPUT,
+            rect => X11::XCB::Rect->new(x => -15, y => -15, width => 10, height => 10 ),
+            override_redirect => 1,
+            background_color => '#ff0000',
+            event_mask => [ 'structure_notify' ],
+        );
+
+        $_sync_window->map;
+
+        wait_for_event $x, 2, sub { $_[0]->{response_type} == MAP_NOTIFY };
+    }
+
+    my $root = $x->get_root_window();
+    # Generate a random number to identify this particular ClientMessage.
+    my $myrnd = int(rand(255)) + 1;
+
+    # Generate a ClientMessage, see xcb_client_message_t
+    my $msg = pack "CCSLLLLLLL",
+         CLIENT_MESSAGE, # response_type
+         32,     # format
+         0,      # sequence
+         $root,  # destination window
+         $x->atom(name => 'I3_SYNC')->id,
+
+         $_sync_window->id,    # data[0]: our own window id
+         $myrnd, # data[1]: a random value to identify the request
+         0,
+         0,
+         0;
+
+    # Send it to the root window -- since i3 uses the SubstructureRedirect
+    # event mask, it will get the ClientMessage.
+    $x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+
+    # now wait until the reply is here
+    return wait_for_event $x, 2, sub {
+        my ($event) = @_;
+        # TODO: const
+        return 0 unless $event->{response_type} == 161;
+
+        my ($win, $rnd) = unpack "LL", $event->{data};
+        return ($rnd == $myrnd);
+    };
+}
+
+sub does_i3_live {
+    my $tree = i3(get_socket_path())->get_tree->recv;
+    my @nodes = @{$tree->{nodes}};
+    my $ok = (@nodes > 0);
+    $tester->ok($ok, 'i3 still lives');
+    return $ok;
+}
+
+# Tries to exit i3 gracefully (with the 'exit' cmd) or kills the PID if that fails
+sub exit_gracefully {
+    my ($pid, $socketpath) = @_;
+    $socketpath ||= get_socket_path();
+
+    my $exited = 0;
+    eval {
+        say "Exiting i3 cleanly...";
+        i3($socketpath)->command('exit')->recv;
+        $exited = 1;
+    };
+
+    if (!$exited) {
+        kill(9, $pid) or die "could not kill i3";
+    }
+
+    if ($socketpath =~ m,^/tmp/i3-test-socket-,) {
+        unlink($socketpath);
+    }
+}
+
+# Gets the socket path from the I3_SOCKET_PATH atom stored on the X11 root window
+sub get_socket_path {
+    my ($cache) = @_;
+    $cache ||= 1;
+
+    if ($cache && defined($_cached_socket_path)) {
+        return $_cached_socket_path;
+    }
+
+    my $x = X11::XCB::Connection->new;
+    my $atom = $x->atom(name => 'I3_SOCKET_PATH');
+    my $cookie = $x->get_property(0, $x->get_root_window(), $atom->id, GET_PROPERTY_TYPE_ANY, 0, 256);
+    my $reply = $x->get_property_reply($cookie->{sequence});
+    my $socketpath = $reply->{value};
+    $_cached_socket_path = $socketpath;
+    return $socketpath;
+}
+
+#
+# launches a new i3 process with the given string as configuration file.
+# useful for tests which test specific config file directives.
+#
+# be sure to use !NO_I3_INSTANCE! somewhere in the file to signal
+# complete-run.pl that it should not create an instance of i3
+#
+sub launch_with_config {
+    my ($config, $dont_add_socket_path) = @_;
+
+    $dont_add_socket_path //= 0;
+
+    if (!defined($tmp_socket_path)) {
+        $tmp_socket_path = File::Temp::tempnam('/tmp', 'i3-test-socket-');
+    }
+
+    my ($fh, $tmpfile) = tempfile('i3-test-config-XXXXX', UNLINK => 1);
+    say $fh $config;
+    say $fh "ipc-socket $tmp_socket_path" unless $dont_add_socket_path;
+    close($fh);
+
+    my $cv = AnyEvent->condvar;
+    my $pid = activate_i3(
+        unix_socket_path => "$tmp_socket_path-activation",
+        display => $ENV{DISPLAY},
+        configfile => $tmpfile,
+        outdir => $ENV{OUTDIR},
+        logpath => $ENV{LOGPATH},
+        valgrind => $ENV{VALGRIND},
+        cv => $cv,
+    );
+
+    # blockingly wait until i3 is ready
+    $cv->recv;
+
+    # force update of the cached socket path in lib/i3test
+    get_socket_path(0);
+
+    return $pid;
+}
+
+1
diff --git a/testcases/t/00-load.t b/testcases/t/00-load.t
deleted file mode 100644 (file)
index 5dfc5c6..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!perl
-
-use Test::More tests => 2;
-
-BEGIN {
-       use_ok( 'X11::XCB::Connection' );
-       use_ok( 'X11::XCB::Window' );
-}
-
-diag( "Testing i3, Perl $], $^X" );
diff --git a/testcases/t/000-load-deps.t b/testcases/t/000-load-deps.t
new file mode 100644 (file)
index 0000000..5dfc5c6
--- /dev/null
@@ -0,0 +1,10 @@
+#!perl
+
+use Test::More tests => 2;
+
+BEGIN {
+       use_ok( 'X11::XCB::Connection' );
+       use_ok( 'X11::XCB::Window' );
+}
+
+diag( "Testing i3, Perl $], $^X" );
diff --git a/testcases/t/001-tile.t b/testcases/t/001-tile.t
new file mode 100644 (file)
index 0000000..0db3b83
--- /dev/null
@@ -0,0 +1,32 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => $original_rect,
+    background_color => '#C0C0C0',
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
+
+$window->map;
+
+sleep(0.25);
+
+my $new_rect = $window->rect;
+ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
+
+done_testing;
diff --git a/testcases/t/002-i3-sync.t b/testcases/t/002-i3-sync.t
new file mode 100644 (file)
index 0000000..7518c94
--- /dev/null
@@ -0,0 +1,15 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# checks if i3 supports I3_SYNC
+#
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+my $result = sync_with_i3($x);
+ok($result, 'syncing was successful');
+
+done_testing;
diff --git a/testcases/t/003-ipc.t b/testcases/t/003-ipc.t
new file mode 100644 (file)
index 0000000..982ece7
--- /dev/null
@@ -0,0 +1,26 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+fresh_workspace;
+
+#####################################################################
+# Ensure IPC works by switching workspaces
+#####################################################################
+
+# Create a window so we can get a focus different from NULL
+my $window = open_window($x);
+
+my $focus = $x->input_focus;
+
+# Switch to another workspace
+fresh_workspace;
+
+sync_with_i3($x);
+my $new_focus = $x->input_focus;
+isnt($focus, $new_focus, "Focus changed");
+
+done_testing;
diff --git a/testcases/t/004-unmanaged.t b/testcases/t/004-unmanaged.t
new file mode 100644 (file)
index 0000000..1ef934e
--- /dev/null
@@ -0,0 +1,33 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => $original_rect,
+    override_redirect => 1,
+    background_color => '#C0C0C0',
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
+
+$window->map;
+
+my $new_rect = $window->rect;
+isa_ok($new_rect, 'X11::XCB::Rect');
+
+is_deeply($new_rect, $original_rect, "window untouched");
+
+done_testing;
diff --git a/testcases/t/005-floating.t b/testcases/t/005-floating.t
new file mode 100644 (file)
index 0000000..d605328
--- /dev/null
@@ -0,0 +1,93 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#C0C0C0',
+    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+    event_mask => [ 'structure_notify' ],
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+$window->map;
+
+wait_for_map $x;
+
+my ($absolute, $top) = $window->rect;
+
+ok($window->mapped, 'Window is mapped');
+cmp_ok($absolute->{width}, '>=', 75, 'i3 raised the width to 75');
+cmp_ok($absolute->{height}, '>=', 50, 'i3 raised the height to 50');
+
+ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
+
+$window->unmap;
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 1, 1, 80, 90],
+    background_color => '#C0C0C0',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+    event_mask => [ 'structure_notify' ],
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+$window->map;
+
+wait_for_map $x;
+
+($absolute, $top) = $window->rect;
+
+cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80");
+cmp_ok($absolute->{height}, '==', 90, "i3 let the height at 90");
+
+# We need to compare the position with decorations due to the way
+# we do decoration rendering (on the parent frame) in the tree branch
+cmp_ok($top->{x}, '==', 1, 'i3 mapped it to x=1');
+cmp_ok($top->{y}, '==', 19, 'i3 mapped it to y=18');
+
+$window->unmap;
+
+#####################################################################
+# check that a tiling window which is then made floating still has
+# at least the size of its initial geometry
+#####################################################################
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 1, 1, 80, 90],
+    background_color => '#C0C0C0',
+    #window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+    event_mask => [ 'structure_notify' ],
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+$window->map;
+
+wait_for_map $x;
+
+cmd 'floating enable';
+
+($absolute, $top) = $window->rect;
+
+cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80");
+cmp_ok($absolute->{height}, '==', 90, "i3 let the height at 90");
+
+$window->unmap;
+
+done_testing;
diff --git a/testcases/t/01-tile.t b/testcases/t/01-tile.t
deleted file mode 100644 (file)
index 0db3b83..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => $original_rect,
-    background_color => '#C0C0C0',
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
-
-$window->map;
-
-sleep(0.25);
-
-my $new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
-
-done_testing;
diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t
deleted file mode 100644 (file)
index 34e5364..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-use List::Util qw(first);
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-sub fullscreen_windows {
-    scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($tmp)}
-}
-
-# get the output of this workspace
-my $tree = $i3->get_tree->recv;
-my @outputs = @{$tree->{nodes}};
-my $output;
-for my $o (@outputs) {
-    # get the first CT_CON of each output
-    my $content = first { $_->{type} == 2 } @{$o->{nodes}};
-    if (defined(first { $_->{name} eq $tmp } @{$content->{nodes}})) {
-        $output = $o;
-        last;
-    }
-}
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-##################################
-# map a window, then fullscreen it
-##################################
-
-my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => $original_rect,
-    background_color => '#C0C0C0',
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
-
-$window->map;
-
-sleep 0.25;
-
-# open another container to make the window get only half of the screen
-cmd 'open';
-
-my $new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
-$original_rect = $new_rect;
-
-sleep 0.25;
-
-$window->fullscreen(1);
-
-sleep 0.25;
-
-$new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
-
-my $orect = $output->{rect};
-my $wrect = $new_rect;
-
-# see if the window really is fullscreen. 20 px for borders are allowed
-my $threshold = 20;
-ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen');
-ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen');
-ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen');
-ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen');
-
-
-$window->unmap;
-
-#########################################################
-# test with a window which is fullscreened before mapping
-#########################################################
-
-# open another container because the empty one will swallow the window we
-# map in a second
-cmd 'open';
-
-$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => $original_rect,
-    background_color => 61440,
-);
-
-is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
-
-$window->fullscreen(1);
-$window->map;
-
-sleep(0.25);
-
-$new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
-ok($window->mapped, "Window is mapped after opening it in fullscreen mode");
-
-$wrect = $new_rect;
-
-# see if the window really is fullscreen. 20 px for borders are allowed
-ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen');
-ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen');
-ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen');
-ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen');
-
-###############################################################################
-# test if setting two windows in fullscreen mode at the same time does not work
-###############################################################################
-
-$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-my $swindow = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => $original_rect,
-    background_color => '#C0C0C0',
-);
-
-$swindow->map;
-sleep 0.25;
-
-ok(!$swindow->mapped, 'window not mapped while fullscreen window active');
-
-$new_rect = $swindow->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
-
-$swindow->fullscreen(1);
-sleep 0.25;
-
-is(fullscreen_windows(), 1, 'amount of fullscreen windows');
-
-$window->fullscreen(0);
-sleep 0.25;
-is(fullscreen_windows(), 0, 'amount of fullscreen windows');
-
-ok($swindow->mapped, 'window mapped after other fullscreen ended');
-
-###########################################################################
-# as $swindow is out of state at the moment (it requested to be fullscreen,
-# but the WM denied), we check what happens if we go out of fullscreen now
-# (nothing should happen)
-###########################################################################
-
-$swindow->fullscreen(0);
-sleep 0.25;
-
-is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling');
-
-cmd 'fullscreen';
-
-is(fullscreen_windows(), 1, 'amount of fullscreen windows after fullscreen command');
-
-cmd 'fullscreen';
-
-is(fullscreen_windows(), 0, 'amount of fullscreen windows after fullscreen command');
-
-# clean up the workspace so that it will be cleaned when switching away
-cmd 'kill' for (@{get_ws_content($tmp)});
-
-done_testing;
diff --git a/testcases/t/03-unmanaged.t b/testcases/t/03-unmanaged.t
deleted file mode 100644 (file)
index 1ef934e..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => $original_rect,
-    override_redirect => 1,
-    background_color => '#C0C0C0',
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
-
-$window->map;
-
-my $new_rect = $window->rect;
-isa_ok($new_rect, 'X11::XCB::Rect');
-
-is_deeply($new_rect, $original_rect, "window untouched");
-
-done_testing;
diff --git a/testcases/t/04-floating.t b/testcases/t/04-floating.t
deleted file mode 100644 (file)
index fcf73f0..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#C0C0C0',
-    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-my ($absolute, $top) = $window->rect;
-
-ok($window->mapped, 'Window is mapped');
-cmp_ok($absolute->{width}, '>=', 75, 'i3 raised the width to 75');
-cmp_ok($absolute->{height}, '>=', 50, 'i3 raised the height to 50');
-
-ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
-
-$window->unmap;
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 1, 1, 80, 90],
-    background_color => '#C0C0C0',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-($absolute, $top) = $window->rect;
-
-cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80");
-cmp_ok($absolute->{height}, '==', 90, "i3 let the height at 90");
-
-# We need to compare the position with decorations due to the way
-# we do decoration rendering (on the parent frame) in the tree branch
-cmp_ok($top->{x}, '==', 1, 'i3 mapped it to x=1');
-cmp_ok($top->{y}, '==', 19, 'i3 mapped it to y=18');
-
-$window->unmap;
-
-#####################################################################
-# check that a tiling window which is then made floating still has
-# at least the size of its initial geometry
-#####################################################################
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 1, 1, 80, 90],
-    background_color => '#C0C0C0',
-    #window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-cmd 'floating enable';
-
-($absolute, $top) = $window->rect;
-
-cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80");
-cmp_ok($absolute->{height}, '==', 90, "i3 let the height at 90");
-
-$window->unmap;
-
-done_testing;
diff --git a/testcases/t/05-ipc.t b/testcases/t/05-ipc.t
deleted file mode 100644 (file)
index a910c93..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-
-fresh_workspace;
-
-#####################################################################
-# Ensure IPC works by switching workspaces
-#####################################################################
-
-# Create a window so we can get a focus different from NULL
-my $window = open_standard_window($x);
-diag("window->id = " . $window->id);
-
-sleep 0.25;
-
-my $focus = $x->input_focus;
-diag("old focus = $focus");
-
-# Switch to another workspace
-fresh_workspace;
-
-my $new_focus = $x->input_focus;
-isnt($focus, $new_focus, "Focus changed");
-
-done_testing;
diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t
deleted file mode 100644 (file)
index d357c8a..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $i3 = i3(get_socket_path());
-my $tmp = fresh_workspace;
-
-#####################################################################
-# Create two windows and make sure focus switching works
-#####################################################################
-
-# Change mode of the container to "default" for following tests
-cmd 'layout default';
-cmd 'split v';
-
-my $top = open_standard_window($x);
-my $mid = open_standard_window($x);
-my $bottom = open_standard_window($x);
-sleep 0.25;
-
-diag("top id = " . $top->id);
-diag("mid id = " . $mid->id);
-diag("bottom id = " . $bottom->id);
-
-#
-# Returns the input focus after sending the given command to i3 via IPC
-# end sleeping for half a second to make sure i3 reacted
-#
-sub focus_after {
-    my $msg = shift;
-
-    $i3->command($msg)->recv;
-    return $x->input_focus;
-}
-
-$focus = $x->input_focus;
-is($focus, $bottom->id, "Latest window focused");
-
-$focus = focus_after('focus up');
-is($focus, $mid->id, "Middle window focused");
-
-$focus = focus_after('focus up');
-is($focus, $top->id, "Top window focused");
-
-#####################################################################
-# Test focus wrapping
-#####################################################################
-
-$focus = focus_after('focus up');
-is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
-
-$focus = focus_after('focus down');
-is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
-
-###############################################
-# Test focus with empty containers and colspan
-###############################################
-
-#my $otmp = get_unused_workspace();
-#$i3->command("workspace $otmp")->recv;
-#
-#$top = i3test::open_standard_window($x);
-#$bottom = i3test::open_standard_window($x);
-#sleep 0.25;
-#
-#$focus = focus_after("mj");
-#$focus = focus_after("mh");
-#$focus = focus_after("k");
-#is($focus, $bottom->id, "Selecting top window without snapping doesn't work");
-#
-#$focus = focus_after("sl");
-#is($focus, $bottom->id, "Bottom window focused");
-#
-#$focus = focus_after("k");
-#is($focus, $top->id, "Top window focused");
-#
-## Same thing, but left/right instead of top/bottom
-#
-#my $o2tmp = get_unused_workspace();
-#$i3->command("workspace $o2tmp")->recv;
-#
-#my $left = i3test::open_standard_window($x);
-#my $right = i3test::open_standard_window($x);
-#sleep 0.25;
-#
-#$focus = focus_after("ml");
-#$focus = focus_after("h");
-#$focus = focus_after("mk");
-#$focus = focus_after("l");
-#is($focus, $left->id, "Selecting right window without snapping doesn't work");
-#
-#$focus = focus_after("sj");
-#is($focus, $left->id, "left window focused");
-#
-#$focus = focus_after("l");
-#is($focus, $right->id, "right window focused");
-
-
-done_testing;
diff --git a/testcases/t/07-move.t b/testcases/t/07-move.t
deleted file mode 100644 (file)
index 6e35ebe..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Beware that this test uses workspace 9 to perform some tests (it expects
-# the workspace to be empty).
-# TODO: skip it by default?
-
-use i3test tests => 8;
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
-
-BEGIN {
-    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-SKIP: {
-    skip "Testcase not yet modified for new move concept", 7;
-
-my $x = X11::XCB::Connection->new;
-
-my $i3 = i3;
-
-# Switch to the nineth workspace
-$i3->command('9')->recv;
-
-#####################################################################
-# Create two windows and make sure focus switching works
-#####################################################################
-
-my $top = i3test::open_standard_window($x);
-sleep(0.25);
-my $mid = i3test::open_standard_window($x);
-sleep(0.25);
-my $bottom = i3test::open_standard_window($x);
-sleep(0.25);
-
-diag("top id = " . $top->id);
-diag("mid id = " . $mid->id);
-diag("bottom id = " . $bottom->id);
-
-#
-# Returns the input focus after sending the given command to i3 via IPC
-# end sleeping for half a second to make sure i3 reacted
-#
-sub focus_after {
-    my $msg = shift;
-
-    $i3->command($msg)->recv;
-    return $x->input_focus;
-}
-
-$focus = $x->input_focus;
-is($focus, $bottom->id, "Latest window focused");
-
-$focus = focus_after("ml");
-is($focus, $bottom->id, "Right window still focused");
-
-$focus = focus_after("h");
-is($focus, $mid->id, "Middle window focused");
-
-#####################################################################
-# Now move to the top window, move right, then move left again
-# (e.g., does i3 remember the focus in the last container?)
-#####################################################################
-
-$focus = focus_after("k");
-is($focus, $top->id, "Top window focused");
-
-$focus = focus_after("l");
-is($focus, $bottom->id, "Right window focused");
-
-$focus = focus_after("h");
-is($focus, $top->id, "Top window focused");
-
-#####################################################################
-# Move window cross-workspace
-#####################################################################
-
-for my $cmd (qw(m12 t m13 12 13)) {
-    $i3->command($cmd)->recv;
-}
-ok(1, "Still living");
-}
diff --git a/testcases/t/08-focus-stack.t b/testcases/t/08-focus-stack.t
deleted file mode 100644 (file)
index f814397..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Checks if the focus is correctly restored, when creating a floating client
-# over an unfocused tiling client and destroying the floating one again.
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $i3 = i3(get_socket_path());
-fresh_workspace;
-
-cmd 'split h';
-my $tiled_left = open_standard_window($x);
-my $tiled_right = open_standard_window($x);
-
-sleep 0.25;
-
-# Get input focus before creating the floating window
-my $focus = $x->input_focus;
-
-# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 1, 1, 30, 30],
-    background_color => '#C0C0C0',
-    type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 1;
-sleep 0.25;
-is($x->input_focus, $window->id, 'floating window focused');
-
-$window->unmap;
-
-sleep 0.25;
-
-is($x->input_focus, $focus, 'Focus correctly restored');
-
-done_testing;
diff --git a/testcases/t/09-stacking.t b/testcases/t/09-stacking.t
deleted file mode 100644 (file)
index 1cb205e..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Beware that this test uses workspace 9 to perform some tests (it expects
-# the workspace to be empty).
-# TODO: skip it by default?
-
-use i3test tests => 22;
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
-
-BEGIN {
-    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-SKIP: {
-    skip "stacking test not yet updated", 21;
-
-my $x = X11::XCB::Connection->new;
-
-my $i3 = i3;
-
-# Switch to the nineth workspace
-$i3->command('9')->recv;
-
-#####################################################################
-# Create two windows and make sure focus switching works
-#####################################################################
-
-my $top = i3test::open_standard_window($x);
-sleep(0.25);
-my $mid = i3test::open_standard_window($x);
-sleep(0.25);
-my $bottom = i3test::open_standard_window($x);
-sleep(0.25);
-
-diag("top id = " . $top->id);
-diag("mid id = " . $mid->id);
-diag("bottom id = " . $bottom->id);
-
-#
-# Returns the input focus after sending the given command to i3 via IPC
-# end sleeping for half a second to make sure i3 reacted
-#
-sub focus_after {
-    my $msg = shift;
-
-    $i3->command($msg)->recv;
-    return $x->input_focus;
-}
-
-$focus = $x->input_focus;
-is($focus, $bottom->id, "Latest window focused");
-
-$focus = focus_after("s");
-is($focus, $bottom->id, "Last window still focused");
-
-$focus = focus_after("k");
-is($focus, $mid->id, "Middle window focused");
-
-$focus = focus_after("k");
-is($focus, $top->id, "Top window focused");
-
-#####################################################################
-# Test focus wrapping
-#####################################################################
-
-$focus = focus_after("k");
-is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
-
-$focus = focus_after("j");
-is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
-
-#####################################################################
-# Restore of focus after moving windows out/into the stack
-#####################################################################
-
-$focus = focus_after("ml");
-is($focus, $top->id, "Top window still focused (focus after moving)");
-
-$focus = focus_after("h");
-is($focus, $bottom->id, "Bottom window focused (focus after moving)");
-
-my $new = i3test::open_standard_window($x);
-sleep(0.25);
-
-# By now, we have this layout:
-# ----------------
-# | mid    |
-# | bottom | top
-# | new    |
-# ----------------
-
-$focus = focus_after("l");
-is($focus, $top->id, "Got top window");
-
-$focus = focus_after("mh");
-is($focus, $top->id, "Moved it into the stack");
-
-$focus = focus_after("k");
-is($focus, $new->id, "Window above is new");
-
-$focus = focus_after("k");
-is($focus, $bottom->id, "Window above is bottom");
-
-$focus = focus_after("k");
-is($focus, $mid->id, "Window above is mid");
-
-$focus = focus_after("k");
-is($focus, $top->id, "At top again");
-
-$focus = focus_after("ml");
-is($focus, $top->id, "Still at top, moved out");
-
-$focus = focus_after("h");
-is($focus, $mid->id, "At mid again");
-
-$focus = focus_after("j");
-is($focus, $bottom->id, "At bottom again");
-
-$focus = focus_after("l");
-is($focus, $top->id, "At top again");
-
-$focus = focus_after("mh");
-is($focus, $top->id, "Still at top, moved into");
-
-$focus = focus_after("k");
-is($focus, $bottom->id, "Window above is bottom");
-
-$focus = focus_after("k");
-is($focus, $mid->id, "Window above is mid");
-
-}
diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t
deleted file mode 100644 (file)
index 3f0a519..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-use List::Util qw(first);
-
-BEGIN {
-    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-#####################################################################
-# verify that there is no dock window yet
-#####################################################################
-
-# Children of all dockareas
-my @docked = get_dock_clients;
-is(@docked, 0, 'no dock clients yet');
-
-#####################################################################
-# Create a dock window and see if it gets managed
-#####################################################################
-
-my $screens = $x->screens;
-
-# Get the primary screen
-my $primary = first { $_->primary } @{$screens};
-
-# TODO: focus the primary screen before
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#FF0000',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
-
-my $rect = $window->rect;
-is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
-is($rect->height, 30, 'height is unchanged');
-
-#####################################################################
-# check that we can find it in the layout tree at the expected position
-#####################################################################
-
-@docked = get_dock_clients('top');
-is(@docked, 1, 'one dock client found');
-
-# verify the position/size
-my $docknode = $docked[0];
-
-is($docknode->{rect}->{x}, 0, 'dock node placed at x=0');
-is($docknode->{rect}->{y}, 0, 'dock node placed at y=0');
-is($docknode->{rect}->{width}, $primary->rect->width, 'dock node as wide as the screen');
-is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
-
-#####################################################################
-# check that re-configuring the height works
-#####################################################################
-
-$window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 50, height => 40));
-
-sleep 0.25;
-
-@docked = get_dock_clients('top');
-is(@docked, 1, 'one dock client found');
-
-# verify the position/size
-$docknode = $docked[0];
-
-is($docknode->{rect}->{x}, 0, 'dock node placed at x=0');
-is($docknode->{rect}->{y}, 0, 'dock node placed at y=0');
-is($docknode->{rect}->{width}, $primary->rect->width, 'dock node as wide as the screen');
-is($docknode->{rect}->{height}, 40, 'dock height changed');
-
-$window->destroy;
-
-sleep 0.25;
-
-@docked = get_dock_clients();
-is(@docked, 0, 'no more dock clients');
-
-#####################################################################
-# check if it gets placed on bottom (by coordinates)
-#####################################################################
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 1000, 30, 30],
-    background_color => '#FF0000',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
-
-my $rect = $window->rect;
-is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
-is($rect->height, 30, 'height is unchanged');
-
-@docked = get_dock_clients('bottom');
-is(@docked, 1, 'dock client on bottom');
-
-$window->destroy;
-
-sleep 0.25;
-
-@docked = get_dock_clients();
-is(@docked, 0, 'no more dock clients');
-
-#####################################################################
-# check if it gets placed on bottom (by hint)
-#####################################################################
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 1000, 30, 30],
-    background_color => '#FF0000',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->_create();
-
-# Add a _NET_WM_STRUT_PARTIAL hint
-my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
-my $atomtype = $x->atom(name => 'CARDINAL');
-
-$x->change_property(
-    PROP_MODE_REPLACE,
-    $window->id,
-    $atomname->id,
-    $atomtype->id,
-    32,         # 32 bit integer
-    12,
-    pack('L12', 0, 0, 16, 0, 0, 0, 0, 0, 0, 1280, 0, 0)
-);
-
-$window->map;
-
-sleep 0.25;
-
-@docked = get_dock_clients('top');
-is(@docked, 1, 'dock client on top');
-
-$window->destroy;
-
-sleep 0.25;
-
-@docked = get_dock_clients();
-is(@docked, 0, 'no more dock clients');
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 1000, 30, 30],
-    background_color => '#FF0000',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->_create();
-
-# Add a _NET_WM_STRUT_PARTIAL hint
-my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
-my $atomtype = $x->atom(name => 'CARDINAL');
-
-$x->change_property(
-    PROP_MODE_REPLACE,
-    $window->id,
-    $atomname->id,
-    $atomtype->id,
-    32,         # 32 bit integer
-    12,
-    pack('L12', 0, 0, 0, 16, 0, 0, 0, 0, 0, 1280, 0, 0)
-);
-
-$window->map;
-
-sleep 0.25;
-
-@docked = get_dock_clients('bottom');
-is(@docked, 1, 'dock client on bottom');
-
-$window->destroy;
-
-
-#####################################################################
-# regression test: transient dock client
-#####################################################################
-
-my $fwindow = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#FF0000',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$fwindow->transient_for($window);
-$fwindow->map;
-
-sleep 0.25;
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/100-fullscreen.t b/testcases/t/100-fullscreen.t
new file mode 100644 (file)
index 0000000..ae8c63f
--- /dev/null
@@ -0,0 +1,172 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+use List::Util qw(first);
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+sub fullscreen_windows {
+    scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($tmp)}
+}
+
+# get the output of this workspace
+my $tree = $i3->get_tree->recv;
+my @outputs = @{$tree->{nodes}};
+my $output;
+for my $o (@outputs) {
+    # get the first CT_CON of each output
+    my $content = first { $_->{type} == 2 } @{$o->{nodes}};
+    if (defined(first { $_->{name} eq $tmp } @{$content->{nodes}})) {
+        $output = $o;
+        last;
+    }
+}
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+##################################
+# map a window, then fullscreen it
+##################################
+
+my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => $original_rect,
+    background_color => '#C0C0C0',
+    event_mask => [ 'structure_notify' ],
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
+
+$window->map;
+
+wait_for_map $x;
+
+# open another container to make the window get only half of the screen
+cmd 'open';
+
+my $new_rect = $window->rect;
+ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
+$original_rect = $new_rect;
+
+$window->fullscreen(1);
+
+sync_with_i3($x);
+
+$new_rect = $window->rect;
+ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
+
+my $orect = $output->{rect};
+my $wrect = $new_rect;
+
+# see if the window really is fullscreen. 20 px for borders are allowed
+my $threshold = 20;
+ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen');
+ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen');
+ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen');
+ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen');
+
+
+$window->unmap;
+
+#########################################################
+# test with a window which is fullscreened before mapping
+#########################################################
+
+# open another container because the empty one will swallow the window we
+# map in a second
+cmd 'open';
+
+$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => $original_rect,
+    background_color => 61440,
+    event_mask => [ 'structure_notify' ],
+);
+
+is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
+
+$window->fullscreen(1);
+$window->map;
+
+wait_for_map $x;
+
+$new_rect = $window->rect;
+ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
+ok($window->mapped, "Window is mapped after opening it in fullscreen mode");
+
+$wrect = $new_rect;
+
+# see if the window really is fullscreen. 20 px for borders are allowed
+ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen');
+ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen');
+ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen');
+ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen');
+
+###############################################################################
+# test if setting two windows in fullscreen mode at the same time does not work
+###############################################################################
+
+$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
+my $swindow = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => $original_rect,
+    background_color => '#C0C0C0',
+    event_mask => [ 'structure_notify' ],
+);
+
+$swindow->map;
+
+sync_with_i3($x);
+
+ok(!$swindow->mapped, 'window not mapped while fullscreen window active');
+
+$new_rect = $swindow->rect;
+ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
+
+$swindow->fullscreen(1);
+sync_with_i3($x);
+
+is(fullscreen_windows(), 1, 'amount of fullscreen windows');
+
+$window->fullscreen(0);
+sync_with_i3($x);
+is(fullscreen_windows(), 0, 'amount of fullscreen windows');
+
+ok($swindow->mapped, 'window mapped after other fullscreen ended');
+
+###########################################################################
+# as $swindow is out of state at the moment (it requested to be fullscreen,
+# but the WM denied), we check what happens if we go out of fullscreen now
+# (nothing should happen)
+###########################################################################
+
+$swindow->fullscreen(0);
+sync_with_i3($x);
+
+is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling');
+
+cmd 'fullscreen';
+
+is(fullscreen_windows(), 1, 'amount of fullscreen windows after fullscreen command');
+
+cmd 'fullscreen';
+
+is(fullscreen_windows(), 0, 'amount of fullscreen windows after fullscreen command');
+
+# clean up the workspace so that it will be cleaned when switching away
+cmd 'kill' for (@{get_ws_content($tmp)});
+
+done_testing;
diff --git a/testcases/t/101-focus.t b/testcases/t/101-focus.t
new file mode 100644 (file)
index 0000000..5ded494
--- /dev/null
@@ -0,0 +1,97 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# Create two windows and make sure focus switching works
+#####################################################################
+
+# Change mode of the container to "default" for following tests
+cmd 'layout default';
+cmd 'split v';
+
+my $top = open_window($x);
+my $mid = open_window($x);
+my $bottom = open_window($x);
+
+#
+# Returns the input focus after sending the given command to i3 via IPC
+# end sleeping for half a second to make sure i3 reacted
+#
+sub focus_after {
+    my $msg = shift;
+
+    cmd $msg;
+    sync_with_i3 $x;
+    return $x->input_focus;
+}
+
+$focus = $x->input_focus;
+is($focus, $bottom->id, "Latest window focused");
+
+$focus = focus_after('focus up');
+is($focus, $mid->id, "Middle window focused");
+
+$focus = focus_after('focus up');
+is($focus, $top->id, "Top window focused");
+
+#####################################################################
+# Test focus wrapping
+#####################################################################
+
+$focus = focus_after('focus up');
+is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
+
+$focus = focus_after('focus down');
+is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
+
+###############################################
+# Test focus with empty containers and colspan
+###############################################
+
+#my $otmp = get_unused_workspace();
+#$i3->command("workspace $otmp")->recv;
+#
+#$top = i3test::open_standard_window($x);
+#$bottom = i3test::open_standard_window($x);
+#sleep 0.25;
+#
+#$focus = focus_after("mj");
+#$focus = focus_after("mh");
+#$focus = focus_after("k");
+#is($focus, $bottom->id, "Selecting top window without snapping doesn't work");
+#
+#$focus = focus_after("sl");
+#is($focus, $bottom->id, "Bottom window focused");
+#
+#$focus = focus_after("k");
+#is($focus, $top->id, "Top window focused");
+#
+## Same thing, but left/right instead of top/bottom
+#
+#my $o2tmp = get_unused_workspace();
+#$i3->command("workspace $o2tmp")->recv;
+#
+#my $left = i3test::open_standard_window($x);
+#my $right = i3test::open_standard_window($x);
+#sleep 0.25;
+#
+#$focus = focus_after("ml");
+#$focus = focus_after("h");
+#$focus = focus_after("mk");
+#$focus = focus_after("l");
+#is($focus, $left->id, "Selecting right window without snapping doesn't work");
+#
+#$focus = focus_after("sj");
+#is($focus, $left->id, "left window focused");
+#
+#$focus = focus_after("l");
+#is($focus, $right->id, "right window focused");
+
+
+done_testing;
diff --git a/testcases/t/102-dock.t b/testcases/t/102-dock.t
new file mode 100644 (file)
index 0000000..cad54c2
--- /dev/null
@@ -0,0 +1,196 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+use List::Util qw(first);
+
+BEGIN {
+    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+my $x = X11::XCB::Connection->new;
+
+#####################################################################
+# verify that there is no dock window yet
+#####################################################################
+
+# Children of all dockareas
+my @docked = get_dock_clients;
+is(@docked, 0, 'no dock clients yet');
+
+#####################################################################
+# Create a dock window and see if it gets managed
+#####################################################################
+
+my $screens = $x->screens;
+
+# Get the primary screen
+my $primary = first { $_->primary } @{$screens};
+
+# TODO: focus the primary screen before
+my $window = open_window($x, {
+        window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+    });
+
+my $rect = $window->rect;
+is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
+is($rect->height, 30, 'height is unchanged');
+
+#####################################################################
+# check that we can find it in the layout tree at the expected position
+#####################################################################
+
+@docked = get_dock_clients('top');
+is(@docked, 1, 'one dock client found');
+
+# verify the position/size
+my $docknode = $docked[0];
+
+is($docknode->{rect}->{x}, 0, 'dock node placed at x=0');
+is($docknode->{rect}->{y}, 0, 'dock node placed at y=0');
+is($docknode->{rect}->{width}, $primary->rect->width, 'dock node as wide as the screen');
+is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
+
+#####################################################################
+# check that re-configuring the height works
+#####################################################################
+
+$window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 50, height => 40));
+
+sync_with_i3 $x;
+
+@docked = get_dock_clients('top');
+is(@docked, 1, 'one dock client found');
+
+# verify the position/size
+$docknode = $docked[0];
+
+is($docknode->{rect}->{x}, 0, 'dock node placed at x=0');
+is($docknode->{rect}->{y}, 0, 'dock node placed at y=0');
+is($docknode->{rect}->{width}, $primary->rect->width, 'dock node as wide as the screen');
+is($docknode->{rect}->{height}, 40, 'dock height changed');
+
+$window->destroy;
+
+wait_for_unmap $x;
+
+@docked = get_dock_clients();
+is(@docked, 0, 'no more dock clients');
+
+#####################################################################
+# check if it gets placed on bottom (by coordinates)
+#####################################################################
+
+$window = open_window($x, {
+        rect => [ 0, 1000, 30, 30 ],
+        background_color => '#FF0000',
+        window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+    });
+
+my $rect = $window->rect;
+is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
+is($rect->height, 30, 'height is unchanged');
+
+@docked = get_dock_clients('bottom');
+is(@docked, 1, 'dock client on bottom');
+
+$window->destroy;
+
+wait_for_unmap $x;
+
+@docked = get_dock_clients();
+is(@docked, 0, 'no more dock clients');
+
+#####################################################################
+# check if it gets placed on bottom (by hint)
+#####################################################################
+
+$window = open_window($x, {
+        dont_map => 1,
+        rect => [ 0, 1000, 30, 30 ],
+        background_color => '#FF0000',
+        window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+    });
+
+$window->_create();
+
+# Add a _NET_WM_STRUT_PARTIAL hint
+my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
+my $atomtype = $x->atom(name => 'CARDINAL');
+
+$x->change_property(
+    PROP_MODE_REPLACE,
+    $window->id,
+    $atomname->id,
+    $atomtype->id,
+    32,         # 32 bit integer
+    12,
+    pack('L12', 0, 0, 16, 0, 0, 0, 0, 0, 0, 1280, 0, 0)
+);
+
+$window->map;
+
+wait_for_map $x;
+
+@docked = get_dock_clients('top');
+is(@docked, 1, 'dock client on top');
+
+$window->destroy;
+
+wait_for_unmap $x;
+
+@docked = get_dock_clients();
+is(@docked, 0, 'no more dock clients');
+
+$window = open_window($x, {
+        dont_map => 1,
+        rect => [ 0, 1000, 30, 30 ],
+        background_color => '#FF0000',
+        window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+    });
+
+$window->_create();
+
+# Add a _NET_WM_STRUT_PARTIAL hint
+my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
+my $atomtype = $x->atom(name => 'CARDINAL');
+
+$x->change_property(
+    PROP_MODE_REPLACE,
+    $window->id,
+    $atomname->id,
+    $atomtype->id,
+    32,         # 32 bit integer
+    12,
+    pack('L12', 0, 0, 0, 16, 0, 0, 0, 0, 0, 1280, 0, 0)
+);
+
+$window->map;
+
+wait_for_map $x;
+
+@docked = get_dock_clients('bottom');
+is(@docked, 1, 'dock client on bottom');
+
+$window->destroy;
+
+
+#####################################################################
+# regression test: transient dock client
+#####################################################################
+
+$fwindow = open_window($x, {
+        dont_map => 1,
+        background_color => '#FF0000',
+        window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+    });
+
+$fwindow->transient_for($window);
+$fwindow->map;
+
+wait_for_map $x;
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/103-move.t b/testcases/t/103-move.t
new file mode 100644 (file)
index 0000000..6e35ebe
--- /dev/null
@@ -0,0 +1,82 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Beware that this test uses workspace 9 to perform some tests (it expects
+# the workspace to be empty).
+# TODO: skip it by default?
+
+use i3test tests => 8;
+use X11::XCB qw(:all);
+use Time::HiRes qw(sleep);
+
+BEGIN {
+    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+SKIP: {
+    skip "Testcase not yet modified for new move concept", 7;
+
+my $x = X11::XCB::Connection->new;
+
+my $i3 = i3;
+
+# Switch to the nineth workspace
+$i3->command('9')->recv;
+
+#####################################################################
+# Create two windows and make sure focus switching works
+#####################################################################
+
+my $top = i3test::open_standard_window($x);
+sleep(0.25);
+my $mid = i3test::open_standard_window($x);
+sleep(0.25);
+my $bottom = i3test::open_standard_window($x);
+sleep(0.25);
+
+diag("top id = " . $top->id);
+diag("mid id = " . $mid->id);
+diag("bottom id = " . $bottom->id);
+
+#
+# Returns the input focus after sending the given command to i3 via IPC
+# end sleeping for half a second to make sure i3 reacted
+#
+sub focus_after {
+    my $msg = shift;
+
+    $i3->command($msg)->recv;
+    return $x->input_focus;
+}
+
+$focus = $x->input_focus;
+is($focus, $bottom->id, "Latest window focused");
+
+$focus = focus_after("ml");
+is($focus, $bottom->id, "Right window still focused");
+
+$focus = focus_after("h");
+is($focus, $mid->id, "Middle window focused");
+
+#####################################################################
+# Now move to the top window, move right, then move left again
+# (e.g., does i3 remember the focus in the last container?)
+#####################################################################
+
+$focus = focus_after("k");
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after("l");
+is($focus, $bottom->id, "Right window focused");
+
+$focus = focus_after("h");
+is($focus, $top->id, "Top window focused");
+
+#####################################################################
+# Move window cross-workspace
+#####################################################################
+
+for my $cmd (qw(m12 t m13 12 13)) {
+    $i3->command($cmd)->recv;
+}
+ok(1, "Still living");
+}
diff --git a/testcases/t/104-focus-stack.t b/testcases/t/104-focus-stack.t
new file mode 100644 (file)
index 0000000..b5be284
--- /dev/null
@@ -0,0 +1,30 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Checks if the focus is correctly restored, when creating a floating client
+# over an unfocused tiling client and destroying the floating one again.
+
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+fresh_workspace;
+
+cmd 'split h';
+my $tiled_left = open_window($x);
+my $tiled_right = open_window($x);
+
+# Get input focus before creating the floating window
+my $focus = $x->input_focus;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = open_floating_window($x);
+
+is($x->input_focus, $window->id, 'floating window focused');
+
+$window->unmap;
+
+wait_for_unmap($x);
+
+is($x->input_focus, $focus, 'Focus correctly restored');
+
+done_testing;
diff --git a/testcases/t/105-stacking.t b/testcases/t/105-stacking.t
new file mode 100644 (file)
index 0000000..cc285f3
--- /dev/null
@@ -0,0 +1,130 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Beware that this test uses workspace 9 to perform some tests (it expects
+# the workspace to be empty).
+# TODO: skip it by default?
+
+use i3test tests => 22;
+use X11::XCB qw(:all);
+use Time::HiRes qw(sleep);
+
+BEGIN {
+    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+SKIP: {
+    skip "stacking test not yet updated", 21;
+
+my $x = X11::XCB::Connection->new;
+
+my $i3 = i3;
+
+# Switch to the nineth workspace
+$i3->command('9')->recv;
+
+#####################################################################
+# Create two windows and make sure focus switching works
+#####################################################################
+
+my $top = i3test::open_standard_window($x);
+my $mid = i3test::open_standard_window($x);
+my $bottom = i3test::open_standard_window($x);
+sleep(0.25);
+
+diag("top id = " . $top->id);
+diag("mid id = " . $mid->id);
+diag("bottom id = " . $bottom->id);
+
+#
+# Returns the input focus after sending the given command to i3 via IPC
+# end sleeping for half a second to make sure i3 reacted
+#
+sub focus_after {
+    my $msg = shift;
+
+    $i3->command($msg)->recv;
+    return $x->input_focus;
+}
+
+$focus = $x->input_focus;
+is($focus, $bottom->id, "Latest window focused");
+
+$focus = focus_after("s");
+is($focus, $bottom->id, "Last window still focused");
+
+$focus = focus_after("k");
+is($focus, $mid->id, "Middle window focused");
+
+$focus = focus_after("k");
+is($focus, $top->id, "Top window focused");
+
+#####################################################################
+# Test focus wrapping
+#####################################################################
+
+$focus = focus_after("k");
+is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
+
+$focus = focus_after("j");
+is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
+
+#####################################################################
+# Restore of focus after moving windows out/into the stack
+#####################################################################
+
+$focus = focus_after("ml");
+is($focus, $top->id, "Top window still focused (focus after moving)");
+
+$focus = focus_after("h");
+is($focus, $bottom->id, "Bottom window focused (focus after moving)");
+
+my $new = i3test::open_standard_window($x);
+sleep(0.25);
+
+# By now, we have this layout:
+# ----------------
+# | mid    |
+# | bottom | top
+# | new    |
+# ----------------
+
+$focus = focus_after("l");
+is($focus, $top->id, "Got top window");
+
+$focus = focus_after("mh");
+is($focus, $top->id, "Moved it into the stack");
+
+$focus = focus_after("k");
+is($focus, $new->id, "Window above is new");
+
+$focus = focus_after("k");
+is($focus, $bottom->id, "Window above is bottom");
+
+$focus = focus_after("k");
+is($focus, $mid->id, "Window above is mid");
+
+$focus = focus_after("k");
+is($focus, $top->id, "At top again");
+
+$focus = focus_after("ml");
+is($focus, $top->id, "Still at top, moved out");
+
+$focus = focus_after("h");
+is($focus, $mid->id, "At mid again");
+
+$focus = focus_after("j");
+is($focus, $bottom->id, "At bottom again");
+
+$focus = focus_after("l");
+is($focus, $top->id, "At top again");
+
+$focus = focus_after("mh");
+is($focus, $top->id, "Still at top, moved into");
+
+$focus = focus_after("k");
+is($focus, $bottom->id, "Window above is bottom");
+
+$focus = focus_after("k");
+is($focus, $mid->id, "Window above is mid");
+
+}
diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t
deleted file mode 100644 (file)
index 542dc82..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-use Digest::SHA1 qw(sha1_base64);
-
-BEGIN {
-    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $i3 = i3(get_socket_path());
-my $tmp = fresh_workspace;
-
-cmd 'split h';
-
-#####################################################################
-# Create two windows and make sure focus switching works
-#####################################################################
-
-my $top = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-my $bottom = open_standard_window($x);
-sleep 0.25;
-
-diag("top id = " . $top->id);
-diag("mid id = " . $mid->id);
-diag("bottom id = " . $bottom->id);
-
-#
-# Returns the input focus after sending the given command to i3 via IPC
-# end sleeping for half a second to make sure i3 reacted
-#
-sub focus_after {
-    my $msg = shift;
-
-    cmd $msg;
-    return $x->input_focus;
-}
-
-$focus = $x->input_focus;
-is($focus, $bottom->id, "Latest window focused");
-
-$focus = focus_after('focus left');
-is($focus, $mid->id, "Middle window focused");
-
-#####################################################################
-# Now goto a mark which does not exist
-#####################################################################
-
-my $random_mark = sha1_base64(rand());
-
-$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
-is($focus, $mid->id, "focus unchanged");
-
-$i3->command("mark $random_mark")->recv;
-
-$focus = focus_after('focus left');
-is($focus, $top->id, "Top window focused");
-
-$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
-is($focus, $mid->id, "goto worked");
-
-# check that we can specify multiple criteria
-
-$focus = focus_after('focus left');
-is($focus, $top->id, "Top window focused");
-
-$focus = focus_after(qq|[con_mark="$random_mark" con_mark="$random_mark"] focus|);
-is($focus, $mid->id, "goto worked");
-
-#####################################################################
-# Check whether the focus command will switch to a different
-# workspace if necessary
-#####################################################################
-
-my $tmp2 = fresh_workspace;
-
-is(focused_ws(), $tmp2, 'tmp2 now focused');
-
-cmd qq|[con_mark="$random_mark"] focus|;
-
-is(focused_ws(), $tmp, 'tmp now focused');
-
-done_testing;
diff --git a/testcases/t/111-goto.t b/testcases/t/111-goto.t
new file mode 100644 (file)
index 0000000..903fa0c
--- /dev/null
@@ -0,0 +1,77 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use File::Temp;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+
+#####################################################################
+# Create two windows and make sure focus switching works
+#####################################################################
+
+my $top = open_window($x);
+my $mid = open_window($x);
+my $bottom = open_window($x);
+
+#
+# Returns the input focus after sending the given command to i3 via IPC
+# and syncing with i3
+#
+sub focus_after {
+    my $msg = shift;
+
+    cmd $msg;
+    sync_with_i3($x);
+    return $x->input_focus;
+}
+
+$focus = $x->input_focus;
+is($focus, $bottom->id, "Latest window focused");
+
+$focus = focus_after('focus left');
+is($focus, $mid->id, "Middle window focused");
+
+#####################################################################
+# Now goto a mark which does not exist
+#####################################################################
+
+my $random_mark = mktemp('mark.XXXXXX');
+
+$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "focus unchanged");
+
+cmd "mark $random_mark";
+
+$focus = focus_after('focus left');
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "goto worked");
+
+# check that we can specify multiple criteria
+
+$focus = focus_after('focus left');
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after(qq|[con_mark="$random_mark" con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "goto worked");
+
+#####################################################################
+# Check whether the focus command will switch to a different
+# workspace if necessary
+#####################################################################
+
+my $tmp2 = fresh_workspace;
+
+is(focused_ws(), $tmp2, 'tmp2 now focused');
+
+cmd qq|[con_mark="$random_mark"] focus|;
+
+is(focused_ws(), $tmp, 'tmp now focused');
+
+done_testing;
diff --git a/testcases/t/112-floating-resize.t b/testcases/t/112-floating-resize.t
new file mode 100644 (file)
index 0000000..ac3387a
--- /dev/null
@@ -0,0 +1,65 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+my $x = X11::XCB::Connection->new;
+
+fresh_workspace;
+
+#####################################################################
+# Create a floating window and see if resizing works
+#####################################################################
+
+my $window = open_floating_window($x);
+
+# See if configurerequests cause window movements (they should not)
+my ($a, $t) = $window->rect;
+$window->rect(X11::XCB::Rect->new(x => $a->x, y => $a->y, width => $a->width, height => $a->height));
+
+sync_with_i3($x);
+
+my ($na, $nt) = $window->rect;
+is_deeply($na, $a, 'Rects are equal after configurerequest');
+
+sub test_resize {
+    $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 100, height => 100));
+
+    sync_with_i3($x);
+
+    my ($absolute, $top) = $window->rect;
+
+    # Make sure the width/height are different from what we’re gonna test, so
+    # that the test will work.
+    isnt($absolute->width, 300, 'width != 300');
+    isnt($absolute->height, 500, 'height != 500');
+
+    $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 300, height => 500));
+
+    sync_with_i3($x);
+
+    ($absolute, $top) = $window->rect;
+
+    is($absolute->width, 300, 'width = 300');
+    is($absolute->height, 500, 'height = 500');
+}
+
+# Test with default border
+test_resize;
+
+# Test borderless
+cmd 'border none';
+
+test_resize;
+
+# Test with 1-px-border
+cmd 'border 1pixel';
+
+test_resize;
+
+done_testing;
diff --git a/testcases/t/113-urgent.t b/testcases/t/113-urgent.t
new file mode 100644 (file)
index 0000000..7954408
--- /dev/null
@@ -0,0 +1,75 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+use List::Util qw(first);
+
+BEGIN {
+    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# Create two windows and put them in stacking mode
+#####################################################################
+
+cmd 'split v';
+
+my $top = open_window($x);
+my $bottom = open_window($x);
+
+my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
+is(@urgent, 0, 'no window got the urgent flag');
+
+# cmd 'layout stacking';
+
+#####################################################################
+# Add the urgency hint, switch to a different workspace and back again
+#####################################################################
+$top->add_hint('urgency');
+sync_with_i3($x);
+
+@content = @{get_ws_content($tmp)};
+@urgent = grep { $_->{urgent} } @content;
+$top_info = first { $_->{window} == $top->id } @content;
+$bottom_info = first { $_->{window} == $bottom->id } @content;
+
+ok($top_info->{urgent}, 'top window is marked urgent');
+ok(!$bottom_info->{urgent}, 'bottom window is not marked urgent');
+is(@urgent, 1, 'exactly one window got the urgent flag');
+
+cmd '[id="' . $top->id . '"] focus';
+
+@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
+is(@urgent, 0, 'no window got the urgent flag after focusing');
+
+$top->add_hint('urgency');
+sync_with_i3($x);
+
+@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
+is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint');
+
+#####################################################################
+# Check if the workspace urgency hint gets set/cleared correctly
+#####################################################################
+my $ws = get_ws($tmp);
+ok(!$ws->{urgent}, 'urgent flag not set on workspace');
+
+my $otmp = fresh_workspace;
+
+$top->add_hint('urgency');
+sync_with_i3($x);
+
+$ws = get_ws($tmp);
+ok($ws->{urgent}, 'urgent flag set on workspace');
+
+cmd "workspace $tmp";
+
+$ws = get_ws($tmp);
+ok(!$ws->{urgent}, 'urgent flag not set on workspace after switching');
+
+done_testing;
diff --git a/testcases/t/114-client-leader.t b/testcases/t/114-client-leader.t
new file mode 100644 (file)
index 0000000..6f7ffce
--- /dev/null
@@ -0,0 +1,96 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+####################################################################################
+# first part: test if a floating window will be correctly positioned above its leader
+#
+# This is verified by opening two windows, then opening a floating window above the
+# right one, then above the left one. If the floating windows are all positioned alike,
+# one of both (depending on your screen resolution) will be positioned wrong.
+####################################################################################
+
+my $left = open_window($x, { name => 'Left' });
+my $right = open_window($x, { name => 'Right' });
+
+my ($abs, $rgeom) = $right->rect;
+
+my $child = open_floating_window($x, {
+        dont_map => 1,
+        name => 'Child window',
+    });
+$child->client_leader($right);
+$child->map;
+
+ok(wait_for_map($x), 'child window mapped');
+
+my $cgeom;
+($abs, $cgeom) = $child->rect;
+cmp_ok($cgeom->x, '>=', $rgeom->x, 'Child X >= right container X');
+
+my $child2 = open_floating_window($x, {
+        dont_map => 1,
+        name => 'Child window 2',
+    });
+$child2->client_leader($left);
+$child2->map;
+
+ok(wait_for_map($x), 'second child window mapped');
+
+($abs, $cgeom) = $child2->rect;
+cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window');
+
+# check wm_transient_for
+my $fwindow = open_window($x, { dont_map => 1 });
+$fwindow->transient_for($right);
+$fwindow->map;
+
+ok(wait_for_map($x), 'transient window mapped');
+
+my ($absolute, $top) = $fwindow->rect;
+ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
+
+SKIP: {
+    skip "(workspace placement by client_leader not yet implemented)", 3;
+
+#####################################################################
+# Create a parent window
+#####################################################################
+
+my $window = open_window($x, { dont_map => 1, name => 'Parent window' });
+$window->map;
+
+ok(wait_for_map($x), 'parent window mapped');
+
+#########################################################################
+# Switch to a different workspace and open a child window. It should be opened
+# on the old workspace.
+#########################################################################
+fresh_workspace;
+
+my $child = open_window($x, { dont_map => 1, name => 'Child window' });
+$child->client_leader($window);
+$child->map;
+
+ok(wait_for_map($x), 'child window mapped');
+
+isnt($x->input_focus, $child->id, "Child window focused");
+
+# Switch back
+cmd "workspace $tmp";
+
+is($x->input_focus, $child->id, "Child window focused");
+
+}
+
+done_testing;
diff --git a/testcases/t/115-ipc-workspaces.t b/testcases/t/115-ipc-workspaces.t
new file mode 100644 (file)
index 0000000..4d9a029
--- /dev/null
@@ -0,0 +1,24 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+####################
+# Request workspaces
+####################
+
+SKIP: {
+    skip "IPC API not yet stabilized", 2;
+
+my $workspaces = $i3->get_workspaces->recv;
+
+ok(@{$workspaces} > 0, "More than zero workspaces found");
+
+#my $name_exists = all { defined($_->{name}) } @{$workspaces};
+#ok($name_exists, "All workspaces have a name");
+
+}
+
+done_testing;
diff --git a/testcases/t/116-nestedcons.t b/testcases/t/116-nestedcons.t
new file mode 100644 (file)
index 0000000..4b3958a
--- /dev/null
@@ -0,0 +1,84 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use List::Util qw(first);
+
+# to not depend on List::MoreUtils
+sub all (&@) {
+    my $cb = shift;
+    for (@_) {
+        return 0 unless $cb->();
+    }
+    return 1;
+}
+
+sub none (&@) {
+    my $cb = shift;
+    for (@_) {
+        return 0 if $cb->();
+    }
+    return 1;
+}
+
+my $i3 = i3(get_socket_path());
+
+####################
+# Request tree
+####################
+
+my $tree = $i3->get_tree->recv;
+
+my $expected = {
+    fullscreen_mode => 0,
+    nodes => ignore(),
+    window => undef,
+    name => 'root',
+    orientation => ignore(),
+    type => 0,
+    id => ignore(),
+    rect => ignore(),
+    window_rect => ignore(),
+    geometry => ignore(),
+    swallows => ignore(),
+    percent => undef,
+    layout => 'default',
+    focus => ignore(),
+    focused => JSON::XS::false,
+    urgent => JSON::XS::false,
+    border => 'normal',
+    'floating_nodes' => ignore(),
+};
+
+cmp_deeply($tree, $expected, 'root node OK');
+
+my @nodes = @{$tree->{nodes}};
+
+ok(@nodes > 0, 'root node has at least one leaf');
+
+ok((all { $_->{type} == 1 } @nodes), 'all nodes are of type CT_OUTPUT');
+ok((none { defined($_->{window}) } @nodes), 'no CT_OUTPUT contains a window');
+ok((all { @{$_->{nodes}} > 0 } @nodes), 'all nodes have at least one leaf (workspace)');
+my @workspaces;
+for my $ws (@nodes) {
+    my $content = first { $_->{type} == 2 } @{$ws->{nodes}};
+    @workspaces = (@workspaces, @{$content->{nodes}});
+}
+
+ok((all { $_->{type} == 4 } @workspaces), 'all workspaces are of type CT_WORKSPACE');
+#ok((all { @{$_->{nodes}} == 0 } @workspaces), 'all workspaces are empty yet');
+ok((none { defined($_->{window}) } @workspaces), 'no CT_OUTPUT contains a window');
+
+# TODO: get the focused container
+
+$i3->command('open')->recv;
+
+# TODO: get the focused container, check if it changed.
+# TODO: get the old focused container, check if there is a new child
+
+#diag(Dumper(\@workspaces));
+
+#diag(Dumper($tree));
+
+
+done_testing;
diff --git a/testcases/t/117-workspace.t b/testcases/t/117-workspace.t
new file mode 100644 (file)
index 0000000..3c3b6cc
--- /dev/null
@@ -0,0 +1,120 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests whether we can switch to a non-existant workspace
+# (necessary for further tests)
+#
+use List::Util qw(first);
+use i3test;
+
+# to ensure that workspace 1 stays open
+cmd 'open';
+
+my $tmp = fresh_workspace;
+ok(workspace_exists($tmp), 'workspace created');
+# if the workspace could not be created, we cannot run any other test
+# (every test starts by creating its workspace)
+if (!workspace_exists($tmp)) {
+    BAIL_OUT('Cannot create workspace, further tests make no sense');
+}
+
+my $otmp = fresh_workspace;
+diag("Other temporary workspace name: $otmp\n");
+
+# As the old workspace was empty, it should get
+# cleaned up as we switch away from it
+cmd "workspace $otmp";
+ok(!workspace_exists($tmp), 'old workspace cleaned up');
+
+# Switch to the same workspace again to make sure it doesn’t get cleaned up
+cmd "workspace $otmp";
+cmd "workspace $otmp";
+ok(workspace_exists($otmp), 'other workspace still exists');
+
+
+#####################################################################
+# check if the workspace next / prev commands work
+#####################################################################
+
+cmd 'workspace next';
+
+ok(!workspace_exists('next'), 'workspace "next" does not exist');
+
+cmd "workspace $tmp";
+cmd 'open';
+
+ok(workspace_exists($tmp), 'workspace created');
+
+cmd "workspace $otmp";
+cmd 'open';
+
+ok(workspace_exists($tmp), 'workspace tmp still exists');
+ok(workspace_exists($otmp), 'workspace otmp created');
+
+is(focused_ws(), $otmp, 'focused workspace is otmp');
+
+cmd 'workspace prev';
+is(focused_ws(), $tmp, 'focused workspace is tmp after workspace prev');
+
+cmd 'workspace next';
+is(focused_ws(), $otmp, 'focused workspace is otmp after workspace next');
+
+
+#####################################################################
+# check that wrapping works
+#####################################################################
+
+cmd 'workspace next';
+is(focused_ws(), '1', 'focused workspace is 1 after workspace next');
+
+cmd 'workspace next';
+is(focused_ws(), $tmp, 'focused workspace is tmp after workspace next');
+
+cmd 'workspace next';
+is(focused_ws(), $otmp, 'focused workspace is otmp after workspace next');
+
+
+cmd 'workspace prev';
+is(focused_ws(), $tmp, 'focused workspace is tmp after workspace prev');
+
+cmd 'workspace prev';
+is(focused_ws(), '1', 'focused workspace is tmp after workspace prev');
+
+cmd 'workspace prev';
+is(focused_ws(), $otmp, 'focused workspace is otmp after workspace prev');
+
+
+#####################################################################
+# check if we can change to "next" / "prev"
+#####################################################################
+
+cmd 'workspace "next"';
+
+ok(workspace_exists('next'), 'workspace "next" exists');
+is(focused_ws(), 'next', 'now on workspace next');
+
+cmd 'workspace "prev"';
+
+ok(workspace_exists('prev'), 'workspace "prev" exists');
+is(focused_ws(), 'prev', 'now on workspace prev');
+
+#####################################################################
+# check that the numbers are assigned/recognized correctly
+#####################################################################
+
+cmd "workspace 3: $tmp";
+my $ws = get_ws("3: $tmp");
+ok(defined($ws), "workspace 3: $tmp was created");
+is($ws->{num}, 3, 'workspace number is 3');
+
+cmd "workspace 0: $tmp";
+my $ws = get_ws("0: $tmp");
+ok(defined($ws), "workspace 0: $tmp was created");
+is($ws->{num}, 0, 'workspace number is 0');
+
+cmd "workspace aa: $tmp";
+my $ws = get_ws("aa: $tmp");
+ok(defined($ws), "workspace aa: $tmp was created");
+is($ws->{num}, -1, 'workspace number is -1');
+
+done_testing;
diff --git a/testcases/t/118-openkill.t b/testcases/t/118-openkill.t
new file mode 100644 (file)
index 0000000..e2a729c
--- /dev/null
@@ -0,0 +1,40 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests whether opening an empty container and killing it again works
+#
+use List::Util qw(first);
+use i3test;
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Open a new container
+cmd 'open';
+
+ok(@{get_ws_content($tmp)} == 1, 'container opened');
+
+cmd 'kill';
+ok(@{get_ws_content($tmp)} == 0, 'container killed');
+
+##############################################################
+# open two containers and kill the one which is not focused
+# by its ID to test if the parser correctly matches the window
+##############################################################
+
+cmd 'open';
+cmd 'open';
+ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+
+my $content = get_ws_content($tmp);
+my $not_focused = first { !$_->{focused} } @{$content};
+my $id = $not_focused->{id};
+
+cmd "[con_id=\"$id\"] kill";
+
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'one container killed');
+ok($content->[0]->{id} != $id, 'correct window killed');
+
+done_testing;
diff --git a/testcases/t/119-match.t b/testcases/t/119-match.t
new file mode 100644 (file)
index 0000000..8b9d21d
--- /dev/null
@@ -0,0 +1,170 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests all kinds of matching methods
+#
+use i3test;
+use X11::XCB qw(:all);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Open a new window
+my $x = X11::XCB::Connection->new;
+my $window = open_window($x);
+my $content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window mapped');
+my $win = $content->[0];
+
+######################################################################
+# first test that matches which should not match this window really do
+# not match it
+######################################################################
+# TODO: specify more match types
+# we can match on any (non-empty) class here since that window does not have
+# WM_CLASS set
+cmd q|[class=".*"] kill|;
+cmd q|[con_id="99999"] kill|;
+
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window still there');
+
+# now kill the window
+cmd 'nop now killing the window';
+my $id = $win->{id};
+cmd qq|[con_id="$id"] kill|;
+
+wait_for_unmap $x;
+
+cmd 'nop checking if its gone';
+$content = get_ws_content($tmp);
+ok(@{$content} == 0, 'window killed');
+
+# TODO: same test, but with pcre expressions
+
+######################################################################
+# check that multiple criteria work are checked with a logical AND,
+# not a logical OR (that is, matching is not cancelled after the first
+# criterion matches).
+######################################################################
+
+$tmp = fresh_workspace;
+
+# TODO: move to X11::XCB
+sub set_wm_class {
+    my ($id, $class, $instance) = @_;
+
+    # Add a _NET_WM_STRUT_PARTIAL hint
+    my $atomname = $x->atom(name => 'WM_CLASS');
+    my $atomtype = $x->atom(name => 'STRING');
+
+    $x->change_property(
+        PROP_MODE_REPLACE,
+        $id,
+        $atomname->id,
+        $atomtype->id,
+        8,
+        length($class) + length($instance) + 2,
+        "$instance\x00$class\x00"
+    );
+}
+
+my $left = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+    event_mask => [ 'structure_notify' ],
+);
+
+$left->_create;
+set_wm_class($left->id, 'special', 'special');
+$left->name('left');
+$left->map;
+ok(wait_for_map($x), 'left window mapped');
+
+my $right = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+    event_mask => [ 'structure_notify' ],
+);
+
+$right->_create;
+set_wm_class($right->id, 'special', 'special');
+$right->name('right');
+$right->map;
+ok(wait_for_map($x), 'right window mapped');
+
+# two windows should be here
+$content = get_ws_content($tmp);
+ok(@{$content} == 2, 'two windows opened');
+
+cmd '[class="special" title="left"] kill';
+
+sync_with_i3($x);
+
+$content = get_ws_content($tmp);
+is(@{$content}, 1, 'one window still there');
+
+######################################################################
+# check that regular expressions work
+######################################################################
+
+$tmp = fresh_workspace;
+
+$left = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+    event_mask => [ 'structure_notify' ],
+);
+
+$left->_create;
+set_wm_class($left->id, 'special7', 'special7');
+$left->name('left');
+$left->map;
+ok(wait_for_map($x), 'left window mapped');
+
+# two windows should be here
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window opened');
+
+cmd '[class="^special[0-9]$"] kill';
+
+wait_for_unmap $x;
+
+$content = get_ws_content($tmp);
+is(@{$content}, 0, 'window killed');
+
+######################################################################
+# check that UTF-8 works when matching
+######################################################################
+
+$tmp = fresh_workspace;
+
+$left = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+    event_mask => [ 'structure_notify' ],
+);
+
+$left->_create;
+set_wm_class($left->id, 'special7', 'special7');
+$left->name('ä 3');
+$left->map;
+ok(wait_for_map($x), 'left window mapped');
+
+# two windows should be here
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window opened');
+
+cmd '[title="^\w [3]$"] kill';
+
+wait_for_unmap $x;
+
+$content = get_ws_content($tmp);
+is(@{$content}, 0, 'window killed');
+
+done_testing;
diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t
deleted file mode 100644 (file)
index 09297df..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Beware that this test uses workspace 9 to perform some tests (it expects
-# the workspace to be empty).
-# TODO: skip it by default?
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-
-fresh_workspace;
-
-#####################################################################
-# Create a floating window and see if resizing works
-#####################################################################
-
-# Create a floating window
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#C0C0C0',
-    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-sleep 0.25;
-
-# See if configurerequests cause window movements (they should not)
-my ($a, $t) = $window->rect;
-$window->rect(X11::XCB::Rect->new(x => $a->x, y => $a->y, width => $a->width, height => $a->height));
-
-sleep 0.25;
-my ($na, $nt) = $window->rect;
-is_deeply($na, $a, 'Rects are equal after configurerequest');
-
-sub test_resize {
-    $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 100, height => 100));
-
-    my ($absolute, $top) = $window->rect;
-
-    # Make sure the width/height are different from what we’re gonna test, so
-    # that the test will work.
-    isnt($absolute->width, 300, 'width != 300');
-    isnt($absolute->height, 500, 'height != 500');
-
-    $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 300, height => 500));
-    sleep 0.25;
-
-    ($absolute, $top) = $window->rect;
-
-    is($absolute->width, 300, 'width = 300');
-    is($absolute->height, 500, 'height = 500');
-}
-
-# Test with default border
-test_resize;
-
-# Test borderless
-cmd 'border none';
-
-test_resize;
-
-# Test with 1-px-border
-cmd 'border 1pixel';
-
-test_resize;
-
-done_testing;
diff --git a/testcases/t/120-multiple-cmds.t b/testcases/t/120-multiple-cmds.t
new file mode 100644 (file)
index 0000000..8cd17a8
--- /dev/null
@@ -0,0 +1,57 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests multiple commands (using ';') and multiple operations (using ',')
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+sub multiple_cmds {
+    my ($cmd) = @_;
+
+    cmd 'open';
+    cmd 'open';
+    ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+
+    cmd $cmd;
+    ok(@{get_ws_content($tmp)} == 0, "both containers killed (cmd = $cmd)");
+}
+multiple_cmds('kill;kill');
+multiple_cmds('kill; kill');
+multiple_cmds('kill ; kill');
+multiple_cmds('kill ;kill');
+multiple_cmds('kill  ;kill');
+multiple_cmds('kill  ;  kill');
+multiple_cmds("kill;\tkill");
+multiple_cmds("kill\t;kill");
+multiple_cmds("kill\t;\tkill");
+multiple_cmds("kill\t ;\tkill");
+multiple_cmds("kill\t ;\t kill");
+multiple_cmds("kill \t ; \t kill");
+
+#####################################################################
+# test if un-quoted strings are handled correctly
+#####################################################################
+
+$tmp = fresh_workspace;
+cmd 'open';
+my $unused = get_unused_workspace;
+ok(!($unused ~~ @{get_workspace_names()}), 'workspace does not exist yet');
+cmd "move workspace $unused; nop parser test";
+ok(($unused ~~ @{get_workspace_names()}), 'workspace exists after moving');
+
+#####################################################################
+# quote the workspace name and use a ; (command separator) in its name
+#####################################################################
+
+cmd 'open';
+$unused = get_unused_workspace;
+$unused .= ';a';
+ok(!($unused ~~ @{get_workspace_names()}), 'workspace does not exist yet');
+cmd qq|move workspace "$unused"; nop parser test|;
+ok(($unused ~~ @{get_workspace_names()}), 'workspace exists after moving');
+
+# TODO: need a non-invasive command before implementing a test which uses ','
+
+done_testing;
diff --git a/testcases/t/121-next-prev.t b/testcases/t/121-next-prev.t
new file mode 100644 (file)
index 0000000..447be31
--- /dev/null
@@ -0,0 +1,76 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests focus switching (next/prev)
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+######################################################################
+# Open one container, verify that 'focus down' and 'focus right' do nothing
+######################################################################
+cmd 'open';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+my $old_focused = $focus->[0];
+
+cmd 'focus down';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $old_focused, 'focus did not change with only one con');
+
+cmd 'focus right';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $old_focused, 'focus did not change with only one con');
+
+######################################################################
+# Open another container, verify that 'focus right' switches
+######################################################################
+my $left = $old_focused;
+
+cmd 'open';
+($nodes, $focus) = get_ws_content($tmp);
+isnt($old_focused, $focus->[0], 'new container is focused');
+my $mid = $focus->[0];
+
+cmd 'open';
+($nodes, $focus) = get_ws_content($tmp);
+isnt($old_focused, $focus->[0], 'new container is focused');
+my $right = $focus->[0];
+
+cmd 'focus right';
+($nodes, $focus) = get_ws_content($tmp);
+isnt($focus->[0], $right, 'focus did change');
+is($focus->[0], $left, 'left container focused (wrapping)');
+
+cmd 'focus right';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $mid, 'middle container focused');
+
+cmd 'focus right';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $right, 'right container focused');
+
+cmd 'focus left';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $mid, 'middle container focused');
+
+cmd 'focus left';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $left, 'left container focused');
+
+cmd 'focus left';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $right, 'right container focused');
+
+
+######################################################################
+# Test focus command
+######################################################################
+
+cmd qq|[con_id="$mid"] focus|;
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $mid, 'middle container focused');
+
+
+done_testing;
diff --git a/testcases/t/122-split.t b/testcases/t/122-split.t
new file mode 100644 (file)
index 0000000..3484c7f
--- /dev/null
@@ -0,0 +1,92 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests splitting
+#
+use i3test;
+use X11::XCB qw(:all);
+
+my $tmp = fresh_workspace;
+
+my $ws = get_ws($tmp);
+is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
+cmd 'split v';
+$ws = get_ws($tmp);
+is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
+
+######################################################################
+# Open two containers, split, open another container. Then verify
+# the layout is like we expect it to be
+######################################################################
+cmd 'open';
+cmd 'open';
+my $content = get_ws_content($tmp);
+
+is(@{$content}, 2, 'two containers on workspace level');
+my $first = $content->[0];
+my $second = $content->[1];
+
+is(@{$first->{nodes}}, 0, 'first container has no children');
+is(@{$second->{nodes}}, 0, 'second container has no children (yet)');
+my $old_name = $second->{name};
+
+
+cmd 'split h';
+cmd 'open';
+
+$content = get_ws_content($tmp);
+
+is(@{$content}, 2, 'two containers on workspace level');
+$first = $content->[0];
+$second = $content->[1];
+
+is(@{$first->{nodes}}, 0, 'first container has no children');
+isnt($second->{name}, $old_name, 'second container was replaced');
+is($second->{orientation}, 'horizontal', 'orientation is horizontal');
+is(@{$second->{nodes}}, 2, 'second container has 2 children');
+is($second->{nodes}->[0]->{name}, $old_name, 'found old second container');
+
+# TODO: extend this test-case (test next/prev)
+# - wrapping (no horizontal switch possible, goes level-up)
+# - going level-up "manually"
+
+######################################################################
+# Test splitting multiple times without actually creating windows
+######################################################################
+
+$tmp = fresh_workspace;
+
+$ws = get_ws($tmp);
+is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
+cmd 'split v';
+$ws = get_ws($tmp);
+is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
+
+cmd 'open';
+my @content = @{get_ws_content($tmp)};
+
+# 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);
+}
+
+my $old_count = sum_nodes(\@content);
+cmd 'split v';
+
+@content = @{get_ws_content($tmp)};
+$old_count = sum_nodes(\@content);
+
+cmd 'split v';
+
+@content = @{get_ws_content($tmp)};
+my $count = sum_nodes(\@content);
+is($count, $old_count, 'not more windows after splitting again');
+
+done_testing;
diff --git a/testcases/t/124-move.t b/testcases/t/124-move.t
new file mode 100644 (file)
index 0000000..a6eb616
--- /dev/null
@@ -0,0 +1,207 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests moving. Basically, there are four different code-paths:
+# 1) move a container which cannot be moved (single container on a workspace)
+# 2) move a container before another single container
+# 3) move a container inside another container
+# 4) move a container in a different direction so that we need to go up in tree
+#
+use i3test;
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+######################################################################
+# 1) move a container which cannot be moved
+######################################################################
+
+cmd 'open';
+
+my $old_content = get_ws_content($tmp);
+is(@{$old_content}, 1, 'one container on this workspace');
+
+my $first = $old_content->[0]->{id};
+
+cmd 'move left';
+cmd 'move right';
+cmd 'move up';
+cmd 'move down';
+
+my $content = get_ws_content($tmp);
+is_deeply($old_content, $content, 'workspace unmodified after useless moves');
+
+######################################################################
+# 2) move a container before another single container
+######################################################################
+
+cmd 'open';
+$content = get_ws_content($tmp);
+is(@{$content}, 2, 'two containers on this workspace');
+my $second = $content->[1]->{id};
+
+is($content->[0]->{id}, $first, 'first container unmodified');
+
+# Move the second container before the first one (→ swap them)
+cmd 'move left';
+$content = get_ws_content($tmp);
+is($content->[0]->{id}, $second, 'first container modified');
+
+# We should not be able to move any further
+cmd 'move left';
+$content = get_ws_content($tmp);
+is($content->[0]->{id}, $second, 'first container unmodified');
+
+# Now move in the other direction
+cmd 'move right';
+$content = get_ws_content($tmp);
+is($content->[0]->{id}, $first, 'first container modified');
+
+# We should not be able to move any further
+cmd 'move right';
+$content = get_ws_content($tmp);
+is($content->[0]->{id}, $first, 'first container unmodified');
+
+######################################################################
+# 3) move a container inside another container
+######################################################################
+
+# Split the current (second) container and create a new container on workspace
+# level. Our layout looks like this now:
+# --------------------------
+# |       | second |       |
+# | first | ------ | third |
+# |       |        |       |
+# --------------------------
+cmd 'split v';
+cmd 'focus parent';
+cmd 'open';
+
+$content = get_ws_content($tmp);
+is(@{$content}, 3, 'three containers on this workspace');
+my $third = $content->[2]->{id};
+
+cmd 'move left';
+$content = get_ws_content($tmp);
+is(@{$content}, 2, 'only two containers on this workspace');
+my $nodes = $content->[1]->{nodes};
+is($nodes->[0]->{id}, $second, 'second container on top');
+is($nodes->[1]->{id}, $third, 'third container on bottom');
+
+######################################################################
+# move it inside the split container
+######################################################################
+
+cmd 'move up';
+$nodes = get_ws_content($tmp)->[1]->{nodes};
+is($nodes->[0]->{id}, $third, 'third container on top');
+is($nodes->[1]->{id}, $second, 'second container on bottom');
+
+# move it outside again
+cmd 'move left';
+$content = get_ws_content($tmp);
+is(@{$content}, 3, 'three nodes on this workspace');
+
+# due to automatic flattening/cleanup, the remaining split container
+# will be replaced by the con itself, so we will still have 3 nodes
+cmd 'move right';
+$content = get_ws_content($tmp);
+is(@{$content}, 2, 'two nodes on this workspace');
+
+######################################################################
+# 4) We create two v-split containers on the workspace, then we move
+#    all Cons from the left v-split to the right one. The old vsplit
+#    container needs to be closed. Verify that it will be closed.
+######################################################################
+
+my $otmp = fresh_workspace;
+
+cmd "open";
+cmd "open";
+cmd "split v";
+cmd "open";
+cmd 'focus left';
+cmd "split v";
+cmd "open";
+cmd "move right";
+cmd 'focus left';
+cmd "move right";
+
+$content = get_ws_content($otmp);
+is(@{$content}, 1, 'only one nodes on this workspace');
+
+######################################################################
+# 5) test moving floating containers.
+######################################################################
+
+$tmp = fresh_workspace;
+my $floatwin = open_floating_window($x);
+my ($absolute_before, $top_before) = $floatwin->rect;
+
+cmd 'move left';
+
+my ($absolute, $top) = $floatwin->rect;
+
+is($absolute->x, ($absolute_before->x - 10), 'moved 10 px to the left');
+is($absolute->y, $absolute_before->y, 'y not changed');
+is($absolute->width, $absolute_before->width, 'width not changed');
+is($absolute->height, $absolute_before->height, 'height not changed');
+
+$absolute_before = $absolute;
+$top_before = $top;
+
+cmd 'move right';
+
+($absolute, $top) = $floatwin->rect;
+
+is($absolute->x, ($absolute_before->x + 10), 'moved 10 px to the right');
+is($absolute->y, $absolute_before->y, 'y not changed');
+is($absolute->width, $absolute_before->width, 'width not changed');
+is($absolute->height, $absolute_before->height, 'height not changed');
+
+$absolute_before = $absolute;
+$top_before = $top;
+
+cmd 'move up';
+
+($absolute, $top) = $floatwin->rect;
+
+is($absolute->x, $absolute_before->x, 'x not changed');
+is($absolute->y, ($absolute_before->y - 10), 'moved 10 px up');
+is($absolute->width, $absolute_before->width, 'width not changed');
+is($absolute->height, $absolute_before->height, 'height not changed');
+
+$absolute_before = $absolute;
+$top_before = $top;
+
+cmd 'move down';
+
+($absolute, $top) = $floatwin->rect;
+
+is($absolute->x, $absolute_before->x, 'x not changed');
+is($absolute->y, ($absolute_before->y + 10), 'moved 10 px up');
+is($absolute->width, $absolute_before->width, 'width not changed');
+is($absolute->height, $absolute_before->height, 'height not changed');
+
+$absolute_before = $absolute;
+$top_before = $top;
+
+######################################################################
+# 6) test moving floating containers with a specific amount of px
+######################################################################
+
+cmd 'move left 20 px';
+
+($absolute, $top) = $floatwin->rect;
+
+is($absolute->x, ($absolute_before->x - 20), 'moved 10 px to the left');
+is($absolute->y, $absolute_before->y, 'y not changed');
+is($absolute->width, $absolute_before->width, 'width not changed');
+is($absolute->height, $absolute_before->height, 'height not changed');
+
+
+
+done_testing;
diff --git a/testcases/t/126-regress-close.t b/testcases/t/126-regress-close.t
new file mode 100644 (file)
index 0000000..8aec87d
--- /dev/null
@@ -0,0 +1,18 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: closing of floating clients did crash i3 when closing the
+# container which contained this client.
+#
+use i3test;
+
+fresh_workspace;
+
+cmd 'open';
+cmd 'mode toggle';
+cmd 'kill';
+cmd 'kill';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/127-regress-floating-parent.t b/testcases/t/127-regress-floating-parent.t
new file mode 100644 (file)
index 0000000..52b8b9c
--- /dev/null
@@ -0,0 +1,41 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: make a container floating, kill its parent, make it tiling again
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+cmd 'open';
+my $left = get_focused($tmp);
+cmd 'open';
+my $old = get_focused($tmp);
+cmd 'split v';
+cmd 'open';
+my $floating = get_focused($tmp);
+diag("focused floating: " . get_focused($tmp));
+cmd 'mode toggle';
+# TODO: eliminate this race conditition
+sleep 1;
+
+# kill old container
+cmd qq|[con_id="$old"] focus|;
+is(get_focused($tmp), $old, 'old container focused');
+cmd 'kill';
+
+# kill left container
+cmd qq|[con_id="$left"] focus|;
+is(get_focused($tmp), $left, 'old container focused');
+cmd 'kill';
+
+# focus floating window, make it tiling again
+cmd qq|[con_id="$floating"] focus|;
+is(get_focused($tmp), $floating, 'floating window focused');
+
+sleep 1;
+cmd 'mode toggle';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/128-open-order.t b/testcases/t/128-open-order.t
new file mode 100644 (file)
index 0000000..b638e70
--- /dev/null
@@ -0,0 +1,37 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Check if new containers are opened after the currently focused one instead
+# of always at the end
+use List::Util qw(first);
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Open two new container
+my $first = open_empty_con($i3);
+
+ok(@{get_ws_content($tmp)} == 1, 'containers opened');
+
+my $second = open_empty_con($i3);
+
+isnt($first, $second, 'different container focused');
+
+##############################################################
+# see if new containers open after the currently focused
+##############################################################
+
+cmd qq|[con_id="$first"] focus|;
+cmd 'open';
+$content = get_ws_content($tmp);
+ok(@{$content} == 3, 'three containers opened');
+
+is($content->[0]->{id}, $first, 'first container unmodified');
+isnt($content->[1]->{id}, $second, 'second container replaced');
+is($content->[2]->{id}, $second, 'third container unmodified');
+
+done_testing;
diff --git a/testcases/t/129-focus-after-close.t b/testcases/t/129-focus-after-close.t
new file mode 100644 (file)
index 0000000..8d22561
--- /dev/null
@@ -0,0 +1,122 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Check if the focus is correctly restored after closing windows.
+#
+use i3test;
+use X11::XCB qw(:all);
+use List::Util qw(first);
+
+my $x = X11::XCB::Connection->new;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_empty_con($i3);
+my $second = open_empty_con($i3);
+
+cmd 'split v';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+ok(!$nodes->[1]->{focused}, 'split container not focused');
+cmd 'focus parent';
+($nodes, $focus) = get_ws_content($tmp);
+ok($nodes->[1]->{focused}, 'split container focused after focus parent');
+
+my $third = open_empty_con($i3);
+
+isnt(get_focused($tmp), $second, 'different container focused');
+
+# We have the following layout now (con is focused):
+# .----------------.
+# | split  |       |
+# | .----. |  con  |
+# | | cn | |       |
+# | `----' |       |
+# `----------------'
+
+##############################################################
+# see if the focus goes down to $first (not to its split parent)
+# when closing $second
+##############################################################
+
+cmd 'kill';
+# TODO: this testcase sometimes has different outcomes when the
+# sleep is missing. why?
+sleep 0.25;
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{nodes}->[0]->{id}, $second, 'second container found');
+ok($nodes->[1]->{nodes}->[0]->{focused}, 'second container focused');
+
+##############################################################
+# another case, using a slightly different layout (regression)
+##############################################################
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+cmd 'split v';
+$first = open_empty_con($i3);
+my $bottom = open_empty_con($i3);
+
+cmd 'focus up';
+cmd 'split h';
+my $middle = open_empty_con($i3);
+my $right = open_empty_con($i3);
+cmd 'focus down';
+
+# We have the following layout now (second is focused):
+# .----------------------------.
+# | .------------------------. |
+# | | first | middle | right | |
+# | `------------------------' |
+# |----------------------------|
+# |                            |
+# |          second            |
+# |                            |
+# `----------------------------'
+
+# Check if the focus is restored to $right when we close $second
+cmd 'kill';
+
+is(get_focused($tmp), $right, 'top right container focused (in focus stack)');
+
+($nodes, $focus) = get_ws_content($tmp);
+my $tr = first { $_->{id} eq $right } @{$nodes->[0]->{nodes}};
+is($tr->{focused}, 1, 'top right container really has focus');
+
+##############################################################
+# check if focus is correct after closing an unfocused window
+##############################################################
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_empty_con($i3);
+$middle = open_empty_con($i3);
+# XXX: the $right empty con will be filled with the x11 window we are creating afterwards
+$right = open_empty_con($i3);
+my $win = open_window($x, { background_color => '#00ff00' });
+
+cmd qq|[con_id="$middle"] focus|;
+$win->destroy;
+
+sleep 0.25;
+
+is(get_focused($tmp), $middle, 'middle container focused');
+
+##############################################################
+# and now for something completely different:
+# check if the pointer position is relevant when restoring focus
+# (it should not be relevant, of course)
+##############################################################
+
+# TODO: add test code as soon as I can reproduce it
+
+done_testing;
diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t
deleted file mode 100644 (file)
index f40b72f..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-use List::Util qw(first);
-
-BEGIN {
-    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# Create two windows and put them in stacking mode
-#####################################################################
-
-cmd 'split v';
-
-my $top = open_standard_window($x);
-my $bottom = open_standard_window($x);
-
-my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
-is(@urgent, 0, 'no window got the urgent flag');
-
-# cmd 'layout stacking';
-
-#####################################################################
-# Add the urgency hint, switch to a different workspace and back again
-#####################################################################
-$top->add_hint('urgency');
-sleep 0.5;
-
-@content = @{get_ws_content($tmp)};
-@urgent = grep { $_->{urgent} } @content;
-$top_info = first { $_->{window} == $top->id } @content;
-$bottom_info = first { $_->{window} == $bottom->id } @content;
-
-ok($top_info->{urgent}, 'top window is marked urgent');
-ok(!$bottom_info->{urgent}, 'bottom window is not marked urgent');
-is(@urgent, 1, 'exactly one window got the urgent flag');
-
-cmd '[id="' . $top->id . '"] focus';
-
-@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
-is(@urgent, 0, 'no window got the urgent flag after focusing');
-
-$top->add_hint('urgency');
-sleep 0.5;
-
-@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
-is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint');
-
-#####################################################################
-# Check if the workspace urgency hint gets set/cleared correctly
-#####################################################################
-my $ws = get_ws($tmp);
-ok(!$ws->{urgent}, 'urgent flag not set on workspace');
-
-my $otmp = fresh_workspace;
-
-$top->add_hint('urgency');
-sleep 0.5;
-
-$ws = get_ws($tmp);
-ok($ws->{urgent}, 'urgent flag set on workspace');
-
-cmd "workspace $tmp";
-
-$ws = get_ws($tmp);
-ok(!$ws->{urgent}, 'urgent flag not set on workspace after switching');
-
-done_testing;
diff --git a/testcases/t/130-close-empty-split.t b/testcases/t/130-close-empty-split.t
new file mode 100644 (file)
index 0000000..57855cd
--- /dev/null
@@ -0,0 +1,82 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Check if empty split containers are automatically closed.
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_empty_con($i3);
+my $second = open_empty_con($i3);
+cmd qq|[con_id="$first"] focus|;
+
+cmd 'split v';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+is($nodes->[0]->{focused}, 0, 'split container not focused');
+
+# focus the split container
+cmd 'level up';
+($nodes, $focus) = get_ws_content($tmp);
+my $split = $focus->[0];
+cmd 'level down';
+
+my $second = open_empty_con($i3);
+
+isnt($first, $second, 'different container focused');
+
+##############################################################
+# close both windows and see if the split container still exists
+##############################################################
+
+cmd 'kill';
+cmd 'kill';
+($nodes, $focus) = get_ws_content($tmp);
+isnt($nodes->[0]->{id}, $split, 'split container closed');
+
+##############################################################
+# same thing but this time we are moving the cons away instead
+# of killing them
+##############################################################
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_empty_con($i3);
+$second = open_empty_con($i3);
+cmd qq|[con_id="$first"] focus|;
+
+cmd 'split v';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+is($nodes->[0]->{focused}, 0, 'split container not focused');
+
+# focus the split container
+cmd 'level up';
+($nodes, $focus) = get_ws_content($tmp);
+my $split = $focus->[0];
+cmd 'level down';
+
+my $second = open_empty_con($i3);
+
+isnt($first, $second, 'different container focused');
+
+##############################################################
+# close both windows and see if the split container still exists
+##############################################################
+
+my $otmp = get_unused_workspace();
+cmd "move workspace $otmp";
+cmd "move workspace $otmp";
+($nodes, $focus) = get_ws_content($tmp);
+isnt($nodes->[0]->{id}, $split, 'split container closed');
+
+done_testing;
diff --git a/testcases/t/131-stacking-order.t b/testcases/t/131-stacking-order.t
new file mode 100644 (file)
index 0000000..9c1e74c
--- /dev/null
@@ -0,0 +1,52 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Check if stacking containers can be used independantly of
+# the split mode (horizontal/vertical) of the underlying
+# container.
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Enforce vertical split mode
+cmd 'split v';
+
+my $first = open_empty_con($i3);
+my $second = open_empty_con($i3);
+
+isnt($first, $second, 'two different containers opened');
+
+##############################################################
+# change mode to stacking and cycle through the containers
+##############################################################
+
+cmd 'layout stacking';
+is(get_focused($tmp), $second, 'second container still focused');
+
+cmd 'focus down';
+is(get_focused($tmp), $first, 'first container focused');
+
+cmd 'focus up';
+is(get_focused($tmp), $second, 'second container focused again');
+
+##############################################################
+# now change the orientation to horizontal and cycle
+##############################################################
+
+cmd 'focus parent';
+cmd 'split h';
+cmd 'focus child';
+
+cmd 'focus down';
+is(get_focused($tmp), $first, 'first container focused');
+
+cmd 'focus up';
+is(get_focused($tmp), $second, 'second container focused again');
+
+
+done_testing;
diff --git a/testcases/t/132-move-workspace.t b/testcases/t/132-move-workspace.t
new file mode 100644 (file)
index 0000000..82e59dd
--- /dev/null
@@ -0,0 +1,95 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Checks if the 'move workspace' command works correctly
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+# We move the pointer out of our way to avoid a bug where the focus will
+# be set to the window under the cursor
+my $x = X11::XCB::Connection->new;
+$x->root->warp_pointer(0, 0);
+
+my $tmp = get_unused_workspace();
+my $tmp2 = get_unused_workspace();
+cmd "workspace $tmp";
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_empty_con($i3);
+my $second = open_empty_con($i3);
+ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws');
+
+cmd "workspace $tmp2";
+ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws yet');
+
+cmd "workspace $tmp";
+
+cmd "move workspace $tmp2";
+ok(@{get_ws_content($tmp)} == 1, 'one container on first ws anymore');
+ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
+my ($nodes, $focus) = get_ws_content($tmp2);
+
+is($focus->[0], $second, 'same container on different ws');
+
+($nodes, $focus) = get_ws_content($tmp);
+ok($nodes->[0]->{focused}, 'first container focused on first ws');
+
+###################################################################
+# check if 'move workspace next' and 'move workspace prev' work
+###################################################################
+
+# Open two containers on the first workspace, one container on the second
+# workspace. Because the workspaces are named, they will be sorted by order of
+# creation.
+$tmp = get_unused_workspace();
+$tmp2 = get_unused_workspace();
+cmd "workspace $tmp";
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+$first = open_empty_con($i3);
+$second = open_empty_con($i3);
+ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws');
+
+cmd "workspace $tmp2";
+ok(@{get_ws_content($tmp2)} == 0, 'no containers yet');
+my $third = open_empty_con($i3);
+ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
+
+# go back to the first workspace, move one of the containers to the next one
+cmd "workspace $tmp";
+cmd 'move workspace next';
+ok(@{get_ws_content($tmp)} == 1, 'one container on first ws');
+ok(@{get_ws_content($tmp2)} == 2, 'two containers on second ws');
+
+# go to the second workspace and move two containers to the first one
+cmd "workspace $tmp2";
+cmd 'move workspace prev';
+cmd 'move workspace prev';
+ok(@{get_ws_content($tmp)} == 3, 'three containers on first ws');
+ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws');
+
+###################################################################
+# check if floating cons are moved to new workspaces properly
+# (that is, if they are floating on the target ws, too)
+###################################################################
+
+$tmp = get_unused_workspace();
+$tmp2 = get_unused_workspace();
+cmd "workspace $tmp";
+
+cmd "open";
+cmd "floating toggle";
+
+my $ws = get_ws($tmp);
+is(@{$ws->{nodes}}, 0, 'no nodes on workspace');
+is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace');
+
+cmd "move workspace $tmp2";
+
+$ws = get_ws($tmp2);
+is(@{$ws->{nodes}}, 0, 'no nodes on workspace');
+is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace');
+
+done_testing;
diff --git a/testcases/t/133-size-hints.t b/testcases/t/133-size-hints.t
new file mode 100644 (file)
index 0000000..d2d77e8
--- /dev/null
@@ -0,0 +1,35 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Checks if size hints are interpreted correctly.
+#
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $win = open_window($x, { dont_map => 1 });
+# XXX: we should check screen size. in screens with an AR of 2.0,
+# this is not a good idea.
+my $aspect = X11::XCB::Sizehints::Aspect->new;
+$aspect->min_num(600);
+$aspect->min_den(300);
+$aspect->max_num(600);
+$aspect->max_den(300);
+$win->_create;
+$win->map;
+wait_for_map $x;
+$win->hints->aspect($aspect);
+$x->flush;
+
+sync_with_i3($x);
+
+my $rect = $win->rect;
+my $ar = $rect->width / $rect->height;
+diag("Aspect ratio = $ar");
+ok(($ar > 1.90) && ($ar < 2.10), 'Aspect ratio about 2.0');
+
+done_testing;
diff --git a/testcases/t/134-invalid-command.t b/testcases/t/134-invalid-command.t
new file mode 100644 (file)
index 0000000..d58985e
--- /dev/null
@@ -0,0 +1,12 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# 
+#
+use i3test;
+
+cmd 'blargh!';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/135-floating-focus.t b/testcases/t/135-floating-focus.t
new file mode 100644 (file)
index 0000000..a1ab211
--- /dev/null
@@ -0,0 +1,228 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#############################################################################
+# 1: see if focus stays the same when toggling tiling/floating mode
+#############################################################################
+
+my $first = open_window($x);
+my $second = open_window($x);
+
+is($x->input_focus, $second->id, 'second window focused');
+
+cmd 'floating enable';
+cmd 'floating disable';
+
+is($x->input_focus, $second->id, 'second window still focused after mode toggle');
+
+#############################################################################
+# 2: see if focus stays on the current floating window if killing another
+# floating window
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_window($x);    # window 2
+$second = open_window($x);   # window 3
+my $third = open_window($x); # window 4
+
+is($x->input_focus, $third->id, 'last container focused');
+
+cmd 'floating enable';
+
+cmd '[id="' . $second->id . '"] focus';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second con focused');
+
+cmd 'floating enable';
+
+# now kill the third one (it's floating). focus should stay unchanged
+cmd '[id="' . $third->id . '"] kill';
+
+# TODO: wait for unmapnotify
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second con still focused after killing third');
+
+
+#############################################################################
+# 3: see if the focus gets reverted correctly when closing floating clients
+# (first to the next floating client, then to the last focused tiling client)
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_window($x, { background_color => '#ff0000' });    # window 5
+$second = open_window($x, { background_color => '#00ff00' });   # window 6
+my $third = open_window($x, { background_color => '#0000ff' }); # window 7
+
+is($x->input_focus, $third->id, 'last container focused');
+
+cmd 'floating enable';
+
+cmd '[id="' . $second->id . '"] focus';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second con focused');
+
+cmd 'floating enable';
+
+# now kill the second one. focus should fall back to the third one, which is
+# also floating
+cmd 'kill';
+
+# TODO: wait for unmapnotify
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'third con focused');
+
+cmd 'kill';
+# TODO: wait for unmapnotify
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
+
+#############################################################################
+# 4: same test as 3, but with another split con
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_window($x, { background_color => '#ff0000' });    # window 5
+cmd 'split v';
+cmd 'layout stacked';
+$second = open_window($x, { background_color => '#00ff00' });   # window 6
+$third = open_window($x, { background_color => '#0000ff' }); # window 7
+
+is($x->input_focus, $third->id, 'last container focused');
+
+cmd 'floating enable';
+
+cmd '[id="' . $second->id . '"] focus';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second con focused');
+
+cmd 'floating enable';
+
+sync_with_i3($x);
+
+# now kill the second one. focus should fall back to the third one, which is
+# also floating
+cmd 'kill';
+
+# TODO: wait for unmapnotify
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'third con focused');
+
+cmd 'kill';
+# TODO: wait for unmapnotify
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
+
+#############################################################################
+# 5: see if the 'focus tiling' and 'focus floating' commands work
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_window($x, { background_color => '#ff0000' });    # window 8
+$second = open_window($x, { background_color => '#00ff00' });   # window 9
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second container focused');
+
+cmd 'floating enable';
+
+is($x->input_focus, $second->id, 'second container focused');
+
+cmd 'focus tiling';
+
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'first (tiling) container focused');
+
+cmd 'focus floating';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second (floating) container focused');
+
+cmd 'focus floating';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second (floating) container still focused');
+
+cmd 'focus mode_toggle';
+
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'first (tiling) container focused');
+
+cmd 'focus mode_toggle';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second (floating) container focused');
+
+#############################################################################
+# 6: see if switching floating focus using the focus left/right command works
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_floating_window($x, { background_color => '#ff0000' });# window 10
+$second = open_floating_window($x, { background_color => '#00ff00' }); # window 11
+$third = open_floating_window($x, { background_color => '#0000ff' }); # window 12
+
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'third container focused');
+
+cmd 'focus left';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second container focused');
+
+cmd 'focus left';
+
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'first container focused');
+
+cmd 'focus left';
+
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'focus wrapped to third container');
+
+cmd 'focus right';
+
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'focus wrapped to first container');
+
+cmd 'focus right';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'focus on second container');
+
+done_testing;
diff --git a/testcases/t/136-floating-ws-empty.t b/testcases/t/136-floating-ws-empty.t
new file mode 100644 (file)
index 0000000..a6e0e40
--- /dev/null
@@ -0,0 +1,35 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Regression test: when only having a floating window on a workspace, it should not be deleted.
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#############################################################################
+# 1: open a floating window, get it mapped
+#############################################################################
+
+ok(workspace_exists($tmp), "workspace $tmp exists");
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = open_floating_window($x);
+ok($window->mapped, 'Window is mapped');
+
+# switch to a different workspace, see if the window is still mapped?
+
+my $otmp = fresh_workspace;
+
+ok(workspace_exists($otmp), "new workspace $otmp exists");
+ok(workspace_exists($tmp), "old workspace $tmp still exists");
+
+done_testing;
diff --git a/testcases/t/137-floating-unmap.t b/testcases/t/137-floating-unmap.t
new file mode 100644 (file)
index 0000000..ab1a33d
--- /dev/null
@@ -0,0 +1,37 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Regression test: Floating windows were not correctly unmapped when switching
+# to a different workspace.
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#############################################################################
+# 1: open a floating window, get it mapped
+#############################################################################
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = open_floating_window($x);
+ok($window->mapped, 'Window is mapped');
+
+# switch to a different workspace, see if the window is still mapped?
+
+my $otmp = fresh_workspace;
+
+sync_with_i3($x);
+
+ok(!$window->mapped, 'Window is not mapped after switching ws');
+
+cmd "nop testcase done";
+
+done_testing;
diff --git a/testcases/t/138-floating-attach.t b/testcases/t/138-floating-attach.t
new file mode 100644 (file)
index 0000000..b08190a
--- /dev/null
@@ -0,0 +1,71 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Regression test: New windows were attached to the container of a floating window
+# if only a floating window is present on the workspace.
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#############################################################################
+# 1: open a floating window, get it mapped
+#############################################################################
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window
+my $window = open_floating_window($x);
+ok($window->mapped, 'Window is mapped');
+
+my $ws = get_ws($tmp);
+my ($nodes, $focus) = get_ws_content($tmp);
+
+is(@{$ws->{floating_nodes}}, 1, 'one floating node');
+is(@{$nodes}, 0, 'no tiling nodes');
+
+# Create a tiling window
+my $twindow = open_window($x);
+
+($nodes, $focus) = get_ws_content($tmp);
+
+is(@{$nodes}, 1, 'one tiling node');
+
+#############################################################################
+# 2: similar case: floating windows should be attached at the currently focused
+# position in the workspace (for example a stack), not just at workspace level.
+#############################################################################
+
+$tmp = fresh_workspace;
+
+my $first = open_window($x);
+my $second = open_window($x);
+
+cmd 'layout stacked';
+
+$ws = get_ws($tmp);
+is(@{$ws->{floating_nodes}}, 0, 'no floating nodes so far');
+is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
+
+# Create a floating window
+my $window = open_floating_window($x);
+ok($window->mapped, 'Window is mapped');
+
+$ws = get_ws($tmp);
+is(@{$ws->{floating_nodes}}, 1, 'one floating nodes');
+is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
+
+my $third = open_window($x);
+
+
+$ws = get_ws($tmp);
+is(@{$ws->{floating_nodes}}, 1, 'one floating nodes');
+is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
+
+done_testing;
diff --git a/testcases/t/139-ws-numbers.t b/testcases/t/139-ws-numbers.t
new file mode 100644 (file)
index 0000000..31f013e
--- /dev/null
@@ -0,0 +1,60 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Check if numbered workspaces and named workspaces are sorted in the right way
+# in get_workspaces IPC output (necessary for i3bar etc.).
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+my $x = X11::XCB::Connection->new;
+
+sub check_order {
+    my ($msg) = @_;
+
+    my @ws = @{$i3->get_workspaces->recv};
+    my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
+    my @sorted = sort @nums;
+
+    cmp_deeply(\@nums, \@sorted, $msg);
+}
+
+check_order('workspace order alright before testing');
+
+#############################################################################
+# open a window to keep this ws open
+#############################################################################
+
+cmd "workspace 93";
+
+open_window($x);
+
+my @ws = @{$i3->get_workspaces->recv};
+my @f = grep { defined($_->{num}) && $_->{num} == 93 } @ws;
+is(@f, 1, 'ws 93 found by num');
+check_order('workspace order alright after opening 93');
+
+cmd "workspace 92";
+open_window($x);
+check_order('workspace order alright after opening 92');
+
+cmd "workspace 94";
+open_window($x);
+check_order('workspace order alright after opening 94');
+
+cmd "workspace 96";
+open_window($x);
+check_order('workspace order alright after opening 96');
+
+cmd "workspace foo";
+open_window($x);
+check_order('workspace order alright after opening foo');
+
+cmd "workspace 91";
+open_window($x);
+check_order('workspace order alright after opening 91');
+
+done_testing;
diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t
deleted file mode 100644 (file)
index 98978eb..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-####################################################################################
-# first part: test if a floating window will be correctly positioned above its leader
-#
-# This is verified by opening two windows, then opening a floating window above the
-# right one, then above the left one. If the floating windows are all positioned alike,
-# one of both (depending on your screen resolution) will be positioned wrong.
-####################################################################################
-
-my $left = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [0, 0, 30, 30],
-    background_color => '#FF0000',
-);
-
-$left->name('Left');
-$left->map;
-
-my $right = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [0, 0, 30, 30],
-    background_color => '#FF0000',
-);
-
-$right->name('Right');
-$right->map;
-
-sleep 0.25;
-
-my ($abs, $rgeom) = $right->rect;
-
-my $child = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#C0C0C0',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-$child->name('Child window');
-$child->client_leader($right);
-$child->map;
-
-sleep 0.25;
-
-my $cgeom;
-($abs, $cgeom) = $child->rect;
-cmp_ok($cgeom->x, '>=', $rgeom->x, 'Child X >= right container X');
-
-my $child2 = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#C0C0C0',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-$child2->name('Child window 2');
-$child2->client_leader($left);
-$child2->map;
-
-sleep 0.25;
-
-($abs, $cgeom) = $child2->rect;
-cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window');
-
-# check wm_transient_for
-
-
-my $fwindow = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#FF0000',
-);
-
-$fwindow->transient_for($right);
-$fwindow->map;
-
-sleep 0.25;
-
-my ($absolute, $top) = $fwindow->rect;
-ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
-
-SKIP: {
-    skip "(workspace placement by client_leader not yet implemented)", 3;
-
-#####################################################################
-# Create a parent window
-#####################################################################
-
-my $window = $x->root->create_child(
-class => WINDOW_CLASS_INPUT_OUTPUT,
-rect => [ 0, 0, 30, 30 ],
-background_color => '#C0C0C0',
-);
-
-$window->name('Parent window');
-$window->map;
-
-sleep 0.25;
-
-#########################################################################
-# Switch to a different workspace and open a child window. It should be opened
-# on the old workspace.
-#########################################################################
-fresh_workspace;
-
-my $child = $x->root->create_child(
-class => WINDOW_CLASS_INPUT_OUTPUT,
-rect => [ 0, 0, 30, 30 ],
-background_color => '#C0C0C0',
-);
-
-$child->name('Child window');
-$child->client_leader($window);
-$child->map;
-
-sleep 0.25;
-
-isnt($x->input_focus, $child->id, "Child window focused");
-
-# Switch back
-cmd "workspace $tmp";
-
-is($x->input_focus, $child->id, "Child window focused");
-
-}
-
-done_testing;
diff --git a/testcases/t/140-focus-lost.t b/testcases/t/140-focus-lost.t
new file mode 100644 (file)
index 0000000..fb77f01
--- /dev/null
@@ -0,0 +1,45 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Regression: Check if the focus stays the same when switching the layout
+# bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+my $x = X11::XCB::Connection->new;
+
+sub check_order {
+    my ($msg) = @_;
+
+    my @ws = @{$i3->get_workspaces->recv};
+    my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
+    my @sorted = sort @nums;
+
+    cmp_deeply(\@nums, \@sorted, $msg);
+}
+
+my $tmp = fresh_workspace;
+
+my $left = open_window($x);
+my $mid = open_window($x);
+my $right = open_window($x);
+
+sync_with_i3($x);
+
+diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id);
+
+is($x->input_focus, $right->id, 'Right window focused');
+
+cmd 'focus left';
+
+is($x->input_focus, $mid->id, 'Mid window focused');
+
+cmd 'layout stacked';
+
+is($x->input_focus, $mid->id, 'Mid window focused');
+
+done_testing;
diff --git a/testcases/t/141-resize.t b/testcases/t/141-resize.t
new file mode 100644 (file)
index 0000000..8691a04
--- /dev/null
@@ -0,0 +1,143 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Tests resizing tiling containers
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+cmd 'split v';
+
+my $top = open_window($x);
+my $bottom = open_window($x);
+
+sync_with_i3($x);
+
+diag("top = " . $top->id . ", bottom = " . $bottom->id);
+
+is($x->input_focus, $bottom->id, 'Bottom window focused');
+
+############################################################
+# resize
+############################################################
+
+cmd 'resize grow up 10 px or 25 ppt';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+is($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
+is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+
+
+############################################################
+# split and check if the 'percent' factor is still correct
+############################################################
+
+cmd 'split h';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+is($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
+is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+
+############################################################
+# checks that resizing within stacked/tabbed cons works
+############################################################
+
+$tmp = fresh_workspace;
+
+cmd 'split v';
+
+$top = open_window($x);
+$bottom = open_window($x);
+
+cmd 'split h';
+cmd 'layout stacked';
+
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[0]->{percent}, 0.5, 'top window got 50%');
+is($nodes->[1]->{percent}, 0.5, 'bottom window got 50%');
+
+cmd 'resize grow up 10 px or 25 ppt';
+
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[0]->{percent}, 0.25, 'top window got 25%');
+is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+
+############################################################
+# checks that resizing floating windows works
+############################################################
+
+$tmp = fresh_workspace;
+
+$top = open_window($x);
+
+cmd 'floating enable';
+
+my @content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok(@content, '==', 1, 'one floating node on this ws');
+
+# up
+my $oldrect = $content[0]->{rect};
+
+cmd 'resize grow up 10 px or 25 ppt';
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{y}, '<', $oldrect->{y}, 'y smaller than before');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y} - 10, 'y exactly 10 px smaller');
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{height}, '>', $oldrect->{height}, 'height bigger than before');
+cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height} + 10, 'height exactly 10 px higher');
+cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'x untouched');
+
+# up, but with a different amount of px
+$oldrect = $content[0]->{rect};
+
+cmd 'resize grow up 12 px or 25 ppt';
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{y}, '<', $oldrect->{y}, 'y smaller than before');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y} - 12, 'y exactly 10 px smaller');
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{height}, '>', $oldrect->{height}, 'height bigger than before');
+cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height} + 12, 'height exactly 10 px higher');
+cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'x untouched');
+
+# left
+$oldrect = $content[0]->{rect};
+
+cmd 'resize grow left 10 px or 25 ppt';
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '<', $oldrect->{x}, 'x smaller than before');
+cmp_ok($content[0]->{rect}->{width}, '>', $oldrect->{width}, 'width bigger than before');
+
+# right
+$oldrect = $content[0]->{rect};
+
+cmd 'resize grow right 10 px or 25 ppt';
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x the same as before');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y the same as before');
+cmp_ok($content[0]->{rect}->{width}, '>', $oldrect->{width}, 'width bigger than before');
+cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height}, 'height the same as before');
+
+# down
+$oldrect = $content[0]->{rect};
+
+cmd 'resize grow down 10 px or 25 ppt';
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x the same as before');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y the same as before');
+cmp_ok($content[0]->{rect}->{height}, '>', $oldrect->{height}, 'height bigger than before');
+cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'width the same as before');
+
+done_testing;
diff --git a/testcases/t/142-regress-move-floating.t b/testcases/t/142-regress-move-floating.t
new file mode 100644 (file)
index 0000000..6b2df80
--- /dev/null
@@ -0,0 +1,17 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: move a floating window to a different workspace crashes i3
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+my $otmp = get_unused_workspace();
+
+cmd 'open';
+cmd 'mode toggle';
+cmd "move workspace $otmp";
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/143-regress-floating-restart.t b/testcases/t/143-regress-floating-restart.t
new file mode 100644 (file)
index 0000000..babbb57
--- /dev/null
@@ -0,0 +1,23 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: floating windows are tiling after restarting, closing them crashes i3
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+cmd 'open';
+cmd 'mode toggle';
+cmd 'restart';
+
+sleep 0.5;
+
+diag('Checking if i3 still lives');
+
+does_i3_live;
+
+my $ws = get_ws($tmp);
+diag('ws = ' . Dumper($ws));
+
+done_testing;
diff --git a/testcases/t/144-regress-floating-resize.t b/testcases/t/144-regress-floating-resize.t
new file mode 100644 (file)
index 0000000..de33eeb
--- /dev/null
@@ -0,0 +1,37 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: when resizing two containers on a workspace, opening a floating
+# client, then closing it again, i3 will re-distribute the space on the
+# workspace as if a tiling container was closed, leading to the containers
+# taking much more space than they possibly could.
+#
+use i3test;
+use List::Util qw(sum);
+
+my $tmp = fresh_workspace;
+
+cmd 'exec /usr/bin/urxvt';
+sleep 0.5;
+cmd 'exec /usr/bin/urxvt';
+sleep 0.5;
+my ($nodes, $focus) = get_ws_content($tmp);
+my $old_sum = sum map { $_->{rect}->{width} } @{$nodes};
+#cmd 'open';
+cmd 'resize grow left 10 px or 25 ppt';
+cmd 'split v';
+#cmd 'open';
+cmd 'exec /usr/bin/urxvt';
+sleep 0.5;
+cmd 'mode toggle';
+sleep 0.5;
+cmd 'kill';
+
+sleep 0.5;
+
+($nodes, $focus) = get_ws_content($tmp);
+my $new_sum = sum map { $_->{rect}->{width} } @{$nodes};
+
+is($old_sum, $new_sum, 'combined container width is still equal');
+
+done_testing;
diff --git a/testcases/t/145-flattening.t b/testcases/t/145-flattening.t
new file mode 100644 (file)
index 0000000..904252e
--- /dev/null
@@ -0,0 +1,31 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# by moving the window in the opposite orientation that its parent has, we
+# force i3 to create a new split container with the appropriate orientation.
+# However, when doing that two times in a row, we end up with two split
+# containers which are then redundant (workspace is horizontal, then v-split,
+# then h-split – we could just append the children of the latest h-split to the
+# workspace itself).
+#
+# This testcase checks that the tree is properly flattened after moving.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+my $left = open_window($x);
+my $mid = open_window($x);
+my $right = open_window($x);
+
+cmd 'move before v';
+cmd 'move after h';
+my $ws = get_ws($tmp);
+
+is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal');
+is(@{$ws->{nodes}}, 3, 'all three windows on workspace level');
+
+done_testing;
diff --git a/testcases/t/146-floating-reinsert.t b/testcases/t/146-floating-reinsert.t
new file mode 100644 (file)
index 0000000..bc1302b
--- /dev/null
@@ -0,0 +1,44 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+my $left = open_window($x);
+my $mid = open_window($x);
+
+cmd 'split v';
+my $bottom = open_window($x);
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+#############################################################################
+# 1: open a floating window, get it mapped
+#############################################################################
+
+# Create a floating window
+my $window = open_floating_window($x);
+ok($window->mapped, 'Window is mapped');
+
+($nodes, $focus) = get_ws_content($tmp);
+is(@{$nodes->[1]->{nodes}}, 2, 'two windows in split con');
+
+#############################################################################
+# 2: make it tiling, see where it ends up
+#############################################################################
+
+cmd 'floating toggle';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+is(@{$nodes->[1]->{nodes}}, 3, 'three windows in split con after floating toggle');
+
+done_testing;
diff --git a/testcases/t/147-regress-floatingmove.t b/testcases/t/147-regress-floatingmove.t
new file mode 100644 (file)
index 0000000..771ace3
--- /dev/null
@@ -0,0 +1,44 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for moving a con outside of a floating con when there are no
+# tiling cons on a workspace
+#
+use X11::XCB qw(:all);
+use Time::HiRes qw(sleep);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+my $left = open_window($x);
+my $mid = open_window($x);
+my $right = open_window($x);
+
+# go to workspace level
+cmd 'level up';
+sleep 0.25;
+
+# make it floating
+cmd 'mode toggle';
+sleep 0.25;
+
+# move the con outside the floating con
+cmd 'move before v';
+sleep 0.25;
+
+does_i3_live;
+
+# move another con outside
+cmd '[id="' . $mid->id . '"] focus';
+cmd 'move before v';
+sleep 0.25;
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/148-regress-floatingmovews.t b/testcases/t/148-regress-floatingmovews.t
new file mode 100644 (file)
index 0000000..6f9dfc4
--- /dev/null
@@ -0,0 +1,38 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for correct focus behaviour when moving a floating con to
+# another workspace.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+# open a tiling window on the first workspace
+open_window($x);
+#sleep 0.25;
+my $first = get_focused($tmp);
+
+# on a different ws, open a floating window
+my $otmp = fresh_workspace;
+open_window($x);
+#sleep 0.25;
+my $float = get_focused($otmp);
+cmd 'mode toggle';
+#sleep 0.25;
+
+# move the floating con to first workspace
+cmd "move workspace $tmp";
+#sleep 0.25;
+
+# switch to the first ws and check focus
+is(get_focused($tmp), $float, 'floating client correctly focused');
+
+done_testing;
diff --git a/testcases/t/15-ipc-workspaces.t b/testcases/t/15-ipc-workspaces.t
deleted file mode 100644 (file)
index 085163b..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use List::MoreUtils qw(all);
-
-my $i3 = i3(get_socket_path());
-
-####################
-# Request workspaces
-####################
-
-SKIP: {
-    skip "IPC API not yet stabilized", 2;
-
-my $workspaces = $i3->get_workspaces->recv;
-
-ok(@{$workspaces} > 0, "More than zero workspaces found");
-
-my $name_exists = all { defined($_->{name}) } @{$workspaces};
-ok($name_exists, "All workspaces have a name");
-
-}
-
-done_testing;
diff --git a/testcases/t/150-regress-dock-restart.t b/testcases/t/150-regress-dock-restart.t
new file mode 100644 (file)
index 0000000..e294e6b
--- /dev/null
@@ -0,0 +1,97 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for inplace restarting with dock clients
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# verify that there is no dock window yet
+#####################################################################
+
+# Children of all dockareas
+my @docked = get_dock_clients;
+
+is(@docked, 0, 'no dock clients yet');
+
+# open a dock client
+
+my $window = open_window($x, {
+        background_color => '#FF0000',
+        window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+    });
+
+#####################################################################
+# check that we can find it in the layout tree at the expected position
+#####################################################################
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+
+# verify the height
+my $docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
+
+# perform an inplace-restart
+cmd 'restart';
+
+sleep 0.25;
+
+does_i3_live;
+
+
+#####################################################################
+# check that we can still find the dock client
+#####################################################################
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+$docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 30, 'dock node has unchanged height after restart');
+
+$window->destroy;
+
+wait_for_unmap $x;
+
+@docked = get_dock_clients;
+is(@docked, 0, 'no dock clients found');
+
+#####################################################################
+# create a dock client with a 1px border
+#####################################################################
+
+$window = open_window($x, {
+        border => 1,
+        rect => [ 0, 0, 30, 20 ],
+        background_color => '#00FF00',
+        window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+    });
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+$docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 20, 'dock node has unchanged height');
+
+cmd 'restart';
+sleep 0.25;
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+$docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 20, 'dock node has unchanged height');
+
+
+done_testing;
diff --git a/testcases/t/151-regress-float-size.t b/testcases/t/151-regress-float-size.t
new file mode 100644 (file)
index 0000000..881ef8c
--- /dev/null
@@ -0,0 +1,17 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for setting a window to floating, tiling and opening a new window
+#
+use i3test;
+
+fresh_workspace;
+
+cmd 'open';
+cmd 'mode toggle';
+cmd 'mode toggle';
+cmd 'open';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/152-regress-level-up.t b/testcases/t/152-regress-level-up.t
new file mode 100644 (file)
index 0000000..678cdd9
--- /dev/null
@@ -0,0 +1,18 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for using level-up to get to the 'content'-container and
+# toggle floating
+#
+use i3test;
+
+fresh_workspace;
+
+cmd 'open';
+cmd 'level up';
+cmd 'level up';
+cmd 'mode toggle';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/153-floating-originalsize.t b/testcases/t/153-floating-originalsize.t
new file mode 100644 (file)
index 0000000..db0b6e9
--- /dev/null
@@ -0,0 +1,40 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Test if the requested width/height is set after making the window floating.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+my $tmp = fresh_workspace;
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = open_window($x, { rect => [ 0, 0, 400, 150 ] });
+
+my ($absolute, $top) = $window->rect;
+
+ok($window->mapped, 'Window is mapped');
+cmp_ok($absolute->{width}, '>', 400, 'i3 raised the width');
+cmp_ok($absolute->{height}, '>', 150, 'i3 raised the height');
+
+cmd 'floating toggle';
+sync_with_i3($x);
+
+($absolute, $top) = $window->rect;
+
+diag('new width: ' . $absolute->{width});
+diag('new height: ' . $absolute->{height});
+
+# we compare with a tolerance of ± 20 pixels for borders in each direction
+# (overkill, but hey)
+cmp_ok($absolute->{width}, '>', 400-20, 'width now > 380');
+cmp_ok($absolute->{width}, '<', 400+20, 'width now < 420');
+cmp_ok($absolute->{height}, '>', 150-20, 'height now > 130');
+cmp_ok($absolute->{height}, '<', 150+20, 'height now < 170');
+
+#cmp_ok($absolute->{width}, '>=', 75, 'i3 raised the width to 75');
+#cmp_ok($absolute->{height}, '>=', 50, 'i3 raised the height to 50');
+
+done_testing;
diff --git a/testcases/t/154-regress-multiple-dock.t b/testcases/t/154-regress-multiple-dock.t
new file mode 100644 (file)
index 0000000..8b7f456
--- /dev/null
@@ -0,0 +1,57 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for closing one of multiple dock clients
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# verify that there is no dock window yet
+#####################################################################
+
+# Children of all dockareas
+my @docked = get_dock_clients;
+
+is(@docked, 0, 'no dock clients yet');
+
+#####################################################################
+# open a dock client
+#####################################################################
+
+my $first = open_window($x, {
+        background_color => '#FF0000',
+        window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+    });
+
+#####################################################################
+# Open a second dock client
+#####################################################################
+
+my $second = open_window($x, {
+        background_color => '#FF0000',
+        window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+    });
+
+#####################################################################
+# Kill the second dock client
+#####################################################################
+cmd "nop destroying dock client";
+$second->destroy;
+
+#####################################################################
+# Now issue a focus command
+#####################################################################
+cmd 'focus right';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/155-floating-split-size.t b/testcases/t/155-floating-split-size.t
new file mode 100644 (file)
index 0000000..5de05e8
--- /dev/null
@@ -0,0 +1,57 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Test to see if i3 combines the geometry of all children in a split container
+# when setting the split container to floating
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# open a window with 200x80
+#####################################################################
+
+my $first = open_window($x, {
+        rect => [ 0, 0, 200, 80],
+        background_color => '#FF0000',
+    });
+
+#####################################################################
+# Open a second window with 300x90
+#####################################################################
+
+my $second = open_window($x, {
+        rect => [ 0, 0, 300, 90],
+        background_color => '#00FF00',
+    });
+
+#####################################################################
+# Set the parent to floating
+#####################################################################
+cmd 'nop setting floating';
+cmd 'focus parent';
+cmd 'floating enable';
+
+#####################################################################
+# Get geometry of the first floating node (the split container)
+#####################################################################
+
+my @nodes = @{get_ws($tmp)->{floating_nodes}};
+my $rect = $nodes[0]->{rect};
+
+# we compare the width with ± 20 pixels for borders
+cmp_ok($rect->{width}, '>', 500-20, 'width now > 480');
+cmp_ok($rect->{width}, '<', 500+20, 'width now < 520');
+# we compare the height with ± 40 pixels for decorations
+cmp_ok($rect->{height}, '>', 90-40, 'width now > 50');
+cmp_ok($rect->{height}, '<', 90+40, 'width now < 130');
+
+done_testing;
diff --git a/testcases/t/156-fullscreen-focus.t b/testcases/t/156-fullscreen-focus.t
new file mode 100644 (file)
index 0000000..a559b5a
--- /dev/null
@@ -0,0 +1,71 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Test if new containers get focused when there is a fullscreen container at
+# the time of launching the new one.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# open the left window
+#####################################################################
+
+my $left = open_window($x, { background_color => '#ff0000' });
+
+is($x->input_focus, $left->id, 'left window focused');
+
+diag("left = " . $left->id);
+
+#####################################################################
+# Open the right window
+#####################################################################
+
+my $right = open_window($x, { background_color => '#00ff00' });
+
+diag("right = " . $right->id);
+
+#####################################################################
+# Set the right window to fullscreen
+#####################################################################
+cmd 'nop setting fullscreen';
+cmd 'fullscreen';
+
+#####################################################################
+# Open a third window
+#####################################################################
+
+my $third = open_window($x, {
+        background_color => '#0000ff',
+        name => 'Third window',
+        dont_map => 1,
+    });
+
+$third->map;
+
+sync_with_i3 $x;
+
+diag("third = " . $third->id);
+
+# move the fullscreen window to a different ws
+
+my $tmp2 = get_unused_workspace;
+
+cmd "move workspace $tmp2";
+
+# verify that the third window has the focus
+
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'third window focused');
+
+done_testing;
diff --git a/testcases/t/157-regress-fullscreen-level-up.t b/testcases/t/157-regress-fullscreen-level-up.t
new file mode 100644 (file)
index 0000000..7a101db
--- /dev/null
@@ -0,0 +1,48 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test: level up should be a noop during fullscreen mode
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# open a window, verify it’s not in fullscreen mode
+#####################################################################
+
+my $win = open_window($x);
+
+my $nodes = get_ws_content $tmp;
+is(@$nodes, 1, 'exactly one client');
+is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen');
+
+#####################################################################
+# make it fullscreen
+#####################################################################
+
+cmd 'nop making fullscreen';
+cmd 'fullscreen';
+
+my $nodes = get_ws_content $tmp;
+is($nodes->[0]->{fullscreen_mode}, 1, 'client fullscreen now');
+
+#####################################################################
+# send level up, try to un-fullscreen
+#####################################################################
+cmd 'level up';
+cmd 'fullscreen';
+
+my $nodes = get_ws_content $tmp;
+is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen any longer');
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/158-wm_take_focus.t b/testcases/t/158-wm_take_focus.t
new file mode 100644 (file)
index 0000000..a90ce1c
--- /dev/null
@@ -0,0 +1,44 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if the WM_TAKE_FOCUS protocol is correctly handled by i3
+#
+use X11::XCB qw(:all);
+use i3test;
+use v5.10;
+
+my $x = X11::XCB::Connection->new;
+
+subtest 'Window without WM_TAKE_FOCUS', sub {
+    fresh_workspace;
+
+    my $window = open_window($x);
+
+    ok(!wait_for_event($x, 1, sub { $_[0]->{response_type} == 161 }), 'did not receive ClientMessage');
+
+    done_testing;
+};
+
+subtest 'Window with WM_TAKE_FOCUS', sub {
+    fresh_workspace;
+
+    my $take_focus = $x->atom(name => 'WM_TAKE_FOCUS');
+
+    my $window = open_window($x, {
+        dont_map => 1,
+        protocols => [ $take_focus ],
+    });
+
+    $window->map;
+
+    ok(wait_for_event($x, 1, sub {
+        return 0 unless $_[0]->{response_type} == 161;
+        my ($data, $time) = unpack("L2", $_[0]->{data});
+        return ($data == $take_focus->id);
+    }), 'got ClientMessage with WM_TAKE_FOCUS atom');
+
+    done_testing;
+};
+
+
+done_testing;
diff --git a/testcases/t/159-socketpaths.t b/testcases/t/159-socketpaths.t
new file mode 100644 (file)
index 0000000..eb6bd79
--- /dev/null
@@ -0,0 +1,70 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests if the various ipc_socket_path options are correctly handled
+#
+use i3test;
+use File::Temp qw(tempfile tempdir);
+use POSIX qw(getuid);
+use v5.10;
+
+#####################################################################
+# default case: socket will be created in /tmp/i3-<username>/ipc-socket.<pid>
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+# ensure XDG_RUNTIME_DIR is not set
+delete $ENV{XDG_RUNTIME_DIR};
+my $pid = launch_with_config($config, 1);
+
+my $folder = "/tmp/i3-" . getpwuid(getuid());
+ok(-d $folder, "folder $folder exists");
+my $socketpath = "$folder/ipc-socket." . $pid;
+ok(-S $socketpath, "file $socketpath exists and is a socket");
+
+exit_gracefully($pid);
+
+#####################################################################
+# XDG_RUNTIME_DIR case: socket gets created in $XDG_RUNTIME_DIR/i3/ipc-socket.<pid>
+#####################################################################
+
+my $rtdir = tempdir(CLEANUP => 1);
+
+ok(! -e "$rtdir/i3", "$rtdir/i3 does not exist yet");
+
+$ENV{XDG_RUNTIME_DIR} = $rtdir;
+
+$pid = launch_with_config($config, 1);
+
+ok(-d "$rtdir/i3", "$rtdir/i3 exists and is a directory");
+$socketpath = "$rtdir/i3/ipc-socket." . $pid;
+ok(-S $socketpath, "file $socketpath exists and is a socket");
+
+exit_gracefully($pid);
+
+#####################################################################
+# configuration file case: socket gets placed whereever we specify
+#####################################################################
+
+my $tmpdir = tempdir(CLEANUP => 1);
+$socketpath = $tmpdir . "/config.sock";
+ok(! -e $socketpath, "$socketpath does not exist yet");
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+ipc-socket $socketpath
+EOT
+
+$pid = launch_with_config($config, 1);
+
+ok(-S $socketpath, "file $socketpath exists and is a socket");
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t
deleted file mode 100644 (file)
index f9d2726..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use List::MoreUtils qw(all none);
-use List::Util qw(first);
-
-my $i3 = i3(get_socket_path());
-
-####################
-# Request tree
-####################
-
-my $tree = $i3->get_tree->recv;
-
-my $expected = {
-    fullscreen_mode => 0,
-    nodes => ignore(),
-    window => undef,
-    name => 'root',
-    orientation => ignore(),
-    type => 0,
-    id => ignore(),
-    rect => ignore(),
-    window_rect => ignore(),
-    geometry => ignore(),
-    swallows => ignore(),
-    percent => undef,
-    layout => 'default',
-    focus => ignore(),
-    focused => JSON::XS::false,
-    urgent => JSON::XS::false,
-    border => 'normal',
-    'floating_nodes' => ignore(),
-};
-
-cmp_deeply($tree, $expected, 'root node OK');
-
-my @nodes = @{$tree->{nodes}};
-
-ok(@nodes > 0, 'root node has at least one leaf');
-
-ok((all { $_->{type} == 1 } @nodes), 'all nodes are of type CT_OUTPUT');
-ok((none { defined($_->{window}) } @nodes), 'no CT_OUTPUT contains a window');
-ok((all { @{$_->{nodes}} > 0 } @nodes), 'all nodes have at least one leaf (workspace)');
-my @workspaces;
-for my $ws (@nodes) {
-    my $content = first { $_->{type} == 2 } @{$ws->{nodes}};
-    @workspaces = (@workspaces, @{$content->{nodes}});
-}
-
-ok((all { $_->{type} == 4 } @workspaces), 'all workspaces are of type CT_WORKSPACE');
-#ok((all { @{$_->{nodes}} == 0 } @workspaces), 'all workspaces are empty yet');
-ok((none { defined($_->{window}) } @workspaces), 'no CT_OUTPUT contains a window');
-
-# TODO: get the focused container
-
-$i3->command('open')->recv;
-
-# TODO: get the focused container, check if it changed.
-# TODO: get the old focused container, check if there is a new child
-
-#diag(Dumper(\@workspaces));
-
-#diag(Dumper($tree));
-
-
-done_testing;
diff --git a/testcases/t/161-regress-borders-restart.t b/testcases/t/161-regress-borders-restart.t
new file mode 100644 (file)
index 0000000..c5e3ef8
--- /dev/null
@@ -0,0 +1,39 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test to check if borders are correctly restored after an inplace
+# restart.
+# found in eb8ad348b28e243cba1972e802ca8ee636472fc9
+#
+use X11::XCB qw(:all);
+use List::Util qw(first);
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+my $tmp = fresh_workspace;
+my $window = open_window($x);
+
+sub get_border_style {
+    my @content = @{get_ws_content($tmp)};
+    my $wininfo = first { $_->{window} == $window->id } @content;
+
+    return $wininfo->{border};
+}
+
+is(get_border_style(), 'normal', 'border style normal');
+
+cmd 'border 1pixel';
+
+is(get_border_style(), '1pixel', 'border style 1pixel after changing');
+
+# perform an inplace-restart
+cmd 'restart';
+
+sleep 0.25;
+
+does_i3_live;
+
+is(get_border_style(), '1pixel', 'border style still 1pixel after restart');
+
+done_testing;
diff --git a/testcases/t/162-regress-dock-urgent.t b/testcases/t/162-regress-dock-urgent.t
new file mode 100644 (file)
index 0000000..5fb8812
--- /dev/null
@@ -0,0 +1,59 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for setting the urgent hint on dock clients.
+# found in 4be3178d4d360c2996217d811e61161c84d25898
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# verify that there is no dock window yet
+#####################################################################
+
+# Children of all dockareas
+my @docked = get_dock_clients;
+
+is(@docked, 0, 'no dock clients yet');
+
+# open a dock client
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#FF0000',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+);
+
+$window->map;
+
+sleep 0.25;
+
+#####################################################################
+# check that we can find it in the layout tree at the expected position
+#####################################################################
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+
+# verify the height
+my $docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
+
+$window->add_hint('urgency');
+
+sync_with_i3($x);
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/163-wm-state.t b/testcases/t/163-wm-state.t
new file mode 100644 (file)
index 0000000..e55d868
--- /dev/null
@@ -0,0 +1,24 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
+# unmapped.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+my $window = open_window($x);
+
+sync_with_i3($x);
+
+is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal');
+
+$window->unmap;
+
+wait_for_unmap $x;
+
+is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn');
+
+done_testing;
diff --git a/testcases/t/164-kill-win-vs-client.t b/testcases/t/164-kill-win-vs-client.t
new file mode 100644 (file)
index 0000000..ef45a78
--- /dev/null
@@ -0,0 +1,66 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
+# unmapped.
+#
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+sub two_windows {
+    my $tmp = fresh_workspace;
+
+    ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+    my $first = open_window($x);
+    my $second = open_window($x);
+
+    sync_with_i3 $x;
+
+    is($x->input_focus, $second->id, 'second window focused');
+    ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+
+    return $tmp;
+}
+
+##############################################################
+# 1: open two windows (in the same client), kill one and see if
+# the other one is still there
+##############################################################
+
+my $tmp = two_windows;
+
+cmd 'kill';
+
+sleep 0.25;
+
+ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
+
+##############################################################
+# 2: same test case as test 1, but with the explicit variant
+# 'kill window'
+##############################################################
+
+my $tmp = two_windows;
+
+cmd 'kill window';
+
+sleep 0.25;
+
+ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
+
+##############################################################
+# 3: open two windows (in the same client), use 'kill client'
+# and check if both are gone
+##############################################################
+
+my $tmp = two_windows;
+
+cmd 'kill client';
+
+sleep 0.25;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers left after killing');
+
+done_testing;
diff --git a/testcases/t/165-for_window.t b/testcases/t/165-for_window.t
new file mode 100644 (file)
index 0000000..cc10013
--- /dev/null
@@ -0,0 +1,469 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+#
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+##############################################################
+# 1: test the following directive:
+#    for_window [class="borderless"] border none
+# by first creating a window with a different class (should get
+# the normal border), then creating a window with the class
+# "borderless" (should get no border)
+##############################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [class="borderless"] border none
+for_window [title="special borderless title"] border none
+EOT
+
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->name('Border window');
+$window->map;
+wait_for_map $x;
+
+my @content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border');
+
+$window->unmap;
+wait_for_unmap $x;
+
+my @content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 0, 'no more nodes');
+diag('content = '. Dumper(\@content));
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+# TODO: move this to X11::XCB::Window
+sub set_wm_class {
+    my ($id, $class, $instance) = @_;
+
+    # Add a _NET_WM_STRUT_PARTIAL hint
+    my $atomname = $x->atom(name => 'WM_CLASS');
+    my $atomtype = $x->atom(name => 'STRING');
+
+    $x->change_property(
+        PROP_MODE_REPLACE,
+        $id,
+        $atomname->id,
+        $atomtype->id,
+        8,
+        length($class) + length($instance) + 2,
+        "$instance\x00$class\x00"
+    );
+}
+
+set_wm_class($window->id, 'borderless', 'borderless');
+$window->name('Borderless window');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border');
+
+$window->unmap;
+wait_for_unmap $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 0, 'no more nodes');
+
+exit_gracefully($pid);
+
+##############################################################
+# 2: match on the title, check if for_window is really executed
+# only once
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [class="borderless"] border none
+for_window [title="special borderless title"] border none
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->name('special title');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border');
+
+$window->name('special borderless title');
+sync_with_i3 $x;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{border}, 'none', 'no border');
+
+$window->name('special title');
+sync_with_i3 $x;
+
+cmd 'border normal';
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{border}, 'normal', 'border reset to normal');
+
+$window->name('special borderless title');
+sync_with_i3 $x;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{border}, 'normal', 'still normal border');
+
+$window->unmap;
+wait_for_unmap $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 0, 'no more nodes');
+
+exit_gracefully($pid);
+
+##############################################################
+# 3: match on the title, set border style *and* a mark
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [class="borderless" title="usethis"] border none
+for_window [class="borderless"] border none
+for_window [title="special borderless title"] border none
+for_window [title="special mark title"] border none, mark bleh
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->name('special mark title');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border');
+
+my $other = open_window($x);
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 2, 'two nodes');
+is($content[0]->{border}, 'none', 'no border');
+is($content[1]->{border}, 'normal', 'normal border');
+ok(!$content[0]->{focused}, 'first one not focused');
+
+cmd qq|[con_mark="bleh"] focus|;
+
+@content = @{get_ws_content($tmp)};
+ok($content[0]->{focused}, 'first node focused');
+
+exit_gracefully($pid);
+
+##############################################################
+# 4: multiple criteria for the for_window command
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [class="borderless" title="usethis"] border none
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+set_wm_class($window->id, 'borderless', 'borderless');
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border');
+
+cmd 'kill';
+wait_for_unmap $x;
+$window->destroy;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 0, 'no nodes on this workspace now');
+
+$window->_create;
+
+set_wm_class($window->id, 'borderless', 'borderless');
+$window->name('notthis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'no border');
+
+
+exit_gracefully($pid);
+
+##############################################################
+# 5: check that a class criterion does not match the instance
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [class="foo"] border 1pixel
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+set_wm_class($window->id, 'bar', 'foo');
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border, not matched');
+
+exit_gracefully($pid);
+
+##############################################################
+# 6: check that the 'instance' criterion works
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [class="foo"] border 1pixel
+for_window [instance="foo"] border none
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+set_wm_class($window->id, 'bar', 'foo');
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border');
+
+exit_gracefully($pid);
+
+##############################################################
+# 7: check that invalid criteria don’t end up matching all windows
+##############################################################
+
+# this configuration is broken because "asdf" is not a valid integer
+# the for_window should therefore recognize this error and don’t add the
+# assignment
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [id="asdf"] border none
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+set_wm_class($window->id, 'bar', 'foo');
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border');
+
+exit_gracefully($pid);
+
+##############################################################
+# 8: check that the role criterion works properly
+##############################################################
+
+# this configuration is broken because "asdf" is not a valid integer
+# the for_window should therefore recognize this error and don’t add the
+# assignment
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [window_role="i3test"] border none
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+my $atomname = $x->atom(name => 'WM_WINDOW_ROLE');
+my $atomtype = $x->atom(name => 'STRING');
+$x->change_property(
+  PROP_MODE_REPLACE,
+  $window->id,
+  $atomname->id,
+  $atomtype->id,
+  8,
+  length("i3test") + 1,
+  "i3test\x00"
+);
+
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border (window_role)');
+
+exit_gracefully($pid);
+
+##############################################################
+# 9: another test for the window_role, but this time it changes
+#    *after* the window has been mapped
+##############################################################
+
+# this configuration is broken because "asdf" is not a valid integer
+# the for_window should therefore recognize this error and don’t add the
+# assignment
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [window_role="i3test"] border none
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border (window_role 2)');
+
+$atomname = $x->atom(name => 'WM_WINDOW_ROLE');
+$atomtype = $x->atom(name => 'STRING');
+$x->change_property(
+  PROP_MODE_REPLACE,
+  $window->id,
+  $atomname->id,
+  $atomtype->id,
+  8,
+  length("i3test") + 1,
+  "i3test\x00"
+);
+
+$x->flush;
+
+sync_with_i3 $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border (window_role 2)');
+
+exit_gracefully($pid);
+
+
+done_testing;
diff --git a/testcases/t/166-assign.t b/testcases/t/166-assign.t
new file mode 100644 (file)
index 0000000..4844f5b
--- /dev/null
@@ -0,0 +1,286 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests if assignments work
+#
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use v5.10;
+
+my $x = X11::XCB::Connection->new;
+
+# TODO: move to X11::XCB
+sub set_wm_class {
+    my ($id, $class, $instance) = @_;
+
+    # Add a _NET_WM_STRUT_PARTIAL hint
+    my $atomname = $x->atom(name => 'WM_CLASS');
+    my $atomtype = $x->atom(name => 'STRING');
+
+    $x->change_property(
+        PROP_MODE_REPLACE,
+        $id,
+        $atomname->id,
+        $atomtype->id,
+        8,
+        length($class) + length($instance) + 2,
+        "$instance\x00$class\x00"
+    );
+}
+
+
+#####################################################################
+# start a window and see that it does not get assigned with an empty config
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+wait_for_map $x;
+
+ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace');
+
+exit_gracefully($pid);
+
+$window->destroy;
+
+#####################################################################
+# start a window and see that it gets assigned to a formerly unused
+# workspace
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign "special" → targetws
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+my $workspaces = get_workspace_names;
+ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+wait_for_map $x;
+
+ok(@{get_ws_content($tmp)} == 0, 'still no containers');
+ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists');
+
+$window->destroy;
+
+exit_gracefully($pid);
+
+sleep 0.25;
+
+#####################################################################
+# start a window and see that it gets assigned to a workspace which has content
+# already, next to the existing node.
+#####################################################################
+
+$pid = launch_with_config($config);
+
+# initialize the target workspace, then go to a fresh one
+ok(!("targetws" ~~ @{get_workspace_names()}), 'targetws does not exist yet');
+cmd 'workspace targetws';
+cmp_ok(@{get_ws_content('targetws')}, '==', 0, 'no containers on targetws yet');
+cmd 'open';
+cmp_ok(@{get_ws_content('targetws')}, '==', 1, 'one container on targetws');
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+ok("targetws" ~~ @{get_workspace_names()}, 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+
+# We use sync_with_i3 instead of wait_for_map here because i3 will not actually
+# map the window -- it will be assigned to a different workspace and will only
+# be mapped once you switch to that workspace
+sync_with_i3 $x;
+
+ok(@{get_ws_content($tmp)} == 0, 'still no containers');
+ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws');
+
+exit_gracefully($pid);
+
+#####################################################################
+# start a window and see that it gets assigned to a workspace which has content
+# already, next to the existing node.
+#####################################################################
+
+$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');
+my $workspaces = get_workspace_names;
+ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+wait_for_map $x;
+
+my $content = get_ws($tmp);
+ok(@{$content->{nodes}} == 0, 'no tiling cons');
+ok(@{$content->{floating_nodes}} == 1, 'one floating con');
+
+$window->destroy;
+
+exit_gracefully($pid);
+
+sleep 0.25;
+
+#####################################################################
+# 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');
+my $workspaces = get_workspace_names;
+ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'SPEcial', 'SPEcial');
+$window->name('special window');
+$window->map;
+wait_for_map $x;
+
+my $content = get_ws($tmp);
+ok(@{$content->{nodes}} == 0, 'no tiling cons');
+ok(@{$content->{floating_nodes}} == 1, 'one floating con');
+
+$window->destroy;
+
+exit_gracefully($pid);
+
+sleep 0.25;
+
+#####################################################################
+# regression test: dock clients with floating assignments should not crash
+# (instead, nothing should happen - dock clients can’t float)
+# ticket #501
+#####################################################################
+
+$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);
+
+# TODO: replace this with checking the process hierarchy
+# XXX: give i3-nagbar some time to start up
+sleep 1;
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+my @docked = get_dock_clients;
+# We expect i3-nagbar as the first dock client due to using the old assign
+# syntax
+is(@docked, 1, 'one dock client yet');
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+wait_for_map $x;
+
+my $content = get_ws($tmp);
+ok(@{$content->{nodes}} == 0, 'no tiling cons');
+ok(@{$content->{floating_nodes}} == 0, 'one floating con');
+@docked = get_dock_clients;
+is(@docked, 2, 'two dock clients now');
+
+$window->destroy;
+
+does_i3_live;
+
+exit_gracefully($pid);
+
+sleep 0.25;
+
+done_testing;
diff --git a/testcases/t/167-workspace_layout.t b/testcases/t/167-workspace_layout.t
new file mode 100644 (file)
index 0000000..e4b18ad
--- /dev/null
@@ -0,0 +1,127 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests the workspace_layout config option.
+#
+
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+#####################################################################
+# 1: check that with an empty config, cons are place next to each
+# other and no split containers are created
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_window($x);
+my $second = open_window($x);
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second window focused');
+ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
+isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 2: set workspace_layout stacked, check that when opening two cons,
+# they end up in a stacked con
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+workspace_layout stacked
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_window($x);
+$second = open_window($x);
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second window focused');
+my @content = @{get_ws_content($tmp)};
+ok(@content == 1, 'one con at workspace level');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+#####################################################################
+# 3: focus parent, open two new cons, check that they end up in a stacked
+# con
+#####################################################################
+
+cmd 'focus parent';
+my $right_top = open_window($x);
+my $right_bot = open_window($x);
+
+@content = @{get_ws_content($tmp)};
+is(@content, 2, 'two cons at workspace level after focus parent');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+is($content[1]->{layout}, 'stacked', 'layout stacked');
+
+#####################################################################
+# 4: move one of the cons to the right, check that it will end up in
+# a stacked con
+#####################################################################
+
+cmd 'move right';
+
+@content = @{get_ws_content($tmp)};
+is(@content, 3, 'three cons at workspace level after move');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+is($content[1]->{layout}, 'stacked', 'layout stacked');
+is($content[2]->{layout}, 'stacked', 'layout stacked');
+
+#####################################################################
+# 5: move it to the left again, check that the stacked con is deleted
+#####################################################################
+
+cmd 'move left';
+
+@content = @{get_ws_content($tmp)};
+is(@content, 2, 'two cons at workspace level after moving back');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+is($content[1]->{layout}, 'stacked', 'layout stacked');
+
+#####################################################################
+# 6: move it to a different workspace, check that it ends up in a
+# stacked con
+#####################################################################
+
+my $otmp = get_unused_workspace;
+
+cmd "move workspace $otmp";
+
+@content = @{get_ws_content($tmp)};
+is(@content, 2, 'still two cons on this workspace');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+is($content[1]->{layout}, 'stacked', 'layout stacked');
+
+@content = @{get_ws_content($otmp)};
+is(@content, 1, 'one con on target workspace');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/168-regress-fullscreen-restart.t b/testcases/t/168-regress-fullscreen-restart.t
new file mode 100644 (file)
index 0000000..1418b40
--- /dev/null
@@ -0,0 +1,28 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Verifies that i3 survives inplace restarts with fullscreen containers
+#
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+fresh_workspace;
+
+open_window($x);
+open_window($x);
+
+cmd 'layout stacking';
+sleep 1;
+
+cmd 'fullscreen';
+sleep 1;
+
+cmd 'restart';
+sleep 1;
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/169-border-toggle.t b/testcases/t/169-border-toggle.t
new file mode 100644 (file)
index 0000000..aec8df6
--- /dev/null
@@ -0,0 +1,40 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if the 'border toggle' command works correctly
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+cmd 'open';
+
+my @nodes = @{get_ws_content($tmp)};
+is(@nodes, 1, 'one container on this workspace');
+is($nodes[0]->{border}, 'normal', 'border style normal');
+
+cmd 'border 1pixel';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
+
+cmd 'border none';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'none', 'border style none');
+
+cmd 'border normal';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'normal', 'border style back to normal');
+
+cmd 'border toggle';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'none', 'border style none');
+
+cmd 'border toggle';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
+
+cmd 'border toggle';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'normal', 'border style back to normal');
+
+done_testing;
diff --git a/testcases/t/17-workspace.t b/testcases/t/17-workspace.t
deleted file mode 100644 (file)
index 19e2df3..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests whether we can switch to a non-existant workspace
-# (necessary for further tests)
-#
-use List::Util qw(first);
-use i3test;
-
-# to ensure that workspace 1 stays open
-cmd 'open';
-
-my $tmp = fresh_workspace;
-ok(workspace_exists($tmp), 'workspace created');
-# if the workspace could not be created, we cannot run any other test
-# (every test starts by creating its workspace)
-if (!workspace_exists($tmp)) {
-    BAIL_OUT('Cannot create workspace, further tests make no sense');
-}
-
-my $otmp = fresh_workspace;
-diag("Other temporary workspace name: $otmp\n");
-
-# As the old workspace was empty, it should get
-# cleaned up as we switch away from it
-cmd "workspace $otmp";
-ok(!workspace_exists($tmp), 'old workspace cleaned up');
-
-# Switch to the same workspace again to make sure it doesn’t get cleaned up
-cmd "workspace $otmp";
-cmd "workspace $otmp";
-ok(workspace_exists($otmp), 'other workspace still exists');
-
-
-#####################################################################
-# check if the workspace next / prev commands work
-#####################################################################
-
-cmd 'workspace next';
-
-ok(!workspace_exists('next'), 'workspace "next" does not exist');
-
-cmd "workspace $tmp";
-cmd 'open';
-
-ok(workspace_exists($tmp), 'workspace created');
-
-cmd "workspace $otmp";
-cmd 'open';
-
-ok(workspace_exists($tmp), 'workspace tmp still exists');
-ok(workspace_exists($otmp), 'workspace otmp created');
-
-is(focused_ws(), $otmp, 'focused workspace is otmp');
-
-cmd 'workspace prev';
-is(focused_ws(), $tmp, 'focused workspace is tmp after workspace prev');
-
-cmd 'workspace next';
-is(focused_ws(), $otmp, 'focused workspace is otmp after workspace next');
-
-
-#####################################################################
-# check that wrapping works
-#####################################################################
-
-cmd 'workspace next';
-is(focused_ws(), '1', 'focused workspace is 1 after workspace next');
-
-cmd 'workspace next';
-is(focused_ws(), $tmp, 'focused workspace is tmp after workspace next');
-
-cmd 'workspace next';
-is(focused_ws(), $otmp, 'focused workspace is otmp after workspace next');
-
-
-cmd 'workspace prev';
-is(focused_ws(), $tmp, 'focused workspace is tmp after workspace prev');
-
-cmd 'workspace prev';
-is(focused_ws(), '1', 'focused workspace is tmp after workspace prev');
-
-cmd 'workspace prev';
-is(focused_ws(), $otmp, 'focused workspace is otmp after workspace prev');
-
-
-#####################################################################
-# check if we can change to "next" / "prev"
-#####################################################################
-
-cmd 'workspace "next"';
-
-ok(workspace_exists('next'), 'workspace "next" exists');
-is(focused_ws(), 'next', 'now on workspace next');
-
-cmd 'workspace "prev"';
-
-ok(workspace_exists('prev'), 'workspace "prev" exists');
-is(focused_ws(), 'prev', 'now on workspace prev');
-
-done_testing;
diff --git a/testcases/t/170-force_focus_wrapping.t b/testcases/t/170-force_focus_wrapping.t
new file mode 100644 (file)
index 0000000..8a990f2
--- /dev/null
@@ -0,0 +1,97 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests if the 'force_focus_wrapping' config directive works correctly.
+#
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+#####################################################################
+# 1: test the wrapping behaviour without force_focus_wrapping
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_window($x);
+my $second = open_window($x);
+
+cmd 'layout tabbed';
+cmd 'focus parent';
+
+my $third = open_window($x);
+is($x->input_focus, $third->id, 'third window focused');
+
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+
+cmd 'focus left';
+is($x->input_focus, $first->id, 'first window focused');
+
+# now test the wrapping
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+
+# but focusing right should not wrap now, but instead focus the third window
+cmd 'focus right';
+is($x->input_focus, $third->id, 'third window focused');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 2: test the wrapping behaviour with force_focus_wrapping
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+force_focus_wrapping true
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_window($x);
+$second = open_window($x);
+
+cmd 'layout tabbed';
+cmd 'focus parent';
+
+$third = open_window($x);
+
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'third window focused');
+
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+
+cmd 'focus left';
+is($x->input_focus, $first->id, 'first window focused');
+
+# now test the wrapping
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+
+# focusing right should now be forced to wrap
+cmd 'focus right';
+is($x->input_focus, $first->id, 'first window focused');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/171-config-migrate.t b/testcases/t/171-config-migrate.t
new file mode 100644 (file)
index 0000000..940afc3
--- /dev/null
@@ -0,0 +1,349 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests if i3-migrate-config-to-v4 correctly migrates all config file
+# directives and commands
+#
+use i3test;
+use Cwd qw(abs_path);
+use File::Temp qw(tempfile tempdir);
+use v5.10;
+
+# reads in a whole file
+sub slurp {
+    open my $fh, '<', shift;
+    local $/;
+    <$fh>;
+}
+
+sub migrate_config {
+    my ($config) = @_;
+
+    my ($fh, $tmpfile) = tempfile('/tmp/i3-migrate-cfg.XXXXXX', UNLINK => 1);
+    print $fh $config;
+    close($fh);
+
+    my $cmd = "sh -c 'exec " . abs_path("../i3-migrate-config-to-v4") . " --v3 <$tmpfile'";
+    return [ split /\n/, qx($cmd) ];
+}
+
+sub line_exists {
+    my ($lines, $pattern) = @_;
+
+    for my $line (@$lines) {
+        return 1 if $line =~ $pattern;
+    }
+
+    return 0
+}
+
+#####################################################################
+# check that some directives remain untouched
+#####################################################################
+
+my $input = <<EOT;
+    font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $output = migrate_config($input);
+ok(line_exists($output, qr|font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1|), 'font directive unchanged');
+
+$input = <<EOT;
+    floating_Modifier Mod1
+    focus_follows_mouse true
+    ipc-socket /tmp/i3-ipc.sock
+    ipc_socket /tmp/i3-ipc.sock
+    exec /usr/bin/i3
+    set stuff Mod1
+    assign "XTerm" → 3
+    assign "XTerm" → ~5
+    client.focused #2F343A #900000 #FFFFFF
+    client.focused_inactive #FF0000 #FF0000 #FF0000
+    client.unfocused #00FF00 #00FF00 #00FF00
+    client.urgent #0000FF #0000FF #0000FF
+    client.background #000000
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^floating_Modifier Mod1$|), 'floating_modifier unchanged');
+ok(line_exists($output, qr|^focus_follows_mouse true$|), 'focus_follows_mouse unchanged');
+ok(line_exists($output, qr|^ipc-socket /tmp/i3-ipc.sock$|), 'ipc-socket unchanged');
+ok(line_exists($output, qr|^ipc_socket /tmp/i3-ipc.sock$|), 'ipc_socket unchanged');
+ok(line_exists($output, qr|^exec /usr/bin/i3|), 'exec unchanged');
+ok(line_exists($output, qr|^set stuff Mod1|), 'set unchanged');
+ok(line_exists($output, qr|^assign "XTerm" → 3|), 'assign unchanged');
+ok(line_exists($output, qr|^assign "XTerm" → ~5|), 'assign unchanged');
+ok(line_exists($output, qr|^client\.focused #2F343A #900000 #FFFFFF$|), 'client.focused unchanged');
+ok(line_exists($output, qr|^client\.focused_inactive #FF0000 #FF0000 #FF0000$|), 'client.focused_inactive unchanged');
+ok(line_exists($output, qr|^client\.unfocused #00FF00 #00FF00 #00FF00$|), 'client.unfocused unchanged');
+ok(line_exists($output, qr|^client\.urgent #0000FF #0000FF #0000FF$|), 'client.urgent unchanged');
+ok(line_exists($output, qr|^client\.background #000000$|), 'client.background unchanged');
+
+#####################################################################
+# check whether the bar colors get removed properly
+#####################################################################
+
+$input = <<EOT;
+    bar.focused #FFFF00 #FFFF00 #FFFF00
+    bar.unfocused #FFFF00 #FFFF00 #FFFF00
+    bar.urgent #FFFF00 #FFFF00 #FFFF00
+EOT
+
+$output = migrate_config($input);
+ok(!line_exists($output, qr|^bar\.|), 'no bar. lines');
+ok(line_exists($output, qr|^#.*REMOVED bar|), 'note bar. removed');
+
+
+#####################################################################
+# check whether the other directives get converted correctly
+#####################################################################
+
+$input = <<EOT;
+    new_container stacking
+    workspace_bar no
+    new_window bb
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^workspace_layout stacking$|), 'new_container changed');
+ok(line_exists($output, qr|REMOVED workspace_bar|), 'workspace_bar removed');
+ok(!line_exists($output, qr|^workspace_bar|), 'no workspace_bar in the output');
+ok(line_exists($output, qr|^new_window none$|), 'new_window changed');
+
+#####################################################################
+# check whether new_window's parameters get changed correctly
+#####################################################################
+
+$output = migrate_config('new_window bb');
+ok(line_exists($output, qr|^new_window none$|), 'new_window bb changed');
+
+$output = migrate_config('new_window bn');
+ok(line_exists($output, qr|^new_window normal$|), 'new_window bn changed');
+
+$output = migrate_config('new_window bp');
+ok(line_exists($output, qr|^new_window 1pixel$|), 'new_window bp changed');
+
+#####################################################################
+# check that some commands remain untouched
+#####################################################################
+
+$input = <<EOT;
+    bindsym Mod1+s exec /usr/bin/urxvt
+    bindsym Mod1+s mark foo
+    bindsym Mod1+s restart
+    bindsym Mod1+s reload
+    bindsym Mod1+s exit
+    bindsym Mod1+s stack-limit cols 2
+    bindsym Mod1+s stack-limit rows 3
+    bind Mod1+c exec /usr/bin/urxvt
+    mode "asdf" {
+        bind 36 mode default
+    }
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^bindsym Mod1\+s exec /usr/bin/urxvt$|), 'exec unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s mark foo$|), 'mark unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s restart$|), 'restart unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s reload$|), 'reload unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s exit$|), 'exit unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s stack-limit cols 2$|), 'stack-limit unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s stack-limit rows 3$|), 'stack-limit unchanged');
+ok(line_exists($output, qr|^bindcode Mod1\+c exec /usr/bin/urxvt$|), 'bind changed to bindcode');
+ok(line_exists($output, qr|^mode "asdf" {$|), 'mode asdf unchanged');
+ok(line_exists($output, qr|^bindcode 36 mode \"default\"$|), 'mode default unchanged');
+ok(line_exists($output, qr|^}$|), 'closing mode bracket still there');
+
+#####################################################################
+# check the simple command replacements
+#####################################################################
+
+$input = <<EOT;
+    bindsym Mod1+s s
+    bindsym Mod1+s d
+    bindsym Mod1+s T
+
+    bindsym Mod1+s f
+    bindsym Mod1+s fg
+
+    bindsym Mod1+s t
+
+    bindsym Mod1+s h
+    bindsym Mod1+s j
+    bindsym Mod1+s k
+    bindsym Mod1+s l
+
+    bindsym Mod1+s mh
+    bindsym Mod1+s mj
+    bindsym Mod1+s mk
+    bindsym Mod1+s ml
+
+    bindsym Mod1+s bn
+    bindsym Mod1+s bp
+    bindsym Mod1+s bb
+    bindsym Mod1+s bt
+
+    bindsym Mod1+j wch
+    bindsym Mod1+j wcml
+
+    bindsym Mod1+k kill
+
+    bindsym Mod1+n nw
+    bindsym Mod1+p pw
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^bindsym Mod1\+s layout stacking$|), 's replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s layout default$|), 'd replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s layout tabbed$|), 'T replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen$|), 'f replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen global$|), 'fg replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s floating toggle$|), 't replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s focus left$|), 'h replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s focus down$|), 'j replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s focus up$|), 'k replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s focus right$|), 'l replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s move left$|), 'mh replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s move down$|), 'mj replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s move up$|), 'mk replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s move right$|), 'ml replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s border normal$|), 'bn replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s border 1pixel$|), 'bp replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s border none$|), 'bb replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s border toggle$|), 'bt replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+j focus parent; focus left$|), 'with container replaced with focus parent; focus left');
+ok(line_exists($output, qr|^bindsym Mod1\+j focus parent; move right$|), 'with container replaced with focus parent; move right');
+ok(line_exists($output, qr|^bindsym Mod1\+k kill$|), 'kill unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+n workspace next$|), 'nw replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+p workspace prev$|), 'pw replaced');
+
+#####################################################################
+# check more advanced replacements
+#####################################################################
+
+$input = <<EOT;
+    bindsym Mod1+s goto foo
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^bindsym Mod1\+s \[con_mark="foo"\] focus$|), 'goto replaced');
+
+#####################################################################
+# check whether focus's parameters get changed correctly
+#####################################################################
+
+$output = migrate_config('bindsym Mod1+f focus 3');
+ok(line_exists($output, qr|^#.*focus.*obsolete.*focus 3$|), 'focus [number] gone');
+
+$output = migrate_config('bindsym Mod1+f focus floating');
+ok(line_exists($output, qr|^bindsym Mod1\+f focus floating$|), 'focus floating unchanged');
+
+$output = migrate_config('bindsym Mod1+f focus tiling');
+ok(line_exists($output, qr|^bindsym Mod1\+f focus tiling$|), 'focus tiling unchanged');
+
+$output = migrate_config('bindsym Mod1+f focus ft');
+ok(line_exists($output, qr|^bindsym Mod1\+f focus mode_toggle$|), 'focus ft changed');
+
+#####################################################################
+# check whether resize's parameters get changed correctly
+#####################################################################
+
+$output = migrate_config('bindsym Mod1+f resize left +10');
+ok(line_exists($output, qr|^bindsym Mod1\+f resize grow left 10 px$|), 'resize left changed');
+
+$output = migrate_config('bindsym Mod1+f resize top -20');
+ok(line_exists($output, qr|^bindsym Mod1\+f resize shrink up 20 px$|), 'resize top changed');
+
+$output = migrate_config('bindsym Mod1+f resize right -20');
+ok(line_exists($output, qr|^bindsym Mod1\+f resize shrink right 20 px$|), 'resize right changed');
+
+$output = migrate_config('bindsym Mod1+f resize bottom +23');
+ok(line_exists($output, qr|^bindsym Mod1\+f resize grow down 23 px$|), 'resize bottom changed');
+
+#####################################################################
+# also resizing, but with indention this time
+#####################################################################
+
+$output = migrate_config("bindsym Mod1+f resize          left    \t +10");
+ok(line_exists($output, qr|^bindsym Mod1\+f resize grow left 10 px$|), 'resize left changed');
+
+#####################################################################
+# check whether jump's parameters get changed correctly
+#####################################################################
+
+$output = migrate_config('bindsym Mod1+f jump 3');
+ok(line_exists($output, qr|^#.*obsolete.*jump 3$|), 'jump to workspace removed');
+
+$output = migrate_config('bindsym Mod1+f jump 3 4 5');
+ok(line_exists($output, qr|^#.*obsolete.*jump 3 4 5$|), 'jump to workspace + col/row removed');
+
+$output = migrate_config('bindsym Mod1+f jump "XTerm"');
+ok(line_exists($output, qr|^bindsym Mod1\+f \[class="XTerm"\] focus$|), 'jump changed');
+
+$output = migrate_config('bindsym Mod1+f jump "XTerm/irssi"');
+ok(line_exists($output, qr|^bindsym Mod1\+f \[class="XTerm" title="irssi"\] focus$|), 'jump changed');
+
+#####################################################################
+# check whether workspace commands are handled correctly
+#####################################################################
+
+$output = migrate_config('workspace 3 output VGA-1');
+ok(line_exists($output, qr|^workspace 3 output VGA-1$|), 'workspace assignment unchanged');
+
+$output = migrate_config('workspace 3 work');
+ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
+ok(line_exists($output, qr|#.*workspace name.*bindings|), 'note present');
+
+$input = <<EOT;
+    workspace 3 work
+    bindsym Mod1+3 3
+EOT
+$output = migrate_config($input);
+ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
+ok(line_exists($output, qr|^bindsym Mod1\+3 workspace work|), 'named workspace in bindings');
+
+# The same, but in reverse order
+$input = <<EOT;
+    bindsym Mod1+3 3
+    workspace 3 work
+EOT
+$output = migrate_config($input);
+ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
+ok(line_exists($output, qr|^bindsym Mod1\+3 workspace work|), 'named workspace in bindings');
+
+$output = migrate_config('bindsym Mod1+3 3');
+ok(line_exists($output, qr|^bindsym Mod1\+3 workspace 3|), 'workspace changed');
+
+$output = migrate_config('bindsym Mod1+3 m3');
+ok(line_exists($output, qr|^bindsym Mod1\+3 move workspace 3|), 'move workspace changed');
+
+$input = <<EOT;
+    workspace 3 work
+    bindsym Mod1+3 m3
+EOT
+$output = migrate_config($input);
+ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
+ok(line_exists($output, qr|^bindsym Mod1\+3 move workspace work|), 'move to named workspace in bindings');
+
+#####################################################################
+# check whether an i3bar call is added if the workspace bar bar was enabled
+#####################################################################
+
+$output = migrate_config('');
+ok(line_exists($output, qr|bar {|), 'i3bar added');
+
+$output = migrate_config('workspace_bar enable');
+ok(line_exists($output, qr|bar {|), 'i3bar added');
+
+$output = migrate_config('workspace_bar no');
+ok(!line_exists($output, qr|bar {|), 'no i3bar added');
+
+#####################################################################
+# check whether the mode command gets quotes
+#####################################################################
+
+$output = migrate_config('bindsym Mod1+m mode foobar');
+ok(line_exists($output, qr|^bindsym Mod1\+m mode "foobar"|), 'mode got quotes');
+
+done_testing();
diff --git a/testcases/t/172-start-on-named-ws.t b/testcases/t/172-start-on-named-ws.t
new file mode 100644 (file)
index 0000000..4493bf8
--- /dev/null
@@ -0,0 +1,65 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# checks if i3 starts up on workspace '1' or the first configured named workspace
+#
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+##############################################################
+# 1: i3 should start with workspace '1'
+##############################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my @names = @{get_workspace_names()};
+cmp_deeply(\@names, [ '1' ], 'i3 starts on workspace 1 without any configuration');
+
+exit_gracefully($pid);
+
+##############################################################
+# 2: with named workspaces, i3 should start on the first named one
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bindsym Mod1+1 workspace foobar
+EOT
+
+$pid = launch_with_config($config);
+
+my @names = @{get_workspace_names()};
+cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
+
+exit_gracefully($pid);
+
+##############################################################
+# 3: the same test as 2, but with a quoted workspace name
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bindsym Mod1+1 workspace "foobar"
+EOT
+
+$pid = launch_with_config($config);
+
+my @names = @{get_workspace_names()};
+cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/173-get-marks.t b/testcases/t/173-get-marks.t
new file mode 100644 (file)
index 0000000..e74c233
--- /dev/null
@@ -0,0 +1,50 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# checks if the IPC message type get_marks works correctly
+#
+use i3test;
+
+sub get_marks {
+    return i3(get_socket_path())->get_marks->recv;
+}
+
+##############################################################
+# 1: check that get_marks returns no marks yet
+##############################################################
+
+my $tmp = fresh_workspace;
+
+my $marks = get_marks();
+cmp_deeply($marks, [], 'no marks set so far');
+
+##############################################################
+# 2: check that setting a mark is reflected in the get_marks reply
+##############################################################
+
+cmd 'open';
+cmd 'mark foo';
+
+cmp_deeply(get_marks(), [ 'foo' ], 'mark foo set');
+
+##############################################################
+# 3: check that the mark is gone after killing the container
+##############################################################
+
+cmd 'kill';
+
+cmp_deeply(get_marks(), [ ], 'mark gone');
+
+##############################################################
+# 4: check that duplicate marks are included twice in the get_marks reply
+##############################################################
+
+cmd 'open';
+cmd 'mark bar';
+
+cmd 'open';
+cmd 'mark bar';
+
+cmp_deeply(get_marks(), [ 'bar', 'bar' ], 'duplicate mark found twice');
+
+done_testing;
diff --git a/testcases/t/173-regress-focus-assign.t b/testcases/t/173-regress-focus-assign.t
new file mode 100644 (file)
index 0000000..70414af
--- /dev/null
@@ -0,0 +1,95 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Regression: Checks if focus is stolen when a window is managed which is
+# assigned to an invisible workspace
+#
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use v5.10;
+
+my $x = X11::XCB::Connection->new;
+
+# TODO: move to X11::XCB
+sub set_wm_class {
+    my ($id, $class, $instance) = @_;
+
+    # Add a _NET_WM_STRUT_PARTIAL hint
+    my $atomname = $x->atom(name => 'WM_CLASS');
+    my $atomtype = $x->atom(name => 'STRING');
+
+    $x->change_property(
+        PROP_MODE_REPLACE,
+        $id,
+        $atomname->id,
+        $atomtype->id,
+        8,
+        length($class) + length($instance) + 2,
+        "$instance\x00$class\x00"
+    );
+}
+
+
+#####################################################################
+# start a window and see that it does not get assigned with an empty config
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign "special" → targetws
+EOT
+
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+ok(get_ws($tmp)->{focused}, 'current workspace focused');
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+sleep 0.25;
+
+
+ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
+ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
+ok(get_ws($tmp)->{focused}, 'current workspace still focused');
+
+#####################################################################
+# the same test, but with a floating window
+#####################################################################
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+sleep 0.25;
+
+
+ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
+ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
+ok(get_ws($tmp)->{focused}, 'current workspace still focused');
+
+exit_gracefully($pid);
+
+$window->destroy;
+
+done_testing;
diff --git a/testcases/t/174-border-config.t b/testcases/t/174-border-config.t
new file mode 100644 (file)
index 0000000..cc1f5c9
--- /dev/null
@@ -0,0 +1,116 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests the new_window and new_float config option.
+#
+
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+#####################################################################
+# 1: check that new windows start with 'normal' border unless configured
+# otherwise
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_window($x);
+
+my @content = @{get_ws_content($tmp)};
+ok(@content == 1, 'one container opened');
+is($content[0]->{border}, 'normal', 'border normal by default');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 2: check that new tiling windows start with '1pixel' border when
+# configured
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+new_window 1pixel
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_window($x);
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 1, 'one container opened');
+is($content[0]->{border}, '1pixel', 'border normal by default');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 3: check that new floating windows start with 'normal' border unless
+# configured otherwise
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_floating_window($x);
+
+my $wscontent = get_ws($tmp);
+my @floating = @{$wscontent->{floating_nodes}};
+ok(@floating == 1, 'one floating container opened');
+my $floatingcon = $floating[0];
+is($floatingcon->{nodes}->[0]->{border}, 'normal', 'border normal by default');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 4: check that new floating windows start with '1pixel' border when
+# configured
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+new_float 1pixel
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_floating_window($x);
+
+$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');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/174-regress-focus-toggle.t b/testcases/t/174-regress-focus-toggle.t
new file mode 100644 (file)
index 0000000..469d1be
--- /dev/null
@@ -0,0 +1,17 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: Checks if i3 still lives after using 'focus mode_toggle' on an
+# empty workspace. This regression was fixed in
+# 0848844f2d41055f6ffc69af1149d7a873460976.
+#
+use i3test;
+use v5.10;
+
+my $tmp = fresh_workspace;
+
+cmd 'focus mode_toggle';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/175-startup-notification.t b/testcases/t/175-startup-notification.t
new file mode 100644 (file)
index 0000000..55df414
--- /dev/null
@@ -0,0 +1,171 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Test for the startup notification protocol.
+#
+
+use i3test;
+use POSIX qw(mkfifo);
+use File::Temp qw(:POSIX);
+
+my $x = X11::XCB::Connection->new;
+use ExtUtils::PkgConfig;
+
+# setup dependency on libstartup-notification using pkg-config
+my %sn_config;
+BEGIN {
+    %sn_config = ExtUtils::PkgConfig->find('libstartup-notification-1.0');
+}
+
+use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
+use Inline C => <<'END_OF_C_CODE';
+
+#include <xcb/xcb.h>
+
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-common.h>
+#include <libsn/sn-launchee.h>
+
+static SnDisplay *sndisplay;
+static SnLauncheeContext *ctx;
+static xcb_connection_t *conn;
+
+// TODO: this should use $x
+void init_ctx() {
+    int screen;
+    if ((conn = xcb_connect(NULL, &screen)) == NULL ||
+        xcb_connection_has_error(conn))
+        errx(1, "x11 conn failed");
+
+    printf("screen = %d\n", screen);
+    sndisplay = sn_xcb_display_new(conn, NULL, NULL);
+    ctx = sn_launchee_context_new_from_environment(sndisplay, screen);
+}
+
+const char *get_startup_id() {
+    return sn_launchee_context_get_startup_id(ctx);
+}
+
+void mark_window(int window) {
+    sn_launchee_context_setup_window(ctx, (Window)window);
+    xcb_flush(conn);
+}
+
+void complete_startup() {
+    /* mark the startup process complete */
+    sn_launchee_context_complete(ctx);
+}
+END_OF_C_CODE
+
+my $first_ws = fresh_workspace;
+
+is(@{get_ws_content($first_ws)}, 0, 'no containers on this workspace yet');
+
+######################################################################
+# 1) initiate startup, switch workspace, create window
+# (should be placed on the original workspace)
+######################################################################
+
+# Start a new process via i3 (to initialize a new startup notification
+# context), then steal its DESKTOP_STARTUP_ID variable. We handle the startup
+# notification in the testcase from there on.
+#
+# This works by setting up a FIFO in which the process (started by i3) will
+# echo its $DESKTOP_STARTUP_ID. We (blockingly) read the variable into
+# $startup_id in the testcase.
+my $tmp = tmpnam();
+mkfifo($tmp, 0600) or die "Could not create FIFO in $tmp";
+
+cmd qq|exec echo \$DESKTOP_STARTUP_ID >$tmp|;
+
+open(my $fh, '<', $tmp);
+chomp(my $startup_id = <$fh>);
+close($fh);
+
+unlink($tmp);
+
+isnt($startup_id, '', 'startup_id not empty');
+
+$ENV{DESKTOP_STARTUP_ID} = $startup_id;
+
+# Create a new libstartup-notification launchee context
+init_ctx();
+
+# Make sure the context was set up successfully
+is(get_startup_id(), $startup_id, 'libstartup-notification returns the same id');
+
+my $second_ws = fresh_workspace;
+
+is(@{get_ws_content($second_ws)}, 0, 'no containers on the second workspace yet');
+
+my $win = open_window($x, { dont_map => 1 });
+mark_window($win->id);
+$win->map;
+# We don’t use wait_for_map because the window will not get mapped -- it is on
+# a different workspace.
+# We sync with i3 here to make sure $x->input_focus is updated.
+sync_with_i3($x);
+
+is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace');
+is(@{get_ws_content($first_ws)}, 1, 'one container on the first workspace');
+
+######################################################################
+# same thing, but with _NET_STARTUP_ID set on the leader
+######################################################################
+
+my $leader = open_window($x, { dont_map => 1 });
+mark_window($leader->id);
+
+$win = open_window($x, { dont_map => 1, client_leader => $leader });
+$win->map;
+sync_with_i3($x);
+
+is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace');
+is(@{get_ws_content($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)
+######################################################################
+
+complete_startup();
+sync_with_i3($x);
+
+my $otherwin = open_window($x);
+is(@{get_ws_content($second_ws)}, 1, 'one container on the second workspace');
+
+######################################################################
+# 3) test that the --no-startup-id flag for exec leads to no DESKTOP_STARTUP_ID
+# environment variable.
+######################################################################
+
+mkfifo($tmp, 0600) or die "Could not create FIFO in $tmp";
+
+cmd qq|exec --no-startup-id echo \$DESKTOP_STARTUP_ID >$tmp|;
+
+open($fh, '<', $tmp);
+chomp($startup_id = <$fh>);
+close($fh);
+
+unlink($tmp);
+
+is($startup_id, '', 'startup_id empty');
+
+######################################################################
+# 4) same thing, but with double quotes in exec
+######################################################################
+
+mkfifo($tmp, 0600) or die "Could not create FIFO in $tmp";
+
+cmd qq|exec --no-startup-id "echo \$DESKTOP_STARTUP_ID >$tmp"|;
+
+open($fh, '<', $tmp);
+chomp($startup_id = <$fh>);
+close($fh);
+
+unlink($tmp);
+
+is($startup_id, '', 'startup_id empty');
+
+
+done_testing;
diff --git a/testcases/t/176-workspace-baf.t b/testcases/t/176-workspace-baf.t
new file mode 100644 (file)
index 0000000..48ea948
--- /dev/null
@@ -0,0 +1,69 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Checks if the 'workspace back_and_forth' command and the
+# 'workspace_auto_back_and_forth' config directive work correctly.
+#
+
+use i3test;
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $first_ws = fresh_workspace;
+ok(get_ws($first_ws)->{focused}, 'first workspace focused');
+
+my $second_ws = fresh_workspace;
+ok(get_ws($second_ws)->{focused}, 'second workspace focused');
+
+my $third_ws = fresh_workspace;
+ok(get_ws($third_ws)->{focused}, 'third workspace focused');
+
+cmd 'workspace back_and_forth';
+ok(get_ws($second_ws)->{focused}, 'second workspace focused');
+
+#####################################################################
+# test that without workspace_auto_back_and_forth switching to the same
+# workspace that is currently focused is a no-op
+#####################################################################
+
+cmd qq|workspace "$second_ws"|;
+ok(get_ws($second_ws)->{focused}, 'second workspace still focused');
+
+exit_gracefully($pid);
+
+#####################################################################
+# the same test, but with workspace_auto_back_and_forth
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+workspace_auto_back_and_forth yes
+EOT
+
+$pid = launch_with_config($config);
+
+$first_ws = fresh_workspace;
+ok(get_ws($first_ws)->{focused}, 'first workspace focused');
+
+$second_ws = fresh_workspace;
+ok(get_ws($second_ws)->{focused}, 'second workspace focused');
+
+$third_ws = fresh_workspace;
+ok(get_ws($third_ws)->{focused}, 'third workspace focused');
+
+cmd qq|workspace "$third_ws"|;
+ok(get_ws($second_ws)->{focused}, 'second workspace focused');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t
new file mode 100644 (file)
index 0000000..0bf287b
--- /dev/null
@@ -0,0 +1,208 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Checks that the bar config is parsed correctly.
+#
+
+use i3test;
+
+#####################################################################
+# test a config without any bars
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $i3 = i3(get_socket_path(0));
+my $bars = $i3->get_bar_config()->recv;
+is(@$bars, 0, 'no bars configured');
+
+exit_gracefully($pid);
+
+#####################################################################
+# now provide a simple bar configuration
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bar {
+    # Start a default instance of i3bar which provides workspace buttons.
+    # Additionally, i3status will provide a statusline.
+    status_command i3status --foo
+}
+EOT
+
+$pid = launch_with_config($config);
+
+$i3 = i3(get_socket_path(0));
+$bars = $i3->get_bar_config()->recv;
+is(@$bars, 1, 'one bar configured');
+
+my $bar_id = shift @$bars;
+
+my $bar_config = $i3->get_bar_config($bar_id)->recv;
+is($bar_config->{status_command}, 'i3status --foo', 'status_command correct');
+ok(!$bar_config->{verbose}, 'verbose off by default');
+ok($bar_config->{workspace_buttons}, 'workspace buttons enabled per default');
+is($bar_config->{mode}, 'dock', 'dock mode by default');
+is($bar_config->{position}, 'bottom', 'position bottom by default');
+
+#####################################################################
+# ensure that reloading cleans up the old bar configs
+#####################################################################
+
+cmd 'reload';
+$bars = $i3->get_bar_config()->recv;
+is(@$bars, 1, 'still one bar configured');
+
+exit_gracefully($pid);
+
+#####################################################################
+# validate a more complex configuration
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bar {
+    # Start a default instance of i3bar which provides workspace buttons.
+    # Additionally, i3status will provide a statusline.
+    status_command i3status --bar
+
+    output HDMI1
+    output HDMI2
+
+    tray_output LVDS1
+    tray_output HDMI2
+    position top
+    mode dock
+    font Terminus
+    workspace_buttons no
+    verbose yes
+    socket_path /tmp/foobar
+
+    colors {
+        background #ff0000
+        statusline   #00ff00
+
+        focused_workspace   #ffffff #285577
+        active_workspace    #888888 #222222
+        inactive_workspace  #888888 #222222
+        urgent_workspace    #ffffff #900000
+    }
+}
+EOT
+
+$pid = launch_with_config($config);
+
+$i3 = i3(get_socket_path(0));
+$bars = $i3->get_bar_config()->recv;
+is(@$bars, 1, 'one bar configured');
+
+$bar_id = shift @$bars;
+
+$bar_config = $i3->get_bar_config($bar_id)->recv;
+is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
+ok($bar_config->{verbose}, 'verbose on');
+ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled');
+is($bar_config->{mode}, 'dock', 'dock mode');
+is($bar_config->{position}, 'top', 'position top');
+is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok');
+is($bar_config->{tray_output}, 'HDMI2', 'tray_output ok');
+is($bar_config->{font}, 'Terminus', 'font ok');
+is($bar_config->{socket_path}, '/tmp/foobar', 'socket_path ok');
+is_deeply($bar_config->{colors},
+    {
+        background => '#ff0000',
+        statusline => '#00ff00',
+        focused_workspace_text => '#ffffff',
+        focused_workspace_bg => '#285577',
+        active_workspace_text => '#888888',
+        active_workspace_bg => '#222222',
+        inactive_workspace_text => '#888888',
+        inactive_workspace_bg => '#222222',
+        urgent_workspace_text => '#ffffff',
+        urgent_workspace_bg => '#900000',
+    }, 'colors ok');
+
+exit_gracefully($pid);
+
+#####################################################################
+# ensure that multiple bars get different IDs
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bar {
+    # Start a default instance of i3bar which provides workspace buttons.
+    # Additionally, i3status will provide a statusline.
+    status_command i3status --bar
+
+    output HDMI1
+}
+
+bar {
+    output VGA1
+}
+EOT
+
+$pid = launch_with_config($config);
+
+$i3 = i3(get_socket_path(0));
+$bars = $i3->get_bar_config()->recv;
+is(@$bars, 2, 'two bars configured');
+isnt($bars->[0], $bars->[1], 'bar IDs are different');
+
+my $bar1_config = $i3->get_bar_config($bars->[0])->recv;
+my $bar2_config = $i3->get_bar_config($bars->[1])->recv;
+
+isnt($bar1_config->{outputs}, $bar2_config->{outputs}, 'outputs different');
+
+exit_gracefully($pid);
+
+#####################################################################
+# make sure comments work properly
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bar {
+    # Start a default instance of i3bar which provides workspace buttons.
+    # Additionally, i3status will provide a statusline.
+    status_command i3status --bar
+    #status_command i3status --qux
+#status_command i3status --qux
+
+    output HDMI1
+    colors {
+        background #000000
+        #background #ffffff
+    }
+}
+EOT
+
+$pid = launch_with_config($config);
+
+$i3 = i3(get_socket_path(0));
+$bars = $i3->get_bar_config()->recv;
+$bar_id = shift @$bars;
+
+$bar_config = $i3->get_bar_config($bar_id)->recv;
+is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
+is($bar_config->{colors}->{background}, '#000000', 'background color ok');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/178-regress-workspace-open.t b/testcases/t/178-regress-workspace-open.t
new file mode 100644 (file)
index 0000000..25fe7d9
--- /dev/null
@@ -0,0 +1,22 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if empty workspaces are closed when the last child
+# exits, as long as they're not empty.
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+# Get a workspace and open a container
+my $ws = fresh_workspace;
+my $con = open_empty_con($i3);
+
+# Go to a second workspace, kill the container
+fresh_workspace;
+cmd "[con_id=\"$con\"] kill";
+
+# The first workspace should have been closed
+ok(!workspace_exists($ws), 'workspace closed');
+
+done_testing;
diff --git a/testcases/t/179-regress-multiple-ws.t b/testcases/t/179-regress-multiple-ws.t
new file mode 100644 (file)
index 0000000..2127117
--- /dev/null
@@ -0,0 +1,25 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# The command "move workspace prev; workspace prev" will lead to an error.
+# This regression is present in 7f9b65f6a752e454c492447be4e21e2ee8faf8fd
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+# Open one workspace to move the con to
+my $old = fresh_workspace;
+my $keep_open_con = open_empty_con($i3);
+
+# Get a workspace and open a container
+my $tmp = fresh_workspace;
+my $con = open_empty_con($i3);
+
+is(@{get_ws_content($tmp)}, 1, 'one container');
+is(@{get_ws_content($old)}, 1, 'one container on old ws');
+
+cmd 'move workspace prev; workspace prev';
+
+is(@{get_ws_content($old)}, 2, 'container moved away');
+
+done_testing;
diff --git a/testcases/t/18-openkill.t b/testcases/t/18-openkill.t
deleted file mode 100644 (file)
index e2a729c..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests whether opening an empty container and killing it again works
-#
-use List::Util qw(first);
-use i3test;
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-# Open a new container
-cmd 'open';
-
-ok(@{get_ws_content($tmp)} == 1, 'container opened');
-
-cmd 'kill';
-ok(@{get_ws_content($tmp)} == 0, 'container killed');
-
-##############################################################
-# open two containers and kill the one which is not focused
-# by its ID to test if the parser correctly matches the window
-##############################################################
-
-cmd 'open';
-cmd 'open';
-ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
-
-my $content = get_ws_content($tmp);
-my $not_focused = first { !$_->{focused} } @{$content};
-my $id = $not_focused->{id};
-
-cmd "[con_id=\"$id\"] kill";
-
-$content = get_ws_content($tmp);
-ok(@{$content} == 1, 'one container killed');
-ok($content->[0]->{id} != $id, 'correct window killed');
-
-done_testing;
diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t
deleted file mode 100644 (file)
index 2332bc7..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests all kinds of matching methods
-#
-use i3test;
-use X11::XCB qw(:all);
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-# Open a new window
-my $x = X11::XCB::Connection->new;
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#C0C0C0',
-);
-
-$window->map;
-# give it some time to be picked up by the window manager
-# TODO: better check for $window->mapped or something like that?
-# maybe we can even wait for getting mapped?
-my $c = 0;
-while (@{get_ws_content($tmp)} == 0 and $c++ < 5) {
-    sleep 0.25;
-}
-my $content = get_ws_content($tmp);
-ok(@{$content} == 1, 'window mapped');
-my $win = $content->[0];
-
-######################################################################
-# first test that matches which should not match this window really do
-# not match it
-######################################################################
-# TODO: use PCRE expressions
-# TODO: specify more match types
-cmd q|[class="*"] kill|;
-cmd q|[con_id="99999"] kill|;
-
-$content = get_ws_content($tmp);
-ok(@{$content} == 1, 'window still there');
-
-# now kill the window
-cmd 'nop now killing the window';
-my $id = $win->{id};
-cmd qq|[con_id="$id"] kill|;
-
-# give i3 some time to pick up the UnmapNotify event
-sleep 0.25;
-
-cmd 'nop checking if its gone';
-$content = get_ws_content($tmp);
-ok(@{$content} == 0, 'window killed');
-
-# TODO: same test, but with pcre expressions
-
-######################################################################
-# check that multiple criteria work are checked with a logical AND,
-# not a logical OR (that is, matching is not cancelled after the first
-# criterion matches).
-######################################################################
-
-$tmp = fresh_workspace;
-
-# TODO: move to X11::XCB
-sub set_wm_class {
-    my ($id, $class, $instance) = @_;
-
-    # Add a _NET_WM_STRUT_PARTIAL hint
-    my $atomname = $x->atom(name => 'WM_CLASS');
-    my $atomtype = $x->atom(name => 'STRING');
-
-    $x->change_property(
-        PROP_MODE_REPLACE,
-        $id,
-        $atomname->id,
-        $atomtype->id,
-        8,
-        length($class) + length($instance) + 2,
-        "$instance\x00$class\x00"
-    );
-}
-
-my $left = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#0000ff',
-);
-
-$left->_create;
-set_wm_class($left->id, 'special', 'special');
-$left->name('left');
-$left->map;
-sleep 0.25;
-
-my $right = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#0000ff',
-);
-
-$right->_create;
-set_wm_class($right->id, 'special', 'special');
-$right->name('right');
-$right->map;
-sleep 0.25;
-
-# two windows should be here
-$content = get_ws_content($tmp);
-ok(@{$content} == 2, 'two windows opened');
-
-cmd '[class="special" title="left"] kill';
-
-sleep 0.25;
-
-$content = get_ws_content($tmp);
-is(@{$content}, 1, 'one window still there');
-
-done_testing;
diff --git a/testcases/t/20-multiple-cmds.t b/testcases/t/20-multiple-cmds.t
deleted file mode 100644 (file)
index 784329f..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests multiple commands (using ';') and multiple operations (using ',')
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-
-sub multiple_cmds {
-    my ($cmd) = @_;
-
-    cmd 'open';
-    cmd 'open';
-    ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
-
-    cmd $cmd;
-    ok(@{get_ws_content($tmp)} == 0, "both containers killed (cmd = $cmd)");
-}
-multiple_cmds('kill;kill');
-multiple_cmds('kill; kill');
-multiple_cmds('kill ; kill');
-multiple_cmds('kill ;kill');
-multiple_cmds('kill  ;kill');
-multiple_cmds('kill  ;  kill');
-multiple_cmds("kill;\tkill");
-multiple_cmds("kill\t;kill");
-multiple_cmds("kill\t;\tkill");
-multiple_cmds("kill\t ;\tkill");
-multiple_cmds("kill\t ;\t kill");
-multiple_cmds("kill \t ; \t kill");
-
-#####################################################################
-# test if un-quoted strings are handled correctly
-#####################################################################
-
-$tmp = fresh_workspace;
-cmd 'open';
-my $unused = get_unused_workspace;
-ok(!($unused ~~ @{get_workspace_names()}), 'workspace does not exist yet');
-cmd "move workspace $unused; nop parser test";
-ok(($unused ~~ @{get_workspace_names()}), 'workspace exists after moving');
-
-#####################################################################
-# quote the workspace name and use a ; (command separator) in its name
-#####################################################################
-
-$unused = get_unused_workspace;
-$unused .= ';a';
-ok(!($unused ~~ @{get_workspace_names()}), 'workspace does not exist yet');
-cmd qq|move workspace "$unused"; nop parser test|;
-ok(($unused ~~ @{get_workspace_names()}), 'workspace exists after moving');
-
-# TODO: need a non-invasive command before implementing a test which uses ','
-
-done_testing;
diff --git a/testcases/t/21-next-prev.t b/testcases/t/21-next-prev.t
deleted file mode 100644 (file)
index 447be31..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests focus switching (next/prev)
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-
-######################################################################
-# Open one container, verify that 'focus down' and 'focus right' do nothing
-######################################################################
-cmd 'open';
-
-my ($nodes, $focus) = get_ws_content($tmp);
-my $old_focused = $focus->[0];
-
-cmd 'focus down';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $old_focused, 'focus did not change with only one con');
-
-cmd 'focus right';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $old_focused, 'focus did not change with only one con');
-
-######################################################################
-# Open another container, verify that 'focus right' switches
-######################################################################
-my $left = $old_focused;
-
-cmd 'open';
-($nodes, $focus) = get_ws_content($tmp);
-isnt($old_focused, $focus->[0], 'new container is focused');
-my $mid = $focus->[0];
-
-cmd 'open';
-($nodes, $focus) = get_ws_content($tmp);
-isnt($old_focused, $focus->[0], 'new container is focused');
-my $right = $focus->[0];
-
-cmd 'focus right';
-($nodes, $focus) = get_ws_content($tmp);
-isnt($focus->[0], $right, 'focus did change');
-is($focus->[0], $left, 'left container focused (wrapping)');
-
-cmd 'focus right';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $mid, 'middle container focused');
-
-cmd 'focus right';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $right, 'right container focused');
-
-cmd 'focus left';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $mid, 'middle container focused');
-
-cmd 'focus left';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $left, 'left container focused');
-
-cmd 'focus left';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $right, 'right container focused');
-
-
-######################################################################
-# Test focus command
-######################################################################
-
-cmd qq|[con_id="$mid"] focus|;
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $mid, 'middle container focused');
-
-
-done_testing;
diff --git a/testcases/t/22-split.t b/testcases/t/22-split.t
deleted file mode 100644 (file)
index 3484c7f..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests splitting
-#
-use i3test;
-use X11::XCB qw(:all);
-
-my $tmp = fresh_workspace;
-
-my $ws = get_ws($tmp);
-is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
-cmd 'split v';
-$ws = get_ws($tmp);
-is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
-
-######################################################################
-# Open two containers, split, open another container. Then verify
-# the layout is like we expect it to be
-######################################################################
-cmd 'open';
-cmd 'open';
-my $content = get_ws_content($tmp);
-
-is(@{$content}, 2, 'two containers on workspace level');
-my $first = $content->[0];
-my $second = $content->[1];
-
-is(@{$first->{nodes}}, 0, 'first container has no children');
-is(@{$second->{nodes}}, 0, 'second container has no children (yet)');
-my $old_name = $second->{name};
-
-
-cmd 'split h';
-cmd 'open';
-
-$content = get_ws_content($tmp);
-
-is(@{$content}, 2, 'two containers on workspace level');
-$first = $content->[0];
-$second = $content->[1];
-
-is(@{$first->{nodes}}, 0, 'first container has no children');
-isnt($second->{name}, $old_name, 'second container was replaced');
-is($second->{orientation}, 'horizontal', 'orientation is horizontal');
-is(@{$second->{nodes}}, 2, 'second container has 2 children');
-is($second->{nodes}->[0]->{name}, $old_name, 'found old second container');
-
-# TODO: extend this test-case (test next/prev)
-# - wrapping (no horizontal switch possible, goes level-up)
-# - going level-up "manually"
-
-######################################################################
-# Test splitting multiple times without actually creating windows
-######################################################################
-
-$tmp = fresh_workspace;
-
-$ws = get_ws($tmp);
-is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
-cmd 'split v';
-$ws = get_ws($tmp);
-is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
-
-cmd 'open';
-my @content = @{get_ws_content($tmp)};
-
-# 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);
-}
-
-my $old_count = sum_nodes(\@content);
-cmd 'split v';
-
-@content = @{get_ws_content($tmp)};
-$old_count = sum_nodes(\@content);
-
-cmd 'split v';
-
-@content = @{get_ws_content($tmp)};
-my $count = sum_nodes(\@content);
-is($count, $old_count, 'not more windows after splitting again');
-
-done_testing;
diff --git a/testcases/t/24-move.t b/testcases/t/24-move.t
deleted file mode 100644 (file)
index 4df3a2a..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests moving. Basically, there are four different code-paths:
-# 1) move a container which cannot be moved (single container on a workspace)
-# 2) move a container before another single container
-# 3) move a container inside another container
-# 4) move a container in a different direction so that we need to go up in tree
-#
-use i3test;
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-######################################################################
-# 1) move a container which cannot be moved
-######################################################################
-
-cmd 'open';
-
-my $old_content = get_ws_content($tmp);
-is(@{$old_content}, 1, 'one container on this workspace');
-
-my $first = $old_content->[0]->{id};
-
-#cmd 'move before h';
-#cmd 'move before v';
-#cmd 'move after v';
-#cmd 'move after h';
-
-my $content = get_ws_content($tmp);
-#is_deeply($old_content, $content, 'workspace unmodified after useless moves');
-
-######################################################################
-# 2) move a container before another single container
-######################################################################
-
-cmd 'open';
-$content = get_ws_content($tmp);
-is(@{$content}, 2, 'two containers on this workspace');
-my $second = $content->[1]->{id};
-
-is($content->[0]->{id}, $first, 'first container unmodified');
-
-# Move the second container before the first one (→ swap them)
-cmd 'move left';
-$content = get_ws_content($tmp);
-is($content->[0]->{id}, $second, 'first container modified');
-
-# We should not be able to move any further
-cmd 'move left';
-$content = get_ws_content($tmp);
-is($content->[0]->{id}, $second, 'first container unmodified');
-
-# Now move in the other direction
-cmd 'move right';
-$content = get_ws_content($tmp);
-is($content->[0]->{id}, $first, 'first container modified');
-
-# We should not be able to move any further
-cmd 'move right';
-$content = get_ws_content($tmp);
-is($content->[0]->{id}, $first, 'first container unmodified');
-
-######################################################################
-# 3) move a container inside another container
-######################################################################
-
-# Split the current (second) container and create a new container on workspace
-# level. Our layout looks like this now:
-# --------------------------
-# |       | second |       |
-# | first | ------ | third |
-# |       |        |       |
-# --------------------------
-cmd 'split v';
-cmd 'focus parent';
-cmd 'open';
-
-$content = get_ws_content($tmp);
-is(@{$content}, 3, 'three containers on this workspace');
-my $third = $content->[2]->{id};
-
-cmd 'move left';
-$content = get_ws_content($tmp);
-is(@{$content}, 2, 'only two containers on this workspace');
-my $nodes = $content->[1]->{nodes};
-is($nodes->[0]->{id}, $second, 'second container on top');
-is($nodes->[1]->{id}, $third, 'third container on bottom');
-
-######################################################################
-# move it inside the split container
-######################################################################
-
-cmd 'move up';
-$nodes = get_ws_content($tmp)->[1]->{nodes};
-is($nodes->[0]->{id}, $third, 'third container on top');
-is($nodes->[1]->{id}, $second, 'second container on bottom');
-
-# move it outside again
-cmd 'move left';
-$content = get_ws_content($tmp);
-is(@{$content}, 3, 'three nodes on this workspace');
-
-# due to automatic flattening/cleanup, the remaining split container
-# will be replaced by the con itself, so we will still have 3 nodes
-cmd 'move right';
-$content = get_ws_content($tmp);
-is(@{$content}, 2, 'two nodes on this workspace');
-
-######################################################################
-# 4) We create two v-split containers on the workspace, then we move
-#    all Cons from the left v-split to the right one. The old vsplit
-#    container needs to be closed. Verify that it will be closed.
-######################################################################
-
-my $otmp = fresh_workspace;
-
-cmd "open";
-cmd "open";
-cmd "split v";
-cmd "open";
-cmd 'focus left';
-cmd "split v";
-cmd "open";
-cmd "move right";
-cmd 'focus left';
-cmd "move right";
-
-$content = get_ws_content($otmp);
-is(@{$content}, 1, 'only one nodes on this workspace');
-
-done_testing;
diff --git a/testcases/t/26-regress-close.t b/testcases/t/26-regress-close.t
deleted file mode 100644 (file)
index 8aec87d..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression: closing of floating clients did crash i3 when closing the
-# container which contained this client.
-#
-use i3test;
-
-fresh_workspace;
-
-cmd 'open';
-cmd 'mode toggle';
-cmd 'kill';
-cmd 'kill';
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/27-regress-floating-parent.t b/testcases/t/27-regress-floating-parent.t
deleted file mode 100644 (file)
index 52b8b9c..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression: make a container floating, kill its parent, make it tiling again
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-
-cmd 'open';
-my $left = get_focused($tmp);
-cmd 'open';
-my $old = get_focused($tmp);
-cmd 'split v';
-cmd 'open';
-my $floating = get_focused($tmp);
-diag("focused floating: " . get_focused($tmp));
-cmd 'mode toggle';
-# TODO: eliminate this race conditition
-sleep 1;
-
-# kill old container
-cmd qq|[con_id="$old"] focus|;
-is(get_focused($tmp), $old, 'old container focused');
-cmd 'kill';
-
-# kill left container
-cmd qq|[con_id="$left"] focus|;
-is(get_focused($tmp), $left, 'old container focused');
-cmd 'kill';
-
-# focus floating window, make it tiling again
-cmd qq|[con_id="$floating"] focus|;
-is(get_focused($tmp), $floating, 'floating window focused');
-
-sleep 1;
-cmd 'mode toggle';
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/28-open-order.t b/testcases/t/28-open-order.t
deleted file mode 100644 (file)
index b638e70..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Check if new containers are opened after the currently focused one instead
-# of always at the end
-use List::Util qw(first);
-use i3test;
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-# Open two new container
-my $first = open_empty_con($i3);
-
-ok(@{get_ws_content($tmp)} == 1, 'containers opened');
-
-my $second = open_empty_con($i3);
-
-isnt($first, $second, 'different container focused');
-
-##############################################################
-# see if new containers open after the currently focused
-##############################################################
-
-cmd qq|[con_id="$first"] focus|;
-cmd 'open';
-$content = get_ws_content($tmp);
-ok(@{$content} == 3, 'three containers opened');
-
-is($content->[0]->{id}, $first, 'first container unmodified');
-isnt($content->[1]->{id}, $second, 'second container replaced');
-is($content->[2]->{id}, $second, 'third container unmodified');
-
-done_testing;
diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/29-focus-after-close.t
deleted file mode 100644 (file)
index ac029eb..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Check if the focus is correctly restored after closing windows.
-#
-use i3test;
-use X11::XCB qw(:all);
-use List::Util qw(first);
-
-my $x = X11::XCB::Connection->new;
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $first = open_empty_con($i3);
-my $second = open_empty_con($i3);
-
-cmd 'split v';
-
-my ($nodes, $focus) = get_ws_content($tmp);
-
-ok(!$nodes->[1]->{focused}, 'split container not focused');
-cmd 'focus parent';
-($nodes, $focus) = get_ws_content($tmp);
-ok($nodes->[1]->{focused}, 'split container focused after focus parent');
-
-my $third = open_empty_con($i3);
-
-isnt(get_focused($tmp), $second, 'different container focused');
-
-# We have the following layout now (con is focused):
-# .----------------.
-# | split  |       |
-# | .----. |  con  |
-# | | cn | |       |
-# | `----' |       |
-# `----------------'
-
-##############################################################
-# see if the focus goes down to $first (not to its split parent)
-# when closing $second
-##############################################################
-
-cmd 'kill';
-# TODO: this testcase sometimes has different outcomes when the
-# sleep is missing. why?
-sleep 0.25;
-($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[1]->{nodes}->[0]->{id}, $second, 'second container found');
-ok($nodes->[1]->{nodes}->[0]->{focused}, 'second container focused');
-
-##############################################################
-# another case, using a slightly different layout (regression)
-##############################################################
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-cmd 'split v';
-$first = open_empty_con($i3);
-my $bottom = open_empty_con($i3);
-
-cmd 'focus up';
-cmd 'split h';
-my $middle = open_empty_con($i3);
-my $right = open_empty_con($i3);
-cmd 'focus down';
-
-# We have the following layout now (second is focused):
-# .----------------------------.
-# | .------------------------. |
-# | | first | middle | right | |
-# | `------------------------' |
-# |----------------------------|
-# |                            |
-# |          second            |
-# |                            |
-# `----------------------------'
-
-# Check if the focus is restored to $right when we close $second
-cmd 'kill';
-
-is(get_focused($tmp), $right, 'top right container focused (in focus stack)');
-
-($nodes, $focus) = get_ws_content($tmp);
-my $tr = first { $_->{id} eq $right } @{$nodes->[0]->{nodes}};
-is($tr->{focused}, 1, 'top right container really has focus');
-
-##############################################################
-# check if focus is correct after closing an unfocused window
-##############################################################
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-$first = open_empty_con($i3);
-$middle = open_empty_con($i3);
-# XXX: the $right empty con will be filled with the x11 window we are creating afterwards
-$right = open_empty_con($i3);
-my $win = open_standard_window($x, '#00ff00');
-
-cmd qq|[con_id="$middle"] focus|;
-$win->destroy;
-
-sleep 0.25;
-
-is(get_focused($tmp), $middle, 'middle container focused');
-
-##############################################################
-# and now for something completely different:
-# check if the pointer position is relevant when restoring focus
-# (it should not be relevant, of course)
-##############################################################
-
-# TODO: add test code as soon as I can reproduce it
-
-done_testing;
diff --git a/testcases/t/30-close-empty-split.t b/testcases/t/30-close-empty-split.t
deleted file mode 100644 (file)
index 57855cd..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Check if empty split containers are automatically closed.
-#
-use i3test;
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $first = open_empty_con($i3);
-my $second = open_empty_con($i3);
-cmd qq|[con_id="$first"] focus|;
-
-cmd 'split v';
-
-($nodes, $focus) = get_ws_content($tmp);
-
-is($nodes->[0]->{focused}, 0, 'split container not focused');
-
-# focus the split container
-cmd 'level up';
-($nodes, $focus) = get_ws_content($tmp);
-my $split = $focus->[0];
-cmd 'level down';
-
-my $second = open_empty_con($i3);
-
-isnt($first, $second, 'different container focused');
-
-##############################################################
-# close both windows and see if the split container still exists
-##############################################################
-
-cmd 'kill';
-cmd 'kill';
-($nodes, $focus) = get_ws_content($tmp);
-isnt($nodes->[0]->{id}, $split, 'split container closed');
-
-##############################################################
-# same thing but this time we are moving the cons away instead
-# of killing them
-##############################################################
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-$first = open_empty_con($i3);
-$second = open_empty_con($i3);
-cmd qq|[con_id="$first"] focus|;
-
-cmd 'split v';
-
-($nodes, $focus) = get_ws_content($tmp);
-
-is($nodes->[0]->{focused}, 0, 'split container not focused');
-
-# focus the split container
-cmd 'level up';
-($nodes, $focus) = get_ws_content($tmp);
-my $split = $focus->[0];
-cmd 'level down';
-
-my $second = open_empty_con($i3);
-
-isnt($first, $second, 'different container focused');
-
-##############################################################
-# close both windows and see if the split container still exists
-##############################################################
-
-my $otmp = get_unused_workspace();
-cmd "move workspace $otmp";
-cmd "move workspace $otmp";
-($nodes, $focus) = get_ws_content($tmp);
-isnt($nodes->[0]->{id}, $split, 'split container closed');
-
-done_testing;
diff --git a/testcases/t/31-stacking-order.t b/testcases/t/31-stacking-order.t
deleted file mode 100644 (file)
index 9c1e74c..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Check if stacking containers can be used independantly of
-# the split mode (horizontal/vertical) of the underlying
-# container.
-#
-use i3test;
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-# Enforce vertical split mode
-cmd 'split v';
-
-my $first = open_empty_con($i3);
-my $second = open_empty_con($i3);
-
-isnt($first, $second, 'two different containers opened');
-
-##############################################################
-# change mode to stacking and cycle through the containers
-##############################################################
-
-cmd 'layout stacking';
-is(get_focused($tmp), $second, 'second container still focused');
-
-cmd 'focus down';
-is(get_focused($tmp), $first, 'first container focused');
-
-cmd 'focus up';
-is(get_focused($tmp), $second, 'second container focused again');
-
-##############################################################
-# now change the orientation to horizontal and cycle
-##############################################################
-
-cmd 'focus parent';
-cmd 'split h';
-cmd 'focus child';
-
-cmd 'focus down';
-is(get_focused($tmp), $first, 'first container focused');
-
-cmd 'focus up';
-is(get_focused($tmp), $second, 'second container focused again');
-
-
-done_testing;
diff --git a/testcases/t/32-move-workspace.t b/testcases/t/32-move-workspace.t
deleted file mode 100644 (file)
index 3b50e39..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Checks if the 'move workspace' command works correctly
-#
-use i3test;
-
-my $i3 = i3(get_socket_path());
-
-# We move the pointer out of our way to avoid a bug where the focus will
-# be set to the window under the cursor
-my $x = X11::XCB::Connection->new;
-$x->root->warp_pointer(0, 0);
-
-my $tmp = get_unused_workspace();
-my $tmp2 = get_unused_workspace();
-cmd "workspace $tmp";
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $first = open_empty_con($i3);
-my $second = open_empty_con($i3);
-ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws');
-
-cmd "workspace $tmp2";
-ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws yet');
-
-cmd "workspace $tmp";
-
-cmd "move workspace $tmp2";
-ok(@{get_ws_content($tmp)} == 1, 'one container on first ws anymore');
-ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
-my ($nodes, $focus) = get_ws_content($tmp2);
-
-is($focus->[0], $second, 'same container on different ws');
-
-($nodes, $focus) = get_ws_content($tmp);
-ok($nodes->[0]->{focused}, 'first container focused on first ws');
-
-###################################################################
-# check if floating cons are moved to new workspaces properly
-# (that is, if they are floating on the target ws, too)
-###################################################################
-
-$tmp = get_unused_workspace();
-$tmp2 = get_unused_workspace();
-cmd "workspace $tmp";
-
-cmd "open";
-cmd "floating toggle";
-
-my $ws = get_ws($tmp);
-is(@{$ws->{nodes}}, 0, 'no nodes on workspace');
-is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace');
-
-cmd "move workspace $tmp2";
-
-$ws = get_ws($tmp2);
-is(@{$ws->{nodes}}, 0, 'no nodes on workspace');
-is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace');
-
-done_testing;
diff --git a/testcases/t/33-size-hints.t b/testcases/t/33-size-hints.t
deleted file mode 100644 (file)
index 05897c8..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Checks if size hints are interpreted correctly.
-#
-use i3test;
-
-my $i3 = i3(get_socket_path());
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $win = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30),
-    background_color => '#C0C0C0',
-);
-
-# XXX: we should check screen size. in screens with an AR of 2.0,
-# this is not a good idea.
-my $aspect = X11::XCB::Sizehints::Aspect->new;
-$aspect->min_num(600);
-$aspect->min_den(300);
-$aspect->max_num(600);
-$aspect->max_den(300);
-$win->_create;
-$win->map;
-sleep 0.25;
-$win->hints->aspect($aspect);
-$x->flush;
-
-sleep 0.25;
-
-my $rect = $win->rect;
-my $ar = $rect->width / $rect->height;
-diag("Aspect ratio = $ar");
-ok(($ar > 1.90) && ($ar < 2.10), 'Aspect ratio about 2.0');
-
-done_testing;
diff --git a/testcases/t/34-invalid-command.t b/testcases/t/34-invalid-command.t
deleted file mode 100644 (file)
index d58985e..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# 
-#
-use i3test;
-
-cmd 'blargh!';
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t
deleted file mode 100644 (file)
index 3f820ea..0000000
+++ /dev/null
@@ -1,174 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-#############################################################################
-# 1: see if focus stays the same when toggling tiling/floating mode
-#############################################################################
-
-my $first = open_standard_window($x);
-my $second = open_standard_window($x);
-
-is($x->input_focus, $second->id, 'second window focused');
-
-cmd 'floating enable';
-cmd 'floating disable';
-
-is($x->input_focus, $second->id, 'second window still focused after mode toggle');
-
-#############################################################################
-# 2: see if focus stays on the current floating window if killing another
-# floating window
-#############################################################################
-
-$tmp = fresh_workspace;
-
-$first = open_standard_window($x);    # window 2
-$second = open_standard_window($x);   # window 3
-my $third = open_standard_window($x); # window 4
-
-is($x->input_focus, $third->id, 'last container focused');
-
-cmd 'floating enable';
-
-cmd '[id="' . $second->id . '"] focus';
-
-is($x->input_focus, $second->id, 'second con focused');
-
-cmd 'floating enable';
-
-# now kill the third one (it's floating). focus should stay unchanged
-cmd '[id="' . $third->id . '"] kill';
-
-sleep 0.25;
-
-is($x->input_focus, $second->id, 'second con still focused after killing third');
-
-
-#############################################################################
-# 3: see if the focus gets reverted correctly when closing floating clients
-# (first to the next floating client, then to the last focused tiling client)
-#############################################################################
-
-$tmp = fresh_workspace;
-
-$first = open_standard_window($x, '#ff0000');    # window 5
-$second = open_standard_window($x, '#00ff00');   # window 6
-my $third = open_standard_window($x, '#0000ff'); # window 7
-
-is($x->input_focus, $third->id, 'last container focused');
-
-cmd 'floating enable';
-
-cmd '[id="' . $second->id . '"] focus';
-
-is($x->input_focus, $second->id, 'second con focused');
-
-cmd 'floating enable';
-
-# now kill the second one. focus should fall back to the third one, which is
-# also floating
-cmd 'kill';
-
-sleep 0.25;
-
-is($x->input_focus, $third->id, 'third con focused');
-
-cmd 'kill';
-
-sleep 0.25;
-
-is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
-
-#############################################################################
-# 4: same test as 3, but with another split con
-#############################################################################
-
-$tmp = fresh_workspace;
-
-$first = open_standard_window($x, '#ff0000');    # window 5
-cmd 'split v';
-cmd 'layout stacked';
-$second = open_standard_window($x, '#00ff00');   # window 6
-$third = open_standard_window($x, '#0000ff'); # window 7
-
-is($x->input_focus, $third->id, 'last container focused');
-
-cmd 'floating enable';
-
-cmd '[id="' . $second->id . '"] focus';
-
-is($x->input_focus, $second->id, 'second con focused');
-
-cmd 'floating enable';
-
-sleep 0.5;
-
-# now kill the second one. focus should fall back to the third one, which is
-# also floating
-cmd 'kill';
-
-sleep 0.25;
-
-is($x->input_focus, $third->id, 'second con focused');
-
-cmd 'kill';
-
-sleep 0.25;
-
-is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
-
-#############################################################################
-# 5: see if the 'focus tiling' and 'focus floating' commands work
-#############################################################################
-
-$tmp = fresh_workspace;
-
-$first = open_standard_window($x, '#ff0000');    # window 8
-$second = open_standard_window($x, '#00ff00');   # window 9
-
-is($x->input_focus, $second->id, 'second container focused');
-
-cmd 'floating enable';
-
-is($x->input_focus, $second->id, 'second container focused');
-
-cmd 'focus tiling';
-
-sleep 0.25;
-
-is($x->input_focus, $first->id, 'first (tiling) container focused');
-
-cmd 'focus floating';
-
-sleep 0.25;
-
-is($x->input_focus, $second->id, 'second (floating) container focused');
-
-cmd 'focus floating';
-
-sleep 0.25;
-
-is($x->input_focus, $second->id, 'second (floating) container still focused');
-
-cmd 'focus mode_toggle';
-
-sleep 0.25;
-
-is($x->input_focus, $first->id, 'first (tiling) container focused');
-
-cmd 'focus mode_toggle';
-
-sleep 0.25;
-
-is($x->input_focus, $second->id, 'second (floating) container focused');
-
-
-done_testing;
diff --git a/testcases/t/36-floating-ws-empty.t b/testcases/t/36-floating-ws-empty.t
deleted file mode 100644 (file)
index f33d04d..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Regression test: when only having a floating window on a workspace, it should not be deleted.
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#############################################################################
-# 1: open a floating window, get it mapped
-#############################################################################
-
-ok(workspace_exists($tmp), "workspace $tmp exists");
-
-my $x = X11::XCB::Connection->new;
-
-# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#C0C0C0',
-    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-ok($window->mapped, 'Window is mapped');
-
-# switch to a different workspace, see if the window is still mapped?
-
-my $otmp = fresh_workspace;
-
-ok(workspace_exists($otmp), "new workspace $otmp exists");
-ok(workspace_exists($tmp), "old workspace $tmp still exists");
-
-done_testing;
diff --git a/testcases/t/37-floating-unmap.t b/testcases/t/37-floating-unmap.t
deleted file mode 100644 (file)
index 3ae4b12..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Regression test: Floating windows were not correctly unmapped when switching
-# to a different workspace.
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#############################################################################
-# 1: open a floating window, get it mapped
-#############################################################################
-
-my $x = X11::XCB::Connection->new;
-
-# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#C0C0C0',
-    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-ok($window->mapped, 'Window is mapped');
-
-# switch to a different workspace, see if the window is still mapped?
-
-my $otmp = fresh_workspace;
-
-sleep 0.25;
-
-ok(!$window->mapped, 'Window is not mapped after switching ws');
-
-cmd "nop testcase done";
-
-done_testing;
diff --git a/testcases/t/38-floating-attach.t b/testcases/t/38-floating-attach.t
deleted file mode 100644 (file)
index 31bddaf..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Regression test: New windows were attached to the container of a floating window
-# if only a floating window is present on the workspace.
-
-use i3test;
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#############################################################################
-# 1: open a floating window, get it mapped
-#############################################################################
-
-my $x = X11::XCB::Connection->new;
-
-# Create a floating window
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#C0C0C0',
-    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-ok($window->mapped, 'Window is mapped');
-
-my $ws = get_ws($tmp);
-my ($nodes, $focus) = get_ws_content($tmp);
-
-is(@{$ws->{floating_nodes}}, 1, 'one floating node');
-is(@{$nodes}, 0, 'no tiling nodes');
-
-# Create a tiling window
-my $twindow = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#C0C0C0',
-);
-
-isa_ok($twindow, 'X11::XCB::Window');
-
-$twindow->map;
-
-sleep 0.25;
-
-($nodes, $focus) = get_ws_content($tmp);
-
-is(@{$nodes}, 1, 'one tiling node');
-
-#############################################################################
-# 2: similar case: floating windows should be attached at the currently focused
-# position in the workspace (for example a stack), not just at workspace level.
-#############################################################################
-
-$tmp = fresh_workspace;
-
-my $first = open_standard_window($x);
-my $second = open_standard_window($x);
-
-cmd 'layout stacked';
-
-$ws = get_ws($tmp);
-is(@{$ws->{floating_nodes}}, 0, 'no floating nodes so far');
-is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
-
-# Create a floating window
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#C0C0C0',
-    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-ok($window->mapped, 'Window is mapped');
-
-$ws = get_ws($tmp);
-is(@{$ws->{floating_nodes}}, 1, 'one floating nodes');
-is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
-
-my $third = open_standard_window($x);
-
-
-$ws = get_ws($tmp);
-is(@{$ws->{floating_nodes}}, 1, 'one floating nodes');
-is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
-
-done_testing;
diff --git a/testcases/t/39-ws-numbers.t b/testcases/t/39-ws-numbers.t
deleted file mode 100644 (file)
index 3afd281..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Check if numbered workspaces and named workspaces are sorted in the right way
-# in get_workspaces IPC output (necessary for i3bar etc.).
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $i3 = i3(get_socket_path());
-my $x = X11::XCB::Connection->new;
-
-sub check_order {
-    my ($msg) = @_;
-
-    my @ws = @{$i3->get_workspaces->recv};
-    my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
-    my @sorted = sort @nums;
-
-    cmp_deeply(\@nums, \@sorted, $msg);
-}
-
-check_order('workspace order alright before testing');
-
-#############################################################################
-# open a window to keep this ws open
-#############################################################################
-
-cmd "workspace 93";
-
-open_standard_window($x);
-
-my @ws = @{$i3->get_workspaces->recv};
-my @f = grep { defined($_->{num}) && $_->{num} == 93 } @ws;
-is(@f, 1, 'ws 93 found by num');
-check_order('workspace order alright after opening 93');
-
-cmd "workspace 92";
-open_standard_window($x);
-check_order('workspace order alright after opening 92');
-
-cmd "workspace 94";
-open_standard_window($x);
-check_order('workspace order alright after opening 94');
-
-cmd "workspace 96";
-open_standard_window($x);
-check_order('workspace order alright after opening 96');
-
-cmd "workspace foo";
-open_standard_window($x);
-check_order('workspace order alright after opening foo');
-
-cmd "workspace 91";
-open_standard_window($x);
-check_order('workspace order alright after opening 91');
-
-done_testing;
diff --git a/testcases/t/40-focus-lost.t b/testcases/t/40-focus-lost.t
deleted file mode 100644 (file)
index 9df220d..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Regression: Check if the focus stays the same when switching the layout
-# bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb
-use i3test;
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $i3 = i3(get_socket_path());
-my $x = X11::XCB::Connection->new;
-
-sub check_order {
-    my ($msg) = @_;
-
-    my @ws = @{$i3->get_workspaces->recv};
-    my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
-    my @sorted = sort @nums;
-
-    cmp_deeply(\@nums, \@sorted, $msg);
-}
-
-my $tmp = fresh_workspace;
-
-my $left = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-my $right = open_standard_window($x);
-sleep 0.25;
-
-diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id);
-
-is($x->input_focus, $right->id, 'Right window focused');
-
-cmd 'focus left';
-
-is($x->input_focus, $mid->id, 'Mid window focused');
-
-cmd 'layout stacked';
-
-is($x->input_focus, $mid->id, 'Mid window focused');
-
-done_testing;
diff --git a/testcases/t/41-resize.t b/testcases/t/41-resize.t
deleted file mode 100644 (file)
index 1d1b120..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Tests resizing tiling containers
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-cmd 'split v';
-
-my $top = open_standard_window($x);
-sleep 0.25;
-my $bottom = open_standard_window($x);
-sleep 0.25;
-
-diag("top = " . $top->id . ", bottom = " . $bottom->id);
-
-is($x->input_focus, $bottom->id, 'Bottom window focused');
-
-############################################################
-# resize
-############################################################
-
-cmd 'resize grow up 10 px or 25 ppt';
-
-my ($nodes, $focus) = get_ws_content($tmp);
-
-is($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
-is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
-
-
-############################################################
-# split and check if the 'percent' factor is still correct
-############################################################
-
-cmd 'split h';
-
-($nodes, $focus) = get_ws_content($tmp);
-
-is($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
-is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
-
-############################################################
-# checks that resizing within stacked/tabbed cons works
-############################################################
-
-$tmp = fresh_workspace;
-
-cmd 'split v';
-
-$top = open_standard_window($x);
-sleep 0.25;
-$bottom = open_standard_window($x);
-sleep 0.25;
-
-cmd 'split h';
-cmd 'layout stacked';
-
-($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.5, 'top window got 50%');
-is($nodes->[1]->{percent}, 0.5, 'bottom window got 50%');
-
-cmd 'resize grow up 10 px or 25 ppt';
-
-($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.25, 'top window got 25%');
-is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
-
-############################################################
-# checks that resizing floating windows works
-############################################################
-
-$tmp = fresh_workspace;
-
-$top = open_standard_window($x);
-sleep 0.25;
-
-cmd 'floating enable';
-
-my @content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok(@content, '==', 1, 'one floating node on this ws');
-
-# up
-my $oldrect = $content[0]->{rect};
-
-cmd 'resize grow up 10 px or 25 ppt';
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{y}, '<', $oldrect->{y}, 'y smaller than before');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y} - 10, 'y exactly 10 px smaller');
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
-cmp_ok($content[0]->{rect}->{height}, '>', $oldrect->{height}, 'height bigger than before');
-cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height} + 10, 'height exactly 10 px higher');
-cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'x untouched');
-
-# up, but with a different amount of px
-$oldrect = $content[0]->{rect};
-
-cmd 'resize grow up 12 px or 25 ppt';
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{y}, '<', $oldrect->{y}, 'y smaller than before');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y} - 12, 'y exactly 10 px smaller');
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
-cmp_ok($content[0]->{rect}->{height}, '>', $oldrect->{height}, 'height bigger than before');
-cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height} + 12, 'height exactly 10 px higher');
-cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'x untouched');
-
-# left
-$oldrect = $content[0]->{rect};
-
-cmd 'resize grow left 10 px or 25 ppt';
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{x}, '<', $oldrect->{x}, 'x smaller than before');
-cmp_ok($content[0]->{rect}->{width}, '>', $oldrect->{width}, 'width bigger than before');
-
-# right
-$oldrect = $content[0]->{rect};
-
-cmd 'resize grow right 10 px or 25 ppt';
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x the same as before');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y the same as before');
-cmp_ok($content[0]->{rect}->{width}, '>', $oldrect->{width}, 'width bigger than before');
-cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height}, 'height the same as before');
-
-# down
-$oldrect = $content[0]->{rect};
-
-cmd 'resize grow down 10 px or 25 ppt';
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x the same as before');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y the same as before');
-cmp_ok($content[0]->{rect}->{height}, '>', $oldrect->{height}, 'height bigger than before');
-cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'width the same as before');
-
-done_testing;
diff --git a/testcases/t/42-regress-move-floating.t b/testcases/t/42-regress-move-floating.t
deleted file mode 100644 (file)
index 6b2df80..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression: move a floating window to a different workspace crashes i3
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-my $otmp = get_unused_workspace();
-
-cmd 'open';
-cmd 'mode toggle';
-cmd "move workspace $otmp";
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/43-regress-floating-restart.t b/testcases/t/43-regress-floating-restart.t
deleted file mode 100644 (file)
index babbb57..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression: floating windows are tiling after restarting, closing them crashes i3
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-
-cmd 'open';
-cmd 'mode toggle';
-cmd 'restart';
-
-sleep 0.5;
-
-diag('Checking if i3 still lives');
-
-does_i3_live;
-
-my $ws = get_ws($tmp);
-diag('ws = ' . Dumper($ws));
-
-done_testing;
diff --git a/testcases/t/44-regress-floating-resize.t b/testcases/t/44-regress-floating-resize.t
deleted file mode 100644 (file)
index de33eeb..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression: when resizing two containers on a workspace, opening a floating
-# client, then closing it again, i3 will re-distribute the space on the
-# workspace as if a tiling container was closed, leading to the containers
-# taking much more space than they possibly could.
-#
-use i3test;
-use List::Util qw(sum);
-
-my $tmp = fresh_workspace;
-
-cmd 'exec /usr/bin/urxvt';
-sleep 0.5;
-cmd 'exec /usr/bin/urxvt';
-sleep 0.5;
-my ($nodes, $focus) = get_ws_content($tmp);
-my $old_sum = sum map { $_->{rect}->{width} } @{$nodes};
-#cmd 'open';
-cmd 'resize grow left 10 px or 25 ppt';
-cmd 'split v';
-#cmd 'open';
-cmd 'exec /usr/bin/urxvt';
-sleep 0.5;
-cmd 'mode toggle';
-sleep 0.5;
-cmd 'kill';
-
-sleep 0.5;
-
-($nodes, $focus) = get_ws_content($tmp);
-my $new_sum = sum map { $_->{rect}->{width} } @{$nodes};
-
-is($old_sum, $new_sum, 'combined container width is still equal');
-
-done_testing;
diff --git a/testcases/t/45-flattening.t b/testcases/t/45-flattening.t
deleted file mode 100644 (file)
index 98b93ef..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# by moving the window in the opposite orientation that its parent has, we
-# force i3 to create a new split container with the appropriate orientation.
-# However, when doing that two times in a row, we end up with two split
-# containers which are then redundant (workspace is horizontal, then v-split,
-# then h-split – we could just append the children of the latest h-split to the
-# workspace itself).
-#
-# This testcase checks that the tree is properly flattened after moving.
-#
-use X11::XCB qw(:all);
-use i3test;
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-my $left = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-my $right = open_standard_window($x);
-sleep 0.25;
-
-cmd 'move before v';
-cmd 'move after h';
-my $ws = get_ws($tmp);
-
-is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal');
-is(@{$ws->{nodes}}, 3, 'all three windows on workspace level');
-
-done_testing;
diff --git a/testcases/t/46-floating-reinsert.t b/testcases/t/46-floating-reinsert.t
deleted file mode 100644 (file)
index a4e90d4..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
-use i3test;
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-my $left = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-
-cmd 'split v';
-my $bottom = open_standard_window($x);
-sleep 0.25;
-
-my ($nodes, $focus) = get_ws_content($tmp);
-
-#############################################################################
-# 1: open a floating window, get it mapped
-#############################################################################
-
-my $x = X11::XCB::Connection->new;
-
-# Create a floating window
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#C0C0C0',
-    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-ok($window->mapped, 'Window is mapped');
-
-($nodes, $focus) = get_ws_content($tmp);
-is(@{$nodes->[1]->{nodes}}, 2, 'two windows in split con');
-
-#############################################################################
-# 2: make it tiling, see where it ends up
-#############################################################################
-
-cmd 'floating toggle';
-
-my ($nodes, $focus) = get_ws_content($tmp);
-
-is(@{$nodes->[1]->{nodes}}, 3, 'three windows in split con after floating toggle');
-
-done_testing;
diff --git a/testcases/t/47-regress-floatingmove.t b/testcases/t/47-regress-floatingmove.t
deleted file mode 100644 (file)
index 6e04916..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for moving a con outside of a floating con when there are no
-# tiling cons on a workspace
-#
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
-use i3test;
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-my $left = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-my $right = open_standard_window($x);
-sleep 0.25;
-
-# go to workspace level
-cmd 'level up';
-sleep 0.25;
-
-# make it floating
-cmd 'mode toggle';
-sleep 0.25;
-
-# move the con outside the floating con
-cmd 'move before v';
-sleep 0.25;
-
-does_i3_live;
-
-# move another con outside
-cmd '[id="' . $mid->id . '"] focus';
-cmd 'move before v';
-sleep 0.25;
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/48-regress-floatingmovews.t b/testcases/t/48-regress-floatingmovews.t
deleted file mode 100644 (file)
index 0bec541..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for correct focus behaviour when moving a floating con to
-# another workspace.
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-# open a tiling window on the first workspace
-open_standard_window($x);
-sleep 0.25;
-my $first = get_focused($tmp);
-
-# on a different ws, open a floating window
-my $otmp = fresh_workspace;
-open_standard_window($x);
-sleep 0.25;
-my $float = get_focused($otmp);
-cmd 'mode toggle';
-sleep 0.25;
-
-# move the floating con to first workspace
-cmd "move workspace $tmp";
-sleep 0.25;
-
-# switch to the first ws and check focus
-is(get_focused($tmp), $float, 'floating client correctly focused');
-
-done_testing;
diff --git a/testcases/t/50-regress-dock-restart.t b/testcases/t/50-regress-dock-restart.t
deleted file mode 100644 (file)
index a4f7beb..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for inplace restarting with dock clients
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# verify that there is no dock window yet
-#####################################################################
-
-# Children of all dockareas
-my @docked = get_dock_clients;
-
-is(@docked, 0, 'no dock clients yet');
-
-# open a dock client
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#FF0000',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
-
-#####################################################################
-# check that we can find it in the layout tree at the expected position
-#####################################################################
-
-@docked = get_dock_clients;
-is(@docked, 1, 'one dock client found');
-
-# verify the height
-my $docknode = $docked[0];
-
-is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
-
-# perform an inplace-restart
-cmd 'restart';
-
-sleep 0.25;
-
-does_i3_live;
-
-
-#####################################################################
-# check that we can still find the dock client
-#####################################################################
-
-@docked = get_dock_clients;
-is(@docked, 1, 'one dock client found');
-$docknode = $docked[0];
-
-is($docknode->{rect}->{height}, 30, 'dock node has unchanged height after restart');
-
-$window->destroy;
-
-sleep 0.25;
-
-@docked = get_dock_clients;
-is(@docked, 0, 'no dock clients found');
-
-#####################################################################
-# create a dock client with a 1px border
-#####################################################################
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    border => 1,
-    rect => [ 0, 0, 30, 20],
-    background_color => '#00FF00',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
-
-@docked = get_dock_clients;
-is(@docked, 1, 'one dock client found');
-$docknode = $docked[0];
-
-is($docknode->{rect}->{height}, 20, 'dock node has unchanged height');
-
-cmd 'restart';
-sleep 0.25;
-
-@docked = get_dock_clients;
-is(@docked, 1, 'one dock client found');
-$docknode = $docked[0];
-
-is($docknode->{rect}->{height}, 20, 'dock node has unchanged height');
-
-
-done_testing;
diff --git a/testcases/t/51-regress-float-size.t b/testcases/t/51-regress-float-size.t
deleted file mode 100644 (file)
index 881ef8c..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for setting a window to floating, tiling and opening a new window
-#
-use i3test;
-
-fresh_workspace;
-
-cmd 'open';
-cmd 'mode toggle';
-cmd 'mode toggle';
-cmd 'open';
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/52-regress-level-up.t b/testcases/t/52-regress-level-up.t
deleted file mode 100644 (file)
index 678cdd9..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for using level-up to get to the 'content'-container and
-# toggle floating
-#
-use i3test;
-
-fresh_workspace;
-
-cmd 'open';
-cmd 'level up';
-cmd 'level up';
-cmd 'mode toggle';
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/53-floating-originalsize.t b/testcases/t/53-floating-originalsize.t
deleted file mode 100644 (file)
index 16e62c2..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Test if the requested width/height is set after making the window floating.
-#
-use X11::XCB qw(:all);
-use i3test;
-
-my $tmp = fresh_workspace;
-
-my $x = X11::XCB::Connection->new;
-
-# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 400, 150],
-    background_color => '#C0C0C0',
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-my ($absolute, $top) = $window->rect;
-
-ok($window->mapped, 'Window is mapped');
-cmp_ok($absolute->{width}, '>', 400, 'i3 raised the width');
-cmp_ok($absolute->{height}, '>', 150, 'i3 raised the height');
-
-cmd 'floating toggle';
-sleep 0.25;
-
-($absolute, $top) = $window->rect;
-
-diag('new width: ' . $absolute->{width});
-diag('new height: ' . $absolute->{height});
-
-# we compare with a tolerance of ± 20 pixels for borders in each direction
-# (overkill, but hey)
-cmp_ok($absolute->{width}, '>', 400-20, 'width now > 380');
-cmp_ok($absolute->{width}, '<', 400+20, 'width now < 420');
-cmp_ok($absolute->{height}, '>', 150-20, 'height now > 130');
-cmp_ok($absolute->{height}, '<', 150+20, 'height now < 170');
-
-#cmp_ok($absolute->{width}, '>=', 75, 'i3 raised the width to 75');
-#cmp_ok($absolute->{height}, '>=', 50, 'i3 raised the height to 50');
-
-done_testing;
diff --git a/testcases/t/54-regress-multiple-dock.t b/testcases/t/54-regress-multiple-dock.t
deleted file mode 100644 (file)
index 36070db..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for closing one of multiple dock clients
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# verify that there is no dock window yet
-#####################################################################
-
-# Children of all dockareas
-my @docked = get_dock_clients;
-
-is(@docked, 0, 'no dock clients yet');
-
-#####################################################################
-# open a dock client
-#####################################################################
-
-my $first = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#FF0000',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$first->map;
-
-sleep 0.25;
-
-#####################################################################
-# Open a second dock client
-#####################################################################
-
-my $second = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#FF0000',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$second->map;
-
-sleep 0.25;
-
-#####################################################################
-# Kill the second dock client
-#####################################################################
-cmd "nop destroying dock client";
-$second->destroy;
-
-#####################################################################
-# Now issue a focus command
-#####################################################################
-cmd 'next v';
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/55-floating-split-size.t b/testcases/t/55-floating-split-size.t
deleted file mode 100644 (file)
index ecffbb1..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Test to see if i3 combines the geometry of all children in a split container
-# when setting the split container to floating
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# open a window with 200x80
-#####################################################################
-
-my $first = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 200, 80],
-    background_color => '#FF0000',
-);
-
-$first->map;
-
-sleep 0.25;
-
-#####################################################################
-# Open a second window with 300x90
-#####################################################################
-
-my $second = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 300, 90],
-    background_color => '#00FF00',
-);
-
-$second->map;
-
-sleep 0.25;
-
-#####################################################################
-# Set the parent to floating
-#####################################################################
-cmd 'nop setting floating';
-cmd 'focus parent';
-cmd 'floating enable';
-
-#####################################################################
-# Get geometry of the first floating node (the split container)
-#####################################################################
-
-my @nodes = @{get_ws($tmp)->{floating_nodes}};
-my $rect = $nodes[0]->{rect};
-
-# we compare the width with ± 20 pixels for borders
-cmp_ok($rect->{width}, '>', 500-20, 'width now > 480');
-cmp_ok($rect->{width}, '<', 500+20, 'width now < 520');
-# we compare the height with ± 40 pixels for decorations
-cmp_ok($rect->{height}, '>', 90-40, 'width now > 50');
-cmp_ok($rect->{height}, '<', 90+40, 'width now < 130');
-
-done_testing;
diff --git a/testcases/t/56-fullscreen-focus.t b/testcases/t/56-fullscreen-focus.t
deleted file mode 100644 (file)
index ee60dc7..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Test if new containers get focused when there is a fullscreen container at
-# the time of launching the new one.
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# open the left window
-#####################################################################
-
-my $left = open_standard_window($x, '#ff0000');
-
-is($x->input_focus, $left->id, 'left window focused');
-
-diag("left = " . $left->id);
-
-#####################################################################
-# Open the right window
-#####################################################################
-
-my $right = open_standard_window($x, '#00ff00');
-
-diag("right = " . $right->id);
-
-#####################################################################
-# Set the right window to fullscreen
-#####################################################################
-cmd 'nop setting fullscreen';
-cmd 'fullscreen';
-
-#####################################################################
-# Open a third window
-#####################################################################
-
-my $third = open_standard_window($x, '#0000ff');
-
-diag("third = " . $third->id);
-
-# move the fullscreen window to a different ws
-
-my $tmp2 = get_unused_workspace;
-
-cmd "move workspace $tmp2";
-
-# verify that the third window has the focus
-
-sleep 0.25;
-
-is($x->input_focus, $third->id, 'third window focused');
-
-done_testing;
diff --git a/testcases/t/57-regress-fullscreen-level-up.t b/testcases/t/57-regress-fullscreen-level-up.t
deleted file mode 100644 (file)
index 3e0b2fe..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test: level up should be a noop during fullscreen mode
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# open a window, verify it’s not in fullscreen mode
-#####################################################################
-
-my $win = open_standard_window($x);
-
-my $nodes = get_ws_content $tmp;
-is(@$nodes, 1, 'exactly one client');
-is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen');
-
-#####################################################################
-# make it fullscreen
-#####################################################################
-
-cmd 'nop making fullscreen';
-cmd 'fullscreen';
-
-my $nodes = get_ws_content $tmp;
-is($nodes->[0]->{fullscreen_mode}, 1, 'client fullscreen now');
-
-#####################################################################
-# send level up, try to un-fullscreen
-#####################################################################
-cmd 'level up';
-cmd 'fullscreen';
-
-my $nodes = get_ws_content $tmp;
-is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen any longer');
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/58-wm_take_focus.t b/testcases/t/58-wm_take_focus.t
deleted file mode 100644 (file)
index 04c785a..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests if the WM_TAKE_FOCUS protocol is correctly handled by i3
-#
-use X11::XCB qw(:all);
-use i3test;
-use v5.10;
-
-BEGIN {
-    use_ok('EV');
-    use_ok('AnyEvent');
-    use_ok('X11::XCB::Window');
-    use_ok('X11::XCB::Event::Generic');
-    use_ok('X11::XCB::Event::MapNotify');
-    use_ok('X11::XCB::Event::ClientMessage');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-subtest 'Window without WM_TAKE_FOCUS', sub {
-
-    my $tmp = fresh_workspace;
-
-    my $window = $x->root->create_child(
-        class => WINDOW_CLASS_INPUT_OUTPUT,
-        rect => [ 0, 0, 30, 30 ],
-        background_color => '#00ff00',
-        event_mask => [ 'structure_notify' ],
-    );
-
-    $window->name('Window 1');
-    $window->map;
-
-    my $cv = AE::cv;
-
-    my $prep = EV::prepare sub {
-        $x->flush;
-    };
-
-    my $check = EV::check sub {
-        while (defined(my $event = $x->poll_for_event)) {
-            if ($event->response_type == 161) {
-                # clientmessage
-                $cv->send(0);
-            }
-        }
-    };
-
-    my $w = EV::io $x->get_file_descriptor, EV::READ, sub {
-        # do nothing, we only need this watcher so that EV picks up the events
-    };
-
-    # Trigger timeout after 1 second
-    my $t = AE::timer 1, 0, sub {
-        $cv->send(1);
-    };
-
-    my $result = $cv->recv;
-    ok($result, 'cv result');
-
-    done_testing;
-};
-
-subtest 'Window with WM_TAKE_FOCUS', sub {
-
-    my $tmp = fresh_workspace;
-
-    my $window = $x->root->create_child(
-        class => WINDOW_CLASS_INPUT_OUTPUT,
-        rect => [ 0, 0, 30, 30 ],
-        background_color => '#00ff00',
-        event_mask => [ 'structure_notify' ],
-        protocols => [ $x->atom(name => 'WM_TAKE_FOCUS') ],
-    );
-
-    $window->name('Window 1');
-    $window->map;
-
-    my $cv = AE::cv;
-
-    my $prep = EV::prepare sub {
-        $x->flush;
-    };
-
-    my $check = EV::check sub {
-        while (defined(my $event = $x->poll_for_event)) {
-            if ($event->response_type == 161) {
-                $cv->send($event->data);
-            }
-        }
-    };
-
-    my $w = EV::io $x->get_file_descriptor, EV::READ, sub {
-        # do nothing, we only need this watcher so that EV picks up the events
-    };
-
-    my $t = AE::timer 1, 0, sub {
-        say "timer!";
-        $cv->send(undef);
-    };
-
-    my $result = $cv->recv;
-    ok(defined($result), 'got a ClientMessage');
-    if (defined($result)) {
-        my ($data, $time) = unpack("L2", $result);
-        is($data, $x->atom(name => 'WM_TAKE_FOCUS')->id, 'first uint32_t contains WM_TAKE_FOCUS atom');
-    }
-
-    done_testing;
-};
-
-
-done_testing;
diff --git a/testcases/t/59-socketpaths.t b/testcases/t/59-socketpaths.t
deleted file mode 100644 (file)
index 3335092..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# Tests if the various ipc_socket_path options are correctly handled
-#
-use i3test;
-use Cwd qw(abs_path);
-use Proc::Background;
-use File::Temp qw(tempfile tempdir);
-use POSIX qw(getuid);
-use v5.10;
-
-# assuming we are run by complete-run.pl
-my $i3_path = abs_path("../i3");
-
-#####################################################################
-# default case: socket will be created in /tmp/i3-<username>/ipc-socket.<pid>
-#####################################################################
-
-my ($fh, $tmpfile) = tempfile();
-say $fh "# i3 config file (v4)";
-say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
-close($fh);
-
-diag("Starting i3");
-my $i3cmd = "unset XDG_RUNTIME_DIR; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
-my $process = Proc::Background->new($i3cmd);
-sleep 1;
-
-diag("pid = " . $process->pid);
-
-my $folder = "/tmp/i3-" . getpwuid(getuid());
-ok(-d $folder, "folder $folder exists");
-my $socketpath = "$folder/ipc-socket." . $process->pid;
-ok(-S $socketpath, "file $socketpath exists and is a socket");
-
-exit_gracefully($process->pid, $socketpath);
-
-sleep 0.25;
-
-#####################################################################
-# XDG_RUNTIME_DIR case: socket gets created in $XDG_RUNTIME_DIR/i3/ipc-socket.<pid>
-#####################################################################
-
-my $rtdir = tempdir(CLEANUP => 1);
-
-ok(! -e "$rtdir/i3", "$rtdir/i3 does not exist yet");
-
-$i3cmd = "export XDG_RUNTIME_DIR=$rtdir; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
-$process = Proc::Background->new($i3cmd);
-sleep 1;
-
-ok(-d "$rtdir/i3", "$rtdir/i3 exists and is a directory");
-$socketpath = "$rtdir/i3/ipc-socket." . $process->pid;
-ok(-S $socketpath, "file $socketpath exists and is a socket");
-
-exit_gracefully($process->pid, $socketpath);
-
-sleep 0.25;
-
-#####################################################################
-# configuration file case: socket gets placed whereever we specify
-#####################################################################
-
-my $tmpdir = tempdir(CLEANUP => 1);
-$socketpath = $tmpdir . "/config.sock";
-ok(! -e $socketpath, "$socketpath does not exist yet");
-
-($fh, $tmpfile) = tempfile();
-say $fh "# i3 config file (v4)";
-say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
-say $fh "ipc-socket $socketpath";
-close($fh);
-
-$i3cmd = "export XDG_RUNTIME_DIR=$rtdir; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
-$process = Proc::Background->new($i3cmd);
-sleep 1;
-
-ok(-S $socketpath, "file $socketpath exists and is a socket");
-
-exit_gracefully($process->pid, $socketpath);
-
-done_testing;
diff --git a/testcases/t/61-regress-borders-restart.t b/testcases/t/61-regress-borders-restart.t
deleted file mode 100644 (file)
index 1acf6c6..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test to check if borders are correctly restored after an inplace
-# restart.
-# found in eb8ad348b28e243cba1972e802ca8ee636472fc9
-#
-use X11::XCB qw(:all);
-use List::Util qw(first);
-use i3test;
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-my $tmp = fresh_workspace;
-my $window = open_standard_window($x);
-
-sub get_border_style {
-    my @content = @{get_ws_content($tmp)};
-    my $wininfo = first { $_->{window} == $window->id } @content;
-
-    return $wininfo->{border};
-}
-
-is(get_border_style(), 'normal', 'border style normal');
-
-cmd 'border 1pixel';
-
-is(get_border_style(), '1pixel', 'border style 1pixel after changing');
-
-# perform an inplace-restart
-cmd 'restart';
-
-sleep 0.25;
-
-does_i3_live;
-
-is(get_border_style(), '1pixel', 'border style still 1pixel after restart');
-
-done_testing;
diff --git a/testcases/t/62-regress-dock-urgent.t b/testcases/t/62-regress-dock-urgent.t
deleted file mode 100644 (file)
index 8d18873..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for setting the urgent hint on dock clients.
-# found in 4be3178d4d360c2996217d811e61161c84d25898
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# verify that there is no dock window yet
-#####################################################################
-
-# Children of all dockareas
-my @docked = get_dock_clients;
-
-is(@docked, 0, 'no dock clients yet');
-
-# open a dock client
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30],
-    background_color => '#FF0000',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
-
-#####################################################################
-# check that we can find it in the layout tree at the expected position
-#####################################################################
-
-@docked = get_dock_clients;
-is(@docked, 1, 'one dock client found');
-
-# verify the height
-my $docknode = $docked[0];
-
-is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
-
-$window->add_hint('urgency');
-
-sleep 0.25;
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/63-wm-state.t b/testcases/t/63-wm-state.t
deleted file mode 100644 (file)
index 7e98328..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
-# unmapped.
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-    use_ok('X11::XCB::Event::Generic');
-    use_ok('X11::XCB::Event::MapNotify');
-    use_ok('X11::XCB::Event::ClientMessage');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#00ff00',
-    event_mask => [ 'structure_notify' ],
-);
-
-$window->name('Window 1');
-$window->map;
-
-diag('window mapped');
-
-sleep 0.5;
-
-is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal');
-
-$window->unmap;
-
-sleep 0.5;
-
-is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn');
-
-done_testing;
diff --git a/testcases/t/64-kill-win-vs-client.t b/testcases/t/64-kill-win-vs-client.t
deleted file mode 100644 (file)
index 2e0669b..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
-# unmapped.
-#
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-use i3test;
-
-my $x = X11::XCB::Connection->new;
-
-sub two_windows {
-    my $tmp = fresh_workspace;
-
-    ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-    my $first = open_standard_window($x);
-    my $second = open_standard_window($x);
-
-    is($x->input_focus, $second->id, 'second window focused');
-    ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
-
-    return $tmp;
-}
-
-##############################################################
-# 1: open two windows (in the same client), kill one and see if
-# the other one is still there
-##############################################################
-
-my $tmp = two_windows;
-
-cmd 'kill';
-
-sleep 0.25;
-
-ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
-
-##############################################################
-# 2: same test case as test 1, but with the explicit variant
-# 'kill window'
-##############################################################
-
-my $tmp = two_windows;
-
-cmd 'kill window';
-
-sleep 0.25;
-
-ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
-
-##############################################################
-# 3: open two windows (in the same client), use 'kill client'
-# and check if both are gone
-##############################################################
-
-my $tmp = two_windows;
-
-cmd 'kill client';
-
-sleep 0.25;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers left after killing');
-
-done_testing;
diff --git a/testcases/t/65-for_window.t b/testcases/t/65-for_window.t
deleted file mode 100644 (file)
index fb4c281..0000000
+++ /dev/null
@@ -1,353 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-#
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-use i3test;
-
-my $x = X11::XCB::Connection->new;
-
-##############################################################
-# 1: test the following directive:
-#    for_window [class="borderless"] border none
-# by first creating a window with a different class (should get
-# the normal border), then creating a window with the class
-# "borderless" (should get no border)
-##############################################################
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [class="borderless"] border none
-for_window [title="special borderless title"] border none
-EOT
-
-my $process = launch_with_config($config);
-
-my $tmp = fresh_workspace;
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#00ff00',
-);
-
-$window->name('Border window');
-$window->map;
-sleep 0.25;
-
-my @content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'normal', 'normal border');
-
-$window->unmap;
-sleep 0.25;
-
-my @content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 0, 'no more nodes');
-diag('content = '. Dumper(\@content));
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#00ff00',
-);
-
-$window->_create;
-
-# TODO: move this to X11::XCB::Window
-sub set_wm_class {
-    my ($id, $class, $instance) = @_;
-
-    # Add a _NET_WM_STRUT_PARTIAL hint
-    my $atomname = $x->atom(name => 'WM_CLASS');
-    my $atomtype = $x->atom(name => 'STRING');
-
-    $x->change_property(
-        PROP_MODE_REPLACE,
-        $id,
-        $atomname->id,
-        $atomtype->id,
-        8,
-        length($class) + length($instance) + 2,
-        "$instance\x00$class\x00"
-    );
-}
-
-set_wm_class($window->id, 'borderless', 'borderless');
-$window->name('Borderless window');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'none', 'no border');
-
-$window->unmap;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 0, 'no more nodes');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 2: match on the title, check if for_window is really executed
-# only once
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [class="borderless"] border none
-for_window [title="special borderless title"] border none
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#00ff00',
-);
-
-$window->name('special title');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'normal', 'normal border');
-
-$window->name('special borderless title');
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-is($content[0]->{border}, 'none', 'no border');
-
-$window->name('special title');
-sleep 0.25;
-
-cmd 'border normal';
-
-@content = @{get_ws_content($tmp)};
-is($content[0]->{border}, 'normal', 'border reset to normal');
-
-$window->name('special borderless title');
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-is($content[0]->{border}, 'normal', 'still normal border');
-
-$window->unmap;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 0, 'no more nodes');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 3: match on the title, set border style *and* a mark
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [class="borderless" title="usethis"] border none
-for_window [class="borderless"] border none
-for_window [title="special borderless title"] border none
-for_window [title="special mark title"] border none, mark bleh
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#00ff00',
-);
-
-$window->name('special mark title');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'none', 'no border');
-
-my $other = open_standard_window($x);
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 2, 'two nodes');
-is($content[0]->{border}, 'none', 'no border');
-is($content[1]->{border}, 'normal', 'normal border');
-ok(!$content[0]->{focused}, 'first one not focused');
-
-cmd qq|[con_mark="bleh"] focus|;
-
-@content = @{get_ws_content($tmp)};
-ok($content[0]->{focused}, 'first node focused');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 4: multiple criteria for the for_window command
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [class="borderless" title="usethis"] border none
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#00ff00',
-);
-
-$window->_create;
-
-set_wm_class($window->id, 'borderless', 'borderless');
-$window->name('usethis');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'none', 'no border');
-
-$window->unmap;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 0, 'no nodes on this workspace now');
-
-set_wm_class($window->id, 'borderless', 'borderless');
-$window->name('notthis');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'normal', 'no border');
-
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 5: check that a class criterion does not match the instance
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [class="foo"] border 1pixel
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#00ff00',
-);
-
-$window->_create;
-
-set_wm_class($window->id, 'bar', 'foo');
-$window->name('usethis');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'normal', 'normal border, not matched');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 6: check that the 'instance' criterion works
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [class="foo"] border 1pixel
-for_window [instance="foo"] border none
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#00ff00',
-);
-
-$window->_create;
-
-set_wm_class($window->id, 'bar', 'foo');
-$window->name('usethis');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'none', 'no border');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 7: check that invalid criteria don’t end up matching all windows
-##############################################################
-
-# this configuration is broken because "asdf" is not a valid integer
-# the for_window should therefore recognize this error and don’t add the
-# assignment
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [id="asdf"] border none
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#00ff00',
-);
-
-$window->_create;
-
-set_wm_class($window->id, 'bar', 'foo');
-$window->name('usethis');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'normal', 'normal border');
-
-exit_gracefully($process->pid);
-
-
-done_testing;
diff --git a/testcases/t/66-assign.t b/testcases/t/66-assign.t
deleted file mode 100644 (file)
index 776710e..0000000
+++ /dev/null
@@ -1,232 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# Tests if assignments work
-#
-use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-use v5.10;
-
-my $x = X11::XCB::Connection->new;
-
-# TODO: move to X11::XCB
-sub set_wm_class {
-    my ($id, $class, $instance) = @_;
-
-    # Add a _NET_WM_STRUT_PARTIAL hint
-    my $atomname = $x->atom(name => 'WM_CLASS');
-    my $atomtype = $x->atom(name => 'STRING');
-
-    $x->change_property(
-        PROP_MODE_REPLACE,
-        $id,
-        $atomname->id,
-        $atomtype->id,
-        8,
-        length($class) + length($instance) + 2,
-        "$instance\x00$class\x00"
-    );
-}
-
-
-#####################################################################
-# start a window and see that it does not get assigned with an empty config
-#####################################################################
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-EOT
-
-my $process = launch_with_config($config);
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#0000ff',
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace');
-
-exit_gracefully($process->pid);
-
-$window->destroy;
-
-sleep 0.25;
-
-#####################################################################
-# start a window and see that it gets assigned to a formerly unused
-# workspace
-#####################################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → targetws
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $workspaces = get_workspace_names;
-ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#0000ff',
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-ok(@{get_ws_content($tmp)} == 0, 'still no containers');
-ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists');
-
-$window->destroy;
-
-exit_gracefully($process->pid);
-
-sleep 0.25;
-
-#####################################################################
-# start a window and see that it gets assigned to a workspace which has content
-# already, next to the existing node.
-#####################################################################
-
-$process = launch_with_config($config);
-
-# initialize the target workspace, then go to a fresh one
-ok(!("targetws" ~~ @{get_workspace_names()}), 'targetws does not exist yet');
-cmd 'workspace targetws';
-cmp_ok(@{get_ws_content('targetws')}, '==', 0, 'no containers on targetws yet');
-cmd 'open';
-cmp_ok(@{get_ws_content('targetws')}, '==', 1, 'one container on targetws');
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-ok("targetws" ~~ @{get_workspace_names()}, 'targetws does not exist yet');
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#0000ff',
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-ok(@{get_ws_content($tmp)} == 0, 'still no containers');
-ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws');
-
-exit_gracefully($process->pid);
-
-#####################################################################
-# start a window and see that it gets assigned to a workspace which has content
-# already, next to the existing node.
-#####################################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → ~
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $workspaces = get_workspace_names;
-ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#0000ff',
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-my $content = get_ws($tmp);
-ok(@{$content->{nodes}} == 0, 'no tiling cons');
-ok(@{$content->{floating_nodes}} == 1, 'one floating con');
-
-$window->destroy;
-
-exit_gracefully($process->pid);
-
-sleep 0.25;
-
-#####################################################################
-# regression test: dock clients with floating assignments should not crash
-# (instead, nothing should happen - dock clients can’t float)
-# ticket #501
-#####################################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → ~
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my @docked = get_dock_clients;
-is(@docked, 0, 'no dock clients yet');
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#0000ff',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-my $content = get_ws($tmp);
-ok(@{$content->{nodes}} == 0, 'no tiling cons');
-ok(@{$content->{floating_nodes}} == 0, 'one floating con');
-@docked = get_dock_clients;
-is(@docked, 1, 'no dock clients yet');
-
-$window->destroy;
-
-does_i3_live;
-
-exit_gracefully($process->pid);
-
-sleep 0.25;
-
-done_testing;
diff --git a/testcases/t/67-workspace_layout.t b/testcases/t/67-workspace_layout.t
deleted file mode 100644 (file)
index 2b9f6e5..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# Tests the workspace_layout config option.
-#
-
-use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
-
-#####################################################################
-# 1: check that with an empty config, cons are place next to each
-# other and no split containers are created
-#####################################################################
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-EOT
-
-my $process = launch_with_config($config);
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $first = open_standard_window($x);
-my $second = open_standard_window($x);
-
-is($x->input_focus, $second->id, 'second window focused');
-ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
-isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
-isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
-
-exit_gracefully($process->pid);
-
-#####################################################################
-# 2: set workspace_layout stacked, check that when opening two cons,
-# they end up in a stacked con
-#####################################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-workspace_layout stacked
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-$first = open_standard_window($x);
-$second = open_standard_window($x);
-
-is($x->input_focus, $second->id, 'second window focused');
-my @content = @{get_ws_content($tmp)};
-ok(@content == 1, 'one con at workspace level');
-is($content[0]->{layout}, 'stacked', 'layout stacked');
-
-#####################################################################
-# 3: focus parent, open two new cons, check that they end up in a stacked
-# con
-#####################################################################
-
-cmd 'focus parent';
-my $right_top = open_standard_window($x);
-my $right_bot = open_standard_window($x);
-
-@content = @{get_ws_content($tmp)};
-is(@content, 2, 'two cons at workspace level after focus parent');
-is($content[0]->{layout}, 'stacked', 'layout stacked');
-is($content[1]->{layout}, 'stacked', 'layout stacked');
-
-#####################################################################
-# 4: move one of the cons to the right, check that it will end up in
-# a stacked con
-#####################################################################
-
-cmd 'move right';
-
-@content = @{get_ws_content($tmp)};
-is(@content, 3, 'three cons at workspace level after move');
-is($content[0]->{layout}, 'stacked', 'layout stacked');
-is($content[1]->{layout}, 'stacked', 'layout stacked');
-is($content[2]->{layout}, 'stacked', 'layout stacked');
-
-#####################################################################
-# 5: move it to the left again, check that the stacked con is deleted
-#####################################################################
-
-cmd 'move left';
-
-@content = @{get_ws_content($tmp)};
-is(@content, 2, 'two cons at workspace level after moving back');
-is($content[0]->{layout}, 'stacked', 'layout stacked');
-is($content[1]->{layout}, 'stacked', 'layout stacked');
-
-#####################################################################
-# 6: move it to a different workspace, check that it ends up in a
-# stacked con
-#####################################################################
-
-my $otmp = get_unused_workspace;
-
-cmd "move workspace $otmp";
-
-@content = @{get_ws_content($tmp)};
-is(@content, 2, 'still two cons on this workspace');
-is($content[0]->{layout}, 'stacked', 'layout stacked');
-is($content[1]->{layout}, 'stacked', 'layout stacked');
-
-@content = @{get_ws_content($otmp)};
-is(@content, 1, 'one con on target workspace');
-is($content[0]->{layout}, 'stacked', 'layout stacked');
-
-exit_gracefully($process->pid);
-
-done_testing;
diff --git a/testcases/t/68-regress-fullscreen-restart.t b/testcases/t/68-regress-fullscreen-restart.t
deleted file mode 100644 (file)
index fcc9ac7..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Verifies that i3 survives inplace restarts with fullscreen containers
-#
-use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
-
-fresh_workspace;
-
-open_standard_window($x);
-open_standard_window($x);
-
-cmd 'layout stacking';
-sleep 1;
-
-cmd 'fullscreen';
-sleep 1;
-
-cmd 'restart';
-sleep 1;
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/69-border-toggle.t b/testcases/t/69-border-toggle.t
deleted file mode 100644 (file)
index aec8df6..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests if the 'border toggle' command works correctly
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-
-cmd 'open';
-
-my @nodes = @{get_ws_content($tmp)};
-is(@nodes, 1, 'one container on this workspace');
-is($nodes[0]->{border}, 'normal', 'border style normal');
-
-cmd 'border 1pixel';
-@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
-
-cmd 'border none';
-@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, 'none', 'border style none');
-
-cmd 'border normal';
-@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, 'normal', 'border style back to normal');
-
-cmd 'border toggle';
-@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, 'none', 'border style none');
-
-cmd 'border toggle';
-@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
-
-cmd 'border toggle';
-@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, 'normal', 'border style back to normal');
-
-done_testing;
diff --git a/testcases/t/70-force_focus_wrapping.t b/testcases/t/70-force_focus_wrapping.t
deleted file mode 100644 (file)
index f2dfc18..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# Tests if the 'force_focus_wrapping' config directive works correctly.
-#
-use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
-
-#####################################################################
-# 1: test the wrapping behaviour without force_focus_wrapping
-#####################################################################
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-EOT
-
-my $process = launch_with_config($config);
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $first = open_standard_window($x);
-my $second = open_standard_window($x);
-
-cmd 'layout tabbed';
-cmd 'focus parent';
-
-my $third = open_standard_window($x);
-is($x->input_focus, $third->id, 'third window focused');
-
-cmd 'focus left';
-is($x->input_focus, $second->id, 'second window focused');
-
-cmd 'focus left';
-is($x->input_focus, $first->id, 'first window focused');
-
-# now test the wrapping
-cmd 'focus left';
-is($x->input_focus, $second->id, 'second window focused');
-
-# but focusing right should not wrap now, but instead focus the third window
-cmd 'focus right';
-is($x->input_focus, $third->id, 'third window focused');
-
-exit_gracefully($process->pid);
-
-#####################################################################
-# 2: test the wrapping behaviour with force_focus_wrapping
-#####################################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-force_focus_wrapping true
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-$first = open_standard_window($x);
-$second = open_standard_window($x);
-
-cmd 'layout tabbed';
-cmd 'focus parent';
-
-$third = open_standard_window($x);
-is($x->input_focus, $third->id, 'third window focused');
-
-cmd 'focus left';
-is($x->input_focus, $second->id, 'second window focused');
-
-cmd 'focus left';
-is($x->input_focus, $first->id, 'first window focused');
-
-# now test the wrapping
-cmd 'focus left';
-is($x->input_focus, $second->id, 'second window focused');
-
-# focusing right should now be forced to wrap
-cmd 'focus right';
-is($x->input_focus, $first->id, 'first window focused');
-
-exit_gracefully($process->pid);
-
-done_testing;
diff --git a/testcases/t/71-config-migrate.t b/testcases/t/71-config-migrate.t
deleted file mode 100644 (file)
index 6b41f2c..0000000
+++ /dev/null
@@ -1,352 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# Tests if i3-migrate-config-to-v4 correctly migrates all config file
-# directives and commands
-#
-use i3test;
-use Cwd qw(abs_path);
-use Proc::Background;
-use File::Temp qw(tempfile tempdir);
-use POSIX qw(getuid);
-use Data::Dumper;
-use v5.10;
-
-# reads in a whole file
-sub slurp {
-    open my $fh, '<', shift;
-    local $/;
-    <$fh>;
-}
-
-sub migrate_config {
-    my ($config) = @_;
-
-    my ($fh, $tmpfile) = tempfile();
-    print $fh $config;
-    close($fh);
-
-    my $cmd = "sh -c 'exec " . abs_path("../i3-migrate-config-to-v4") . " --v3 <$tmpfile'";
-    return [ split /\n/, qx($cmd) ];
-}
-
-sub line_exists {
-    my ($lines, $pattern) = @_;
-
-    for my $line (@$lines) {
-        return 1 if $line =~ $pattern;
-    }
-
-    return 0
-}
-
-#####################################################################
-# check that some directives remain untouched
-#####################################################################
-
-my $input = <<EOT;
-    font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-EOT
-
-my $output = migrate_config($input);
-ok(line_exists($output, qr|font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1|), 'font directive unchanged');
-
-$input = <<EOT;
-    floating_Modifier Mod1
-    focus_follows_mouse true
-    ipc-socket /tmp/i3-ipc.sock
-    ipc_socket /tmp/i3-ipc.sock
-    exec /usr/bin/i3
-    set stuff Mod1
-    assign "XTerm" → 3
-    assign "XTerm" → ~5
-    client.focused #2F343A #900000 #FFFFFF
-    client.focused_inactive #FF0000 #FF0000 #FF0000
-    client.unfocused #00FF00 #00FF00 #00FF00
-    client.urgent #0000FF #0000FF #0000FF
-    client.background #000000
-EOT
-
-$output = migrate_config($input);
-ok(line_exists($output, qr|^floating_Modifier Mod1$|), 'floating_modifier unchanged');
-ok(line_exists($output, qr|^focus_follows_mouse true$|), 'focus_follows_mouse unchanged');
-ok(line_exists($output, qr|^ipc-socket /tmp/i3-ipc.sock$|), 'ipc-socket unchanged');
-ok(line_exists($output, qr|^ipc_socket /tmp/i3-ipc.sock$|), 'ipc_socket unchanged');
-ok(line_exists($output, qr|^exec /usr/bin/i3|), 'exec unchanged');
-ok(line_exists($output, qr|^set stuff Mod1|), 'set unchanged');
-ok(line_exists($output, qr|^assign "XTerm" → 3|), 'assign unchanged');
-ok(line_exists($output, qr|^assign "XTerm" → ~5|), 'assign unchanged');
-ok(line_exists($output, qr|^client\.focused #2F343A #900000 #FFFFFF$|), 'client.focused unchanged');
-ok(line_exists($output, qr|^client\.focused_inactive #FF0000 #FF0000 #FF0000$|), 'client.focused_inactive unchanged');
-ok(line_exists($output, qr|^client\.unfocused #00FF00 #00FF00 #00FF00$|), 'client.unfocused unchanged');
-ok(line_exists($output, qr|^client\.urgent #0000FF #0000FF #0000FF$|), 'client.urgent unchanged');
-ok(line_exists($output, qr|^client\.background #000000$|), 'client.background unchanged');
-
-#####################################################################
-# check whether the bar colors get removed properly
-#####################################################################
-
-$input = <<EOT;
-    bar.focused #FFFF00 #FFFF00 #FFFF00
-    bar.unfocused #FFFF00 #FFFF00 #FFFF00
-    bar.urgent #FFFF00 #FFFF00 #FFFF00
-EOT
-
-$output = migrate_config($input);
-ok(!line_exists($output, qr|^bar\.|), 'no bar. lines');
-ok(line_exists($output, qr|^#.*REMOVED bar|), 'note bar. removed');
-
-
-#####################################################################
-# check whether the other directives get converted correctly
-#####################################################################
-
-$input = <<EOT;
-    new_container stacking
-    workspace_bar no
-    new_window bb
-EOT
-
-$output = migrate_config($input);
-ok(line_exists($output, qr|^workspace_layout stacking$|), 'new_container changed');
-ok(line_exists($output, qr|REMOVED workspace_bar|), 'workspace_bar removed');
-ok(!line_exists($output, qr|^workspace_bar|), 'no workspace_bar in the output');
-ok(line_exists($output, qr|^new_window none$|), 'new_window changed');
-
-#####################################################################
-# check whether new_window's parameters get changed correctly
-#####################################################################
-
-$output = migrate_config('new_window bb');
-ok(line_exists($output, qr|^new_window none$|), 'new_window bb changed');
-
-$output = migrate_config('new_window bn');
-ok(line_exists($output, qr|^new_window normal$|), 'new_window bn changed');
-
-$output = migrate_config('new_window bp');
-ok(line_exists($output, qr|^new_window 1pixel$|), 'new_window bp changed');
-
-#####################################################################
-# check that some commands remain untouched
-#####################################################################
-
-$input = <<EOT;
-    bindsym Mod1+s exec /usr/bin/urxvt
-    bindsym Mod1+s mark foo
-    bindsym Mod1+s restart
-    bindsym Mod1+s reload
-    bindsym Mod1+s exit
-    bindsym Mod1+s stack-limit cols 2
-    bindsym Mod1+s stack-limit rows 3
-    bind Mod1+c exec /usr/bin/urxvt
-    mode "asdf" {
-        bind 36 mode default
-    }
-EOT
-
-$output = migrate_config($input);
-ok(line_exists($output, qr|^bindsym Mod1\+s exec /usr/bin/urxvt$|), 'exec unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s mark foo$|), 'mark unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s restart$|), 'restart unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s reload$|), 'reload unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s exit$|), 'exit unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s stack-limit cols 2$|), 'stack-limit unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s stack-limit rows 3$|), 'stack-limit unchanged');
-ok(line_exists($output, qr|^bindcode Mod1\+c exec /usr/bin/urxvt$|), 'bind changed to bindcode');
-ok(line_exists($output, qr|^mode "asdf" {$|), 'mode asdf unchanged');
-ok(line_exists($output, qr|^bindcode 36 mode \"default\"$|), 'mode default unchanged');
-ok(line_exists($output, qr|^}$|), 'closing mode bracket still there');
-
-#####################################################################
-# check the simple command replacements
-#####################################################################
-
-$input = <<EOT;
-    bindsym Mod1+s s
-    bindsym Mod1+s d
-    bindsym Mod1+s T
-
-    bindsym Mod1+s f
-    bindsym Mod1+s fg
-
-    bindsym Mod1+s t
-
-    bindsym Mod1+s h
-    bindsym Mod1+s j
-    bindsym Mod1+s k
-    bindsym Mod1+s l
-
-    bindsym Mod1+s mh
-    bindsym Mod1+s mj
-    bindsym Mod1+s mk
-    bindsym Mod1+s ml
-
-    bindsym Mod1+s bn
-    bindsym Mod1+s bp
-    bindsym Mod1+s bb
-    bindsym Mod1+s bt
-
-    bindsym Mod1+j wch
-    bindsym Mod1+j wcml
-
-    bindsym Mod1+k kill
-
-    bindsym Mod1+n nw
-    bindsym Mod1+p pw
-EOT
-
-$output = migrate_config($input);
-ok(line_exists($output, qr|^bindsym Mod1\+s layout stacking$|), 's replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s layout default$|), 'd replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s layout tabbed$|), 'T replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen$|), 'f replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen global$|), 'fg replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s floating toggle$|), 't replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s focus left$|), 'h replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s focus down$|), 'j replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s focus up$|), 'k replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s focus right$|), 'l replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s move left$|), 'mh replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s move down$|), 'mj replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s move up$|), 'mk replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s move right$|), 'ml replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s border normal$|), 'bn replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s border 1pixel$|), 'bp replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s border none$|), 'bb replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s border toggle$|), 'bt replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+j focus parent; focus left$|), 'with container replaced with focus parent; focus left');
-ok(line_exists($output, qr|^bindsym Mod1\+j focus parent; move right$|), 'with container replaced with focus parent; move right');
-ok(line_exists($output, qr|^bindsym Mod1\+k kill$|), 'kill unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+n workspace next$|), 'nw replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+p workspace prev$|), 'pw replaced');
-
-#####################################################################
-# check more advanced replacements
-#####################################################################
-
-$input = <<EOT;
-    bindsym Mod1+s goto foo
-EOT
-
-$output = migrate_config($input);
-ok(line_exists($output, qr|^bindsym Mod1\+s \[con_mark="foo"\] focus$|), 'goto replaced');
-
-#####################################################################
-# check whether focus's parameters get changed correctly
-#####################################################################
-
-$output = migrate_config('bindsym Mod1+f focus 3');
-ok(line_exists($output, qr|^#.*focus.*obsolete.*focus 3$|), 'focus [number] gone');
-
-$output = migrate_config('bindsym Mod1+f focus floating');
-ok(line_exists($output, qr|^bindsym Mod1\+f focus floating$|), 'focus floating unchanged');
-
-$output = migrate_config('bindsym Mod1+f focus tiling');
-ok(line_exists($output, qr|^bindsym Mod1\+f focus tiling$|), 'focus tiling unchanged');
-
-$output = migrate_config('bindsym Mod1+f focus ft');
-ok(line_exists($output, qr|^bindsym Mod1\+f focus mode_toggle$|), 'focus ft changed');
-
-#####################################################################
-# check whether resize's parameters get changed correctly
-#####################################################################
-
-$output = migrate_config('bindsym Mod1+f resize left +10');
-ok(line_exists($output, qr|^bindsym Mod1\+f resize grow left 10 px$|), 'resize left changed');
-
-$output = migrate_config('bindsym Mod1+f resize top -20');
-ok(line_exists($output, qr|^bindsym Mod1\+f resize shrink up 20 px$|), 'resize top changed');
-
-$output = migrate_config('bindsym Mod1+f resize right -20');
-ok(line_exists($output, qr|^bindsym Mod1\+f resize shrink right 20 px$|), 'resize right changed');
-
-$output = migrate_config('bindsym Mod1+f resize bottom +23');
-ok(line_exists($output, qr|^bindsym Mod1\+f resize grow down 23 px$|), 'resize bottom changed');
-
-#####################################################################
-# also resizing, but with indention this time
-#####################################################################
-
-$output = migrate_config("bindsym Mod1+f resize          left    \t +10");
-ok(line_exists($output, qr|^bindsym Mod1\+f resize grow left 10 px$|), 'resize left changed');
-
-#####################################################################
-# check whether jump's parameters get changed correctly
-#####################################################################
-
-$output = migrate_config('bindsym Mod1+f jump 3');
-ok(line_exists($output, qr|^#.*obsolete.*jump 3$|), 'jump to workspace removed');
-
-$output = migrate_config('bindsym Mod1+f jump 3 4 5');
-ok(line_exists($output, qr|^#.*obsolete.*jump 3 4 5$|), 'jump to workspace + col/row removed');
-
-$output = migrate_config('bindsym Mod1+f jump "XTerm"');
-ok(line_exists($output, qr|^bindsym Mod1\+f \[class="XTerm"\] focus$|), 'jump changed');
-
-$output = migrate_config('bindsym Mod1+f jump "XTerm/irssi"');
-ok(line_exists($output, qr|^bindsym Mod1\+f \[class="XTerm" title="irssi"\] focus$|), 'jump changed');
-
-#####################################################################
-# check whether workspace commands are handled correctly
-#####################################################################
-
-$output = migrate_config('workspace 3 output VGA-1');
-ok(line_exists($output, qr|^workspace 3 output VGA-1$|), 'workspace assignment unchanged');
-
-$output = migrate_config('workspace 3 work');
-ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
-ok(line_exists($output, qr|#.*workspace name.*bindings|), 'note present');
-
-$input = <<EOT;
-    workspace 3 work
-    bindsym Mod1+3 3
-EOT
-$output = migrate_config($input);
-ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
-ok(line_exists($output, qr|^bindsym Mod1\+3 workspace work|), 'named workspace in bindings');
-
-# The same, but in reverse order
-$input = <<EOT;
-    bindsym Mod1+3 3
-    workspace 3 work
-EOT
-$output = migrate_config($input);
-ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
-ok(line_exists($output, qr|^bindsym Mod1\+3 workspace work|), 'named workspace in bindings');
-
-$output = migrate_config('bindsym Mod1+3 3');
-ok(line_exists($output, qr|^bindsym Mod1\+3 workspace 3|), 'workspace changed');
-
-$output = migrate_config('bindsym Mod1+3 m3');
-ok(line_exists($output, qr|^bindsym Mod1\+3 move workspace 3|), 'move workspace changed');
-
-$input = <<EOT;
-    workspace 3 work
-    bindsym Mod1+3 m3
-EOT
-$output = migrate_config($input);
-ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
-ok(line_exists($output, qr|^bindsym Mod1\+3 move workspace work|), 'move to named workspace in bindings');
-
-#####################################################################
-# check whether an i3bar call is added if the workspace bar bar was enabled
-#####################################################################
-
-$output = migrate_config('');
-ok(line_exists($output, qr|i3bar|), 'i3bar added');
-
-$output = migrate_config('workspace_bar enable');
-ok(line_exists($output, qr|i3bar|), 'i3bar added');
-
-$output = migrate_config('workspace_bar no');
-ok(!line_exists($output, qr|i3bar|), 'no i3bar added');
-
-#####################################################################
-# check whether the mode command gets quotes
-#####################################################################
-
-$output = migrate_config('bindsym Mod1+m mode foobar');
-ok(line_exists($output, qr|^bindsym Mod1\+m mode "foobar"|), 'mode got quotes');
-
-done_testing();
diff --git a/testcases/t/72-start-on-named-ws.t b/testcases/t/72-start-on-named-ws.t
deleted file mode 100644 (file)
index 1266186..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# checks if i3 starts up on workspace '1' or the first configured named workspace
-#
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-use i3test;
-
-my $x = X11::XCB::Connection->new;
-
-##############################################################
-# 1: i3 should start with workspace '1'
-##############################################################
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-EOT
-
-my $process = launch_with_config($config);
-
-my @names = @{get_workspace_names()};
-cmp_deeply(\@names, [ '1' ], 'i3 starts on workspace 1 without any configuration');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 2: with named workspaces, i3 should start on the first named one
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-
-bindsym Mod1+1 workspace foobar
-EOT
-
-$process = launch_with_config($config);
-
-my @names = @{get_workspace_names()};
-cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 3: the same test as 2, but with a quoted workspace name
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-
-bindsym Mod1+1 workspace "foobar"
-EOT
-
-$process = launch_with_config($config);
-
-my @names = @{get_workspace_names()};
-cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
-
-exit_gracefully($process->pid);
-
-done_testing;
diff --git a/testcases/t/73-regress-focus-assign.t b/testcases/t/73-regress-focus-assign.t
deleted file mode 100644 (file)
index bab7c9a..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# Regression: Checks if focus is stolen when a window is managed which is
-# assigned to an invisible workspace
-#
-use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-use v5.10;
-
-my $x = X11::XCB::Connection->new;
-
-# TODO: move to X11::XCB
-sub set_wm_class {
-    my ($id, $class, $instance) = @_;
-
-    # Add a _NET_WM_STRUT_PARTIAL hint
-    my $atomname = $x->atom(name => 'WM_CLASS');
-    my $atomtype = $x->atom(name => 'STRING');
-
-    $x->change_property(
-        PROP_MODE_REPLACE,
-        $id,
-        $atomname->id,
-        $atomtype->id,
-        8,
-        length($class) + length($instance) + 2,
-        "$instance\x00$class\x00"
-    );
-}
-
-
-#####################################################################
-# start a window and see that it does not get assigned with an empty config
-#####################################################################
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → targetws
-EOT
-
-my $process = launch_with_config($config);
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-ok(get_ws($tmp)->{focused}, 'current workspace focused');
-
-my $window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#0000ff',
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-
-ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
-ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
-ok(get_ws($tmp)->{focused}, 'current workspace still focused');
-
-#####################################################################
-# the same test, but with a floating window
-#####################################################################
-
-$window = $x->root->create_child(
-    class => WINDOW_CLASS_INPUT_OUTPUT,
-    rect => [ 0, 0, 30, 30 ],
-    background_color => '#0000ff',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-
-ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
-ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
-ok(get_ws($tmp)->{focused}, 'current workspace still focused');
-
-exit_gracefully($process->pid);
-
-$window->destroy;
-
-done_testing;
diff --git a/testcases/t/74-regress-focus-toggle.t b/testcases/t/74-regress-focus-toggle.t
deleted file mode 100644 (file)
index 469d1be..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression: Checks if i3 still lives after using 'focus mode_toggle' on an
-# empty workspace. This regression was fixed in
-# 0848844f2d41055f6ffc69af1149d7a873460976.
-#
-use i3test;
-use v5.10;
-
-my $tmp = fresh_workspace;
-
-cmd 'focus mode_toggle';
-
-does_i3_live;
-
-done_testing;
diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm
deleted file mode 100644 (file)
index 054bb2a..0000000
+++ /dev/null
@@ -1,265 +0,0 @@
-package i3test;
-# vim:ts=4:sw=4:expandtab
-
-use File::Temp qw(tmpnam tempfile tempdir);
-use Test::Builder;
-use X11::XCB::Rect;
-use X11::XCB::Window;
-use X11::XCB qw(:all);
-use AnyEvent::I3;
-use List::Util qw(first);
-use List::MoreUtils qw(lastval);
-use Time::HiRes qw(sleep);
-use Try::Tiny;
-use Cwd qw(abs_path);
-use Proc::Background;
-
-use v5.10;
-
-use Exporter ();
-our @EXPORT = qw(get_workspace_names get_unused_workspace fresh_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window get_dock_clients cmd does_i3_live exit_gracefully workspace_exists focused_ws get_socket_path launch_with_config);
-
-my $tester = Test::Builder->new();
-my $_cached_socket_path = undef;
-my $tmp_socket_path = undef;
-
-BEGIN {
-    my $window_count = 0;
-    sub counter_window {
-        return $window_count++;
-    }
-}
-
-sub import {
-    my $class = shift;
-    my $pkg = caller;
-    eval "package $pkg;
-use Test::Most" . (@_ > 0 ? " qw(@_)" : "") . ";
-use Data::Dumper;
-use AnyEvent::I3;
-use Time::HiRes qw(sleep);
-use Test::Deep qw(eq_deeply cmp_deeply cmp_set cmp_bag cmp_methods useclass noclass set bag subbagof superbagof subsetof supersetof superhashof subhashof bool str arraylength Isa ignore methods regexprefonly regexpmatches num regexponly scalref reftype hashkeysonly blessed array re hash regexpref hash_each shallow array_each code arrayelementsonly arraylengthonly scalarrefonly listmethods any hashkeys isa);
-use v5.10;
-use strict;
-use warnings;
-";
-    @_ = ($class);
-    goto \&Exporter::import;
-}
-
-sub open_standard_window {
-    my ($x, $color) = @_;
-
-    $color ||= '#c0c0c0';
-
-    my $window = $x->root->create_child(
-        class => WINDOW_CLASS_INPUT_OUTPUT,
-        rect => [ 0, 0, 30, 30 ],
-        background_color => $color,
-    );
-
-    $window->name('Window ' . counter_window());
-    $window->map;
-
-    sleep(0.25);
-
-    return $window;
-}
-
-sub open_empty_con {
-    my ($i3) = @_;
-
-    my $reply = $i3->command('open')->recv;
-    return $reply->{id};
-}
-
-sub get_workspace_names {
-    my $i3 = i3(get_socket_path());
-    my $tree = $i3->get_tree->recv;
-    my @outputs = @{$tree->{nodes}};
-    my @cons;
-    for my $output (@outputs) {
-        # get the first CT_CON of each output
-        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
-        @cons = (@cons, @{$content->{nodes}});
-    }
-    [ map { $_->{name} } @cons ]
-}
-
-sub get_unused_workspace {
-    my @names = get_workspace_names();
-    my $tmp;
-    do { $tmp = tmpnam() } while ($tmp ~~ @names);
-    $tmp
-}
-
-sub fresh_workspace {
-    my $unused = get_unused_workspace;
-    cmd("workspace $unused");
-    $unused
-}
-
-sub get_ws {
-    my ($name) = @_;
-    my $i3 = i3(get_socket_path());
-    my $tree = $i3->get_tree->recv;
-
-    my @outputs = @{$tree->{nodes}};
-    my @workspaces;
-    for my $output (@outputs) {
-        # get the first CT_CON of each output
-        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
-        @workspaces = (@workspaces, @{$content->{nodes}});
-    }
-
-    # as there can only be one workspace with this name, we can safely
-    # return the first entry
-    return first { $_->{name} eq $name } @workspaces;
-}
-
-#
-# returns the content (== tree, starting from the node of a workspace)
-# of a workspace. If called in array context, also includes the focus
-# stack of the workspace
-#
-sub get_ws_content {
-    my ($name) = @_;
-    my $con = get_ws($name);
-    return wantarray ? ($con->{nodes}, $con->{focus}) : $con->{nodes};
-}
-
-sub get_focused {
-    my ($ws) = @_;
-    my $con = get_ws($ws);
-
-    my @focused = @{$con->{focus}};
-    my $lf;
-    while (@focused > 0) {
-        $lf = $focused[0];
-        last unless defined($con->{focus});
-        @focused = @{$con->{focus}};
-        @cons = grep { $_->{id} == $lf } (@{$con->{nodes}}, @{$con->{'floating_nodes'}});
-        $con = $cons[0];
-    }
-
-    return $lf;
-}
-
-sub get_dock_clients {
-    my $which = shift;
-
-    my $tree = i3(get_socket_path())->get_tree->recv;
-    my @outputs = @{$tree->{nodes}};
-    # Children of all dockareas
-    my @docked;
-    for my $output (@outputs) {
-        if (!defined($which)) {
-            @docked = (@docked, map { @{$_->{nodes}} }
-                                grep { $_->{type} == 5 }
-                                @{$output->{nodes}});
-        } elsif ($which eq 'top') {
-            my $first = first { $_->{type} == 5 } @{$output->{nodes}};
-            @docked = (@docked, @{$first->{nodes}});
-        } elsif ($which eq 'bottom') {
-            my $last = lastval { $_->{type} == 5 } @{$output->{nodes}};
-            @docked = (@docked, @{$last->{nodes}});
-        }
-    }
-    return @docked;
-}
-
-sub cmd {
-    i3(get_socket_path())->command(@_)->recv
-}
-
-sub workspace_exists {
-    my ($name) = @_;
-    ($name ~~ @{get_workspace_names()})
-}
-
-sub focused_ws {
-    my $i3 = i3(get_socket_path());
-    my $tree = $i3->get_tree->recv;
-    my @outputs = @{$tree->{nodes}};
-    my @cons;
-    for my $output (@outputs) {
-        # get the first CT_CON of each output
-        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
-        my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
-        return $first->{name}
-    }
-}
-
-sub does_i3_live {
-    my $tree = i3(get_socket_path())->get_tree->recv;
-    my @nodes = @{$tree->{nodes}};
-    my $ok = (@nodes > 0);
-    $tester->ok($ok, 'i3 still lives');
-    return $ok;
-}
-
-# Tries to exit i3 gracefully (with the 'exit' cmd) or kills the PID if that fails
-sub exit_gracefully {
-    my ($pid, $socketpath) = @_;
-    $socketpath ||= get_socket_path();
-
-    my $exited = 0;
-    try {
-        say "Exiting i3 cleanly...";
-        i3($socketpath)->command('exit')->recv;
-        $exited = 1;
-    };
-
-    if (!$exited) {
-        kill(9, $pid) or die "could not kill i3";
-    }
-}
-
-# Gets the socket path from the I3_SOCKET_PATH atom stored on the X11 root window
-sub get_socket_path {
-    my ($cache) = @_;
-    $cache ||= 1;
-
-    if ($cache && defined($_cached_socket_path)) {
-        return $_cached_socket_path;
-    }
-
-    my $x = X11::XCB::Connection->new;
-    my $atom = $x->atom(name => 'I3_SOCKET_PATH');
-    my $cookie = $x->get_property(0, $x->get_root_window(), $atom->id, GET_PROPERTY_TYPE_ANY, 0, 256);
-    my $reply = $x->get_property_reply($cookie->{sequence});
-    my $socketpath = $reply->{value};
-    $_cached_socket_path = $socketpath;
-    return $socketpath;
-}
-
-#
-# launches a new i3 process with the given string as configuration file.
-# useful for tests which test specific config file directives.
-#
-# be sure to use !NO_I3_INSTANCE! somewhere in the file to signal
-# complete-run.pl that it should not create an instance of i3
-#
-sub launch_with_config {
-    my ($config) = @_;
-
-    if (!defined($tmp_socket_path)) {
-        $tmp_socket_path = File::Temp::tempnam('/tmp', 'i3-test-socket-');
-    }
-
-    my ($fh, $tmpfile) = tempfile('i3-test-config-XXXXX', UNLINK => 1);
-    say $fh $config;
-    say $fh "ipc-socket $tmp_socket_path";
-    close($fh);
-
-    my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
-    my $process = Proc::Background->new($i3cmd);
-    sleep 1;
-
-    # force update of the cached socket path in lib/i3test
-    get_socket_path(0);
-
-    return $process;
-}
-
-1