]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'master' into next
authorMichael Stapelberg <michael@stapelberg.de>
Mon, 19 Mar 2012 20:42:08 +0000 (21:42 +0100)
committerMichael Stapelberg <michael@stapelberg.de>
Mon, 19 Mar 2012 20:42:08 +0000 (21:42 +0100)
189 files changed:
.gitignore
Makefile
common.mk
debian/changelog
debian/control
debian/i3-wm.docs
debian/patches/manpage-x-terminal-emulator.patch
debian/patches/use-x-terminal-emulator.patch
debian/rules
docs/Makefile
docs/debugging
docs/debugging-release-version [new file with mode: 0644]
docs/hacking-howto
docs/i3bar-protocol [new file with mode: 0644]
docs/ipc
docs/slides-2012-01-25/TdilE.jpg [new file with mode: 0644]
docs/slides-2012-01-25/Ubuntu_Linux_Jaunty_screenshot.png [new file with mode: 0644]
docs/slides-2012-01-25/i3.tex [new file with mode: 0644]
docs/userguide
generate-command-parser.pl [new file with mode: 0755]
i3-config-wizard/Makefile
i3-config-wizard/main.c
i3-dump-log/Makefile [new file with mode: 0644]
i3-dump-log/main.c [new file with mode: 0644]
i3-input/i3-input.h
i3-input/main.c
i3-input/ucs2_to_utf8.c [deleted file]
i3-migrate-config-to-v4
i3-msg/main.c
i3-nagbar/main.c
i3-sensible-editor
i3-sensible-pager
i3-sensible-terminal
i3.applications.desktop [new file with mode: 0644]
i3.config
i3.config.keycodes
i3.desktop [deleted file]
i3.xsession.desktop [new file with mode: 0644]
i3bar/include/common.h
i3bar/include/config.h
i3bar/include/ucs2_to_utf8.h [deleted file]
i3bar/include/xcb.h
i3bar/src/child.c
i3bar/src/config.c
i3bar/src/ucs2_to_utf8.c [deleted file]
i3bar/src/workspaces.c
i3bar/src/xcb.c
include/all.h
include/atoms.xmacro
include/commands.h [new file with mode: 0644]
include/commands_parser.h [new file with mode: 0644]
include/config.h
include/data.h
include/i3.h
include/libi3.h
include/log.h
include/scratchpad.h [new file with mode: 0644]
include/shmlog.h [new file with mode: 0644]
include/util.h
include/workspace.h
include/xcb.h
libi3/font.c [new file with mode: 0644]
libi3/get_colorpixel.c
libi3/get_socket_path.c [deleted file]
libi3/load_font.c [deleted file]
libi3/root_atom_contents.c [new file with mode: 0644]
libi3/ucs2_conversion.c [new file with mode: 0644]
man/Makefile
man/i3-dump-log.man [new file with mode: 0644]
man/i3-msg.man
man/i3-sensible-editor.man
man/i3-sensible-pager.man
man/i3-sensible-terminal.man
man/i3.man
parser-specs/commands.spec [new file with mode: 0644]
parser-specs/highlighting.vim [new file with mode: 0644]
show-download-count.sh [new file with mode: 0644]
src/assignments.c
src/cfgparse.l
src/cfgparse.y
src/cmdparse.l [deleted file]
src/cmdparse.y [deleted file]
src/commands.c [new file with mode: 0644]
src/commands_parser.c [new file with mode: 0644]
src/con.c
src/config.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/randr.c
src/render.c
src/scratchpad.c [new file with mode: 0644]
src/sighandler.c
src/startup.c
src/tree.c
src/util.c
src/window.c
src/workspace.c
src/x.c
src/xcb.c
testcases/Makefile.PL
testcases/complete-run.pl
testcases/lib/SocketActivation.pm
testcases/lib/StartXDummy.pm
testcases/lib/TestWorker.pm [new file with mode: 0644]
testcases/lib/i3test.pm
testcases/restart-state.golden [new file with mode: 0644]
testcases/t/001-tile.t
testcases/t/002-i3-sync.t
testcases/t/003-ipc.t
testcases/t/004-unmanaged.t
testcases/t/005-floating.t
testcases/t/100-fullscreen.t
testcases/t/101-focus.t
testcases/t/102-dock.t
testcases/t/103-move.t
testcases/t/104-focus-stack.t
testcases/t/105-stacking.t
testcases/t/111-goto.t
testcases/t/112-floating-resize.t
testcases/t/113-urgent.t
testcases/t/114-client-leader.t
testcases/t/116-nestedcons.t
testcases/t/117-workspace.t
testcases/t/119-match.t
testcases/t/122-split.t
testcases/t/124-move.t
testcases/t/127-regress-floating-parent.t
testcases/t/128-open-order.t
testcases/t/129-focus-after-close.t
testcases/t/130-close-empty-split.t
testcases/t/132-move-workspace.t
testcases/t/133-size-hints.t
testcases/t/135-floating-focus.t
testcases/t/136-floating-ws-empty.t
testcases/t/137-floating-unmap.t
testcases/t/138-floating-attach.t
testcases/t/139-ws-numbers.t
testcases/t/140-focus-lost.t
testcases/t/141-resize.t
testcases/t/143-regress-floating-restart.t
testcases/t/144-regress-floating-resize.t
testcases/t/145-flattening.t
testcases/t/146-floating-reinsert.t
testcases/t/147-regress-floatingmove.t
testcases/t/148-regress-floatingmovews.t
testcases/t/150-regress-dock-restart.t
testcases/t/153-floating-originalsize.t
testcases/t/154-regress-multiple-dock.t
testcases/t/155-floating-split-size.t
testcases/t/156-fullscreen-focus.t
testcases/t/157-regress-fullscreen-level-up.t
testcases/t/158-wm_take_focus.t
testcases/t/159-socketpaths.t
testcases/t/161-regress-borders-restart.t
testcases/t/162-regress-dock-urgent.t
testcases/t/163-wm-state.t
testcases/t/164-kill-win-vs-client.t
testcases/t/165-for_window.t
testcases/t/166-assign.t
testcases/t/167-workspace_layout.t
testcases/t/168-regress-fullscreen-restart.t
testcases/t/170-force_focus_wrapping.t
testcases/t/171-config-migrate.t
testcases/t/172-start-on-named-ws.t
testcases/t/173-get-marks.t
testcases/t/173-regress-focus-assign.t
testcases/t/174-border-config.t
testcases/t/175-startup-notification.t
testcases/t/176-workspace-baf.t
testcases/t/177-bar-config.t
testcases/t/180-fd-leaks.t [new file with mode: 0644]
testcases/t/181-regress-float-border.t [new file with mode: 0644]
testcases/t/183-config-variables.t [new file with mode: 0644]
testcases/t/185-scratchpad.t [new file with mode: 0644]
testcases/t/187-commands-parser.t [new file with mode: 0644]
testcases/t/188-regress-focus-restart.t [new file with mode: 0644]
testcases/t/189-floating-constraints.t [new file with mode: 0644]
testcases/t/500-multi-monitor.t [new file with mode: 0644]
testcases/t/501-scratchpad.t [new file with mode: 0644]
testcases/t/502-focus-output.t [new file with mode: 0644]
testcases/t/503-workspace.t [new file with mode: 0644]
testcases/t/504-move-workspace-to-output.t [new file with mode: 0644]

index cad6ad9a2120daffdde2a58c3b705fa253c0a77a..b641592b9069e8222382d3bd2ca23d512753cf52 100644 (file)
@@ -7,6 +7,7 @@ loglevels.tmp
 *.gcno
 testcases/testsuite-*
 testcases/latest
+testcases/Makefile
 *.output
 *.tab.*
 *.yy.c
index c0797839c97e9ffe5cd61ad98908b0caac8f0447..25290b81318e52ad558c90a20c02ab751915412a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -3,10 +3,11 @@ TOPDIR=$(shell pwd)
 include $(TOPDIR)/common.mk
 
 # Depend on the object files of all source-files in src/*.c and on all header files
-AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c
+AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c
 FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c))
 FILES:=$(FILES:.c=.o)
 HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
+CMDPARSE_HEADERS:=include/GENERATED_call.h include/GENERATED_enums.h include/GENERATED_tokens.h
 
 # Recursively generate loglevels.h by explicitly calling make
 # We need this step because we need to ensure that loglevels.h will be
@@ -18,7 +19,7 @@ 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 i3-dump-log
 
 # Depend on the specific file (.c for each .o) and on all headers
 src/%.o: src/%.c ${HEADERS}
@@ -27,7 +28,7 @@ src/%.o: src/%.c ${HEADERS}
 
 all: i3 subdirs
 
-i3: libi3/libi3.a src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES}
+i3: libi3/libi3.a src/cfgparse.y.o src/cfgparse.yy.o ${FILES}
        echo "[i3] LINK i3"
        $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
 
@@ -53,27 +54,33 @@ loglevels.h:
        done; \
        echo "};") > include/loglevels.h;
 
+# The GENERATED_* files are actually all created from a single pass, so all
+# files just depend on the first one.
+include/GENERATED_call.h: generate-command-parser.pl parser-specs/commands.spec
+       echo "[i3] Generating command parser"
+       (cd include; ../generate-command-parser.pl)
+include/GENERATED_enums.h: include/GENERATED_call.h
+include/GENERATED_tokens.h: include/GENERATED_call.h
+
+# This target compiles the command parser twice:
+# Once with -DTEST_PARSER, creating a stand-alone executable used for tests,
+# and once as an object file for i3.
+src/commands_parser.o: src/commands_parser.c ${HEADERS} ${CMDPARSE_HEADERS}
+       echo "[i3] CC $<"
+       $(CC) $(CPPFLAGS) $(CFLAGS) -DTEST_PARSER -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -o test.commands_parser $< $(LIBS)
+       $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $<
+
 src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS}
        echo "[i3] LEX $<"
-       flex -i -o$(@:.o=.c) $<
+       $(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 "[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 "[i3] YACC $<"
-       bison --debug --verbose -b $(basename $< .y) -d $<
+       $(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 "[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 "[i3] INSTALL"
@@ -81,6 +88,7 @@ install: all
        $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/applications
        $(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/
@@ -89,7 +97,8 @@ install: all
        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
-       $(INSTALL) -m 0644 i3.desktop $(DESTDIR)$(PREFIX)/share/xsessions/
+       $(INSTALL) -m 0644 i3.xsession.desktop $(DESTDIR)$(PREFIX)/share/xsessions/i3.desktop
+       $(INSTALL) -m 0644 i3.applications.desktop $(DESTDIR)$(PREFIX)/share/applications/i3.desktop
        $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/
        for dir in $(SUBDIRS); do \
                $(MAKE) -C $$dir install; \
@@ -99,8 +108,8 @@ 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-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}
+       cp i3-migrate-config-to-v4 generate-command-parser.pl i3-sensible-* i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3.applications.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION}
+       cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar i3-dump-log yajl-fallback include man parser-specs i3-${VERSION}
        # Only copy toplevel documentation (important stuff)
        mkdir i3-${VERSION}/docs
        # Pre-generate documentation
@@ -121,7 +130,7 @@ dist: distclean
        rm -rf i3-${VERSION}
 
 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
+       rm -f src/*.o src/*.gcno src/cmdparse.* src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.{output,dot} loglevels.tmp include/loglevels.h include/GENERATED_*
        (which lcov >/dev/null 2>&1 && lcov -d . --zerocounters) || true
        $(MAKE) -C libi3 clean
        $(MAKE) -C docs clean
index a9d1661879f5c9f55d8e396f6bb71a5624988ee1..537d4dda5d43e492a144a5a0fc19290206ab1c00 100644 (file)
--- a/common.mk
+++ b/common.mk
@@ -2,6 +2,8 @@ UNAME=$(shell uname)
 DEBUG=1
 COVERAGE=0
 INSTALL=install
+FLEX=flex
+BISON=bison
 ifndef PREFIX
   PREFIX=/usr
 endif
@@ -66,6 +68,7 @@ CPPFLAGS += -DPCRE_HAS_UCP=1
 endif
 
 LIBS += -lm
+LIBS += -lrt
 LIBS += -L $(TOPDIR)/libi3 -li3
 LIBS += $(call ldflags_for_lib, xcb-event,xcb-event)
 LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms)
index 397941c574e1e8ca426448220e961b7708685522..d144f0617de41724c01476c95648ae662981e185 100644 (file)
@@ -1,3 +1,9 @@
+i3-wm (4.1.3-0) unstable; urgency=low
+
+  * NOT YET RELEASED
+
+ -- Michael Stapelberg <michael@stapelberg.de>  Fri, 27 Jan 2012 19:34:11 +0000
+
 i3-wm (4.1.2-2) unstable; urgency=low
 
   * Rebuild against libyajl and libxcb from unstable
index e3786cfb4f19854133381b873192cdd4b91f7cd1..1119d69d3bfa9722f555de79b7212986edc461b9 100644 (file)
@@ -10,8 +10,8 @@ Homepage: http://i3wm.org/
 Package: i3
 Architecture: any
 Section: x11
-Depends: i3-wm, ${misc:Depends}
-Recommends: i3lock, suckless-tools, i3status
+Depends: i3-wm (=${binary:Version}), ${misc:Depends}
+Recommends: i3lock (>= 2.2), suckless-tools, i3status (>= 2.3)
 Description: metapackage (i3 window manager, screen locker, menu, statusbar)
  This metapackage installs the i3 window manager (i3-wm), the i3lock screen
  locker, i3status (for system information) and suckless-tools (for dmenu).
index e5896855a0c396d0e49fc3741669a71d0da233d3..9f7ed2c6cb1f440566b4971edab22ae89811d915 100644 (file)
@@ -1,5 +1,7 @@
 docs/debugging.html
+docs/debugging-release-version.html
 docs/hacking-howto.html
+docs/i3bar-protocol.html
 docs/userguide.html
 docs/bigpicture.png
 docs/single_terminal.png
@@ -17,3 +19,9 @@ docs/keyboard-layer2.png
 docs/testsuite.html
 docs/i3-sync-working.png
 docs/i3-sync.png
+docs/tree-layout1.png
+docs/tree-layout2.png
+docs/tree-shot1.png
+docs/tree-shot2.png
+docs/tree-shot3.png
+docs/tree-shot4.png
index 61ff016004f0e76825c7e6d1fda6fe188ad8fe1a..ed6cb465f542b1c1cf5b764513c76d56744f0be4 100644 (file)
@@ -1,14 +1,12 @@
-## Description: Document Debian-specific x-terminal-emulator in the manpage.
-## Origin/Author: Michael Stapelberg
-Index: i3-4.1/man/i3-sensible-terminal.man
+Index: i3-4.1.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
+--- i3-4.1.1.orig/man/i3-sensible-terminal.man 2011-12-28 23:56:55.487581000 +0100
++++ i3-4.1.1/man/i3-sensible-terminal.man      2011-12-28 23:57:06.725802633 +0100
 @@ -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
++* x-terminal-emulator
  * urxvt
  * rxvt
+ * terminator
index 28e9200df49c85caf2fc785e62ffdef694f79cb8..0d71f7c4d3fe14f8dee694d500782ae12a7188eb 100644 (file)
@@ -1,21 +1,17 @@
-## 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
+Index: i3-4.1.1/i3-sensible-terminal
+===================================================================
+--- i3-4.1.1.orig/i3-sensible-terminal 2011-12-28 23:51:52.455610236 +0100
++++ i3-4.1.1/i3-sensible-terminal      2011-12-28 23:52:00.826775027 +0100
+@@ -4,11 +4,7 @@
+ #
  # 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:
- [ -n "$TERMINAL" ] && 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 "$@"
+-for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do
++for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do
+     if which $terminal > /dev/null 2>&1; then
+         exec $terminal "$@"
+     fi
index a6bfc2c05e9ff13901f73681a5a5c9a469acda8c..38b60e1338380ffaead4a9c88e0457ba18e3ed02 100755 (executable)
@@ -4,6 +4,11 @@
 DPKG_EXPORT_BUILDFLAGS = 1
 -include /usr/share/dpkg/buildflags.mk
 
+ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
+    NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
+    MAKEFLAGS += -j$(NUMJOBS)
+endif
+
 build: build-arch build-indep
 build-arch: build-stamp
 build-indep: build-stamp
index d1e0768ae4f8a086e174df2c46d783450b0ad611..6584ac0622595e0d4b7ab0c99a4c06f26a5127b8 100644 (file)
@@ -1,14 +1,20 @@
 # 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 testsuite.html
+all: hacking-howto.html debugging.html debugging-release-version.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf testsuite.html i3bar-protocol.html
 
 hacking-howto.html: hacking-howto
        $(ASCIIDOC) -a toc -n $<
 
+i3bar-protocol.html: i3bar-protocol
+       $(ASCIIDOC) -a toc -n $<
+
 debugging.html: debugging
        $(ASCIIDOC) -n $<
 
+debugging-release-version.html: debugging-release-version
+       $(ASCIIDOC) -n $<
+
 userguide.html: userguide
        $(ASCIIDOC) -a toc -n $<
 
index 5e71ecf0c84cca936ec419787ac275eb009cdb1c..35460847db49b7987abf604988b2d07b6a857b86 100644 (file)
@@ -1,7 +1,7 @@
 Debugging i3: How To
 ====================
-Michael Stapelberg <michael+i3@stapelberg.de>
-July 2011
+Michael Stapelberg <michael@i3wm.org>
+February 2012
 
 This document describes how to debug i3 suitably for sending us useful bug
 reports, even if you have no clue of C programming.
@@ -10,100 +10,79 @@ First of all: Thank you for being interested in debugging i3. It really means
 something to us to get your bug fixed. If you have any questions about the
 debugging and/or need further help, do not hesitate to contact us!
 
-== Enabling logging
+== Verify you are using the latest (development) version
 
-i3 logs useful information to stdout. To have a clearly defined place where log
-files will be saved, you should redirect stdout and stderr in your
-+~/.xsession+. While you’re at it, putting each run of i3 in a separate log
-file with date/time in its filename is a good idea to not get confused about
-the different log files later on.
+Please verify that you are using the latest version of i3:
 
---------------------------------------------------------------------
-exec /usr/bin/i3 >~/i3log-$(date +'%F-%k-%M-%S') 2>&1
---------------------------------------------------------------------
-
-To enable verbose output and all levels of debug output (required when
-attaching logfiles to bugreports), add the parameters +-V -d all+, like this:
-
---------------------------------------------------------------------
-exec /usr/bin/i3 -V -d all >~/i3log-$(date +'%F-%k-%M-%S') 2>&1
---------------------------------------------------------------------
-
-== Enabling core dumps
+---------------
+$ i3 --version
+i3 version 4.1.2-248-g51728ba (2012-02-12, branch "next")
+---------------
 
-When i3 crashes, often you have the chance of getting a 'core dump' (an image
-of the memory of the i3 process which can be loaded into a debugger). To get a
-core dump, you have to make sure that the user limit for core dump files is set
-high enough. Many systems ship with a default value which even forbids core
-dumps completely. To disable the limit completely and thus enable core dumps,
-use the following command (in your +~/.xsession+, before starting i3):
+Your version can look like this:
 
--------------------
-ulimit -c unlimited
--------------------
+4.1.2::
+You are using a release version. Please
+upgrade to a development version first, or read
+link:debugging-release-version[Debugging i3: How To (release version)].
 
-Furthermore, to easily recognize core dumps and allow multiple of them, you
-should set a custom core dump filename pattern, using a command like the
-following:
+4.1.2-248-g51728ba::
+Your version is 248 commits newer than 4.1.2, and the git revision of your
+version is +51728ba+. Go to http://code.i3wm.org/i3/commit/?h=next and see if
+the line "commit" starts with the same revision. If so, you are using the
+latest version.
 
----------------------------------------------
-sudo sysctl -w kernel.core_pattern=core.%e.%p
----------------------------------------------
+Development versions of i3 have several properties which make debugging easier:
 
-This will generate files which have the executable’s file name (%e) and the
-process id (%p) in it. You can save this setting across reboots using
-+/etc/sysctl.conf+.
+1. Shared memory debug logging is enabled by default. You do not have to enable
+   logging explicitly.
+2. Core dumps are enabled by default.
+3. If you are using a version from the Debian/Ubuntu autobuilder, it is
+   compiled without optimization. Debug symbols are available in the i3-wm-dbg
+   package. When compiling i3 yourself, debug mode is the default.
 
-== Compiling with debug symbols
+== Obtaining the debug logfile
 
-To actually get useful core dumps, you should make sure that your version of i3
-is compiled with debug symbols, that is, that the symbols are not stripped
-during the build process. You can check whether your executable contains
-symbols by issuing the following command:
+No matter whether i3 misbehaved in some way without crashing or whether it just
+crashed, the logfile provides all information necessary to debug the problem.
 
-----------------
-file $(which i3)
-----------------
-
-You should get an output like this:
-------------------------------------------------------------------------------
-/usr/bin/i3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically
-linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
-------------------------------------------------------------------------------
-
-Notice the +not stripped+, which is the important part. If you have a version
-which is stripped, please have a look if your distribution provides debug
-symbols (package +i3-wm-dbg+ on Debian for example) or if you can turn off
-stripping. If nothing helps, please build i3 from source.
+To save a compressed version of the logfile (suitable for attaching it to a
+bugreport), use:
+--------------------------------------------------------------------
+i3-dump-log | gzip -9c > /tmp/i3.log.gz
+--------------------------------------------------------------------
 
-== Generating a backtrace
+This command does not depend on i3 (it also works when i3 currently displays
+the crash dialog), but it requires a working X11 connection. When running it
+from a virtual console (Ctrl-Alt-F1), use:
 
-Once you have made sure that your i3 is compiled with debug symbols and that
-core dumps are enabled, you can start making sense out of the core dumps.
+--------------------------------------------------------------------
+DISPLAY=:0 i3-dump-log | gzip -9c > /tmp/i3.log.gz
+--------------------------------------------------------------------
 
-Because the core dump depends on the original executable (and its debug
-symbols), please do this as soon as you encounter the problem. If you
-re-compile i3, your core dump might be useless afterwards.
+== Obtaining a backtrace
 
-Please install +gdb+, a debugger for C. No worries, you don’t need to learn it
-now.  Start gdb using the following command (replacing the actual name of the
-core dump of course):
+When i3 displays its crash dialog, do the following:
 
-----------------------------
-gdb $(which i3) core.i3.3849
-----------------------------
+1. Switch to a virtual console (Ctrl-Alt-F1) or login from a different computer
+2. Generate a backtrace (see below)
+3. Switch back to the crash dialog (Ctrl-Alt-F7)
+4. Restart i3 in-place (you will keep your session), continue working
 
-Then, generate a backtrace using:
+This is how you get a backtrace from a running i3 process:
 
---------------
-backtrace full
---------------
+-----------------
+I3PID=$(pidof i3)
+gdb /proc/$I3PID/exe $I3PID \
+    --batch --quiet \
+    --ex 'backtrace full' > /tmp/i3-backtrace.txt 2>&1
+-----------------
 
 == Sending bug reports/debugging on IRC
 
-When sending bug reports, please paste the relevant part of the log (if in
-doubt, please send us rather too much information than too less) and the whole
-backtrace (if there was a core dump).
+When sending bug reports, please attach the *whole* log file. Even if you think
+you found the section which clearly highlights the problem, additional
+information might be necessary to completely diagnose the problem.
 
 When debugging with us in IRC, be prepared to use a so called nopaste service
 such as http://nopaste.info or http://pastebin.com because pasting large
diff --git a/docs/debugging-release-version b/docs/debugging-release-version
new file mode 100644 (file)
index 0000000..71aa773
--- /dev/null
@@ -0,0 +1,126 @@
+Debugging i3: How To (release version)
+======================================
+Michael Stapelberg <michael@i3wm.org>
+February 2012
+
+This document describes how to debug i3 suitably for sending us useful bug
+reports, even if you have no clue of C programming.
+
+First of all: Thank you for being interested in debugging i3. It really means
+something to us to get your bug fixed. If you have any questions about the
+debugging and/or need further help, do not hesitate to contact us!
+
+NOTE: This document is for the release version of i3. If you are using a
+development version, please see link:debugging.html[Debugging i3: How To]
+instead.
+
+== Consider using the development version
+
+This document is for the release version of i3. In many cases, bugs are already
+fixed in the development version of i3. If they aren’t, we will still ask you
+to reproduce your error with the most recent development version of i3.
+Therefore, please upgrade to a development version and continue reading at
+link:debugging.html[Debugging i3: How To].
+
+If you absolutely cannot upgrade to a development version of i3, you may
+continue reading this document.
+
+== Enabling logging
+
+i3 logs useful information to stdout. To have a clearly defined place where log
+files will be saved, you should redirect stdout and stderr in your
++~/.xsession+. While you’re at it, putting each run of i3 in a separate log
+file with date/time in its filename is a good idea to not get confused about
+the different log files later on.
+
+--------------------------------------------------------------------
+exec /usr/bin/i3 >~/i3log-$(date +'%F-%k-%M-%S') 2>&1
+--------------------------------------------------------------------
+
+To enable verbose output and all levels of debug output (required when
+attaching logfiles to bugreports), add the parameters +-V -d all+, like this:
+
+--------------------------------------------------------------------
+exec /usr/bin/i3 -V -d all >~/i3log-$(date +'%F-%k-%M-%S') 2>&1
+--------------------------------------------------------------------
+
+== Enabling core dumps
+
+When i3 crashes, often you have the chance of getting a 'core dump' (an image
+of the memory of the i3 process which can be loaded into a debugger). To get a
+core dump, you have to make sure that the user limit for core dump files is set
+high enough. Many systems ship with a default value which even forbids core
+dumps completely. To disable the limit completely and thus enable core dumps,
+use the following command (in your +~/.xsession+, before starting i3):
+
+-------------------
+ulimit -c unlimited
+-------------------
+
+Furthermore, to easily recognize core dumps and allow multiple of them, you
+should set a custom core dump filename pattern, using a command like the
+following:
+
+---------------------------------------------
+sudo sysctl -w kernel.core_pattern=core.%e.%p
+---------------------------------------------
+
+This will generate files which have the executable’s file name (%e) and the
+process id (%p) in it. You can save this setting across reboots using
++/etc/sysctl.conf+.
+
+== Compiling with debug symbols
+
+To actually get useful core dumps, you should make sure that your version of i3
+is compiled with debug symbols, that is, that the symbols are not stripped
+during the build process. You can check whether your executable contains
+symbols by issuing the following command:
+
+----------------
+file $(which i3)
+----------------
+
+You should get an output like this:
+------------------------------------------------------------------------------
+/usr/bin/i3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically
+linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
+------------------------------------------------------------------------------
+
+Notice the +not stripped+, which is the important part. If you have a version
+which is stripped, please have a look if your distribution provides debug
+symbols (package +i3-wm-dbg+ on Debian for example) or if you can turn off
+stripping. If nothing helps, please build i3 from source.
+
+== Generating a backtrace
+
+Once you have made sure that your i3 is compiled with debug symbols and that
+core dumps are enabled, you can start making sense out of the core dumps.
+
+Because the core dump depends on the original executable (and its debug
+symbols), please do this as soon as you encounter the problem. If you
+re-compile i3, your core dump might be useless afterwards.
+
+Please install +gdb+, a debugger for C. No worries, you don’t need to learn it
+now.  Start gdb using the following command (replacing the actual name of the
+core dump of course):
+
+----------------------------
+gdb $(which i3) core.i3.3849
+----------------------------
+
+Then, generate a backtrace using:
+
+--------------
+backtrace full
+--------------
+
+== Sending bug reports/debugging on IRC
+
+When sending bug reports, please paste the relevant part of the log (if in
+doubt, please send us rather too much information than too less) and the whole
+backtrace (if there was a core dump).
+
+When debugging with us in IRC, be prepared to use a so called nopaste service
+such as http://nopaste.info or http://pastebin.com because pasting large
+amounts of text in IRC sometimes leads to incomplete lines (servers have line
+length limitations) or flood kicks.
index 9a7ec9d4547fd1ce421bd81e14b1bcbc5c3ff74d..73ae96335991d5312e1b61b5a0c265be8a16926f 100644 (file)
@@ -63,57 +63,22 @@ to a specific file type, a window manager should not limit itself to a certain
 layout (like dwm, awesome, …) but provide mechanisms for you to easily create
 the layout you need at the moment.
 
-=== The layout table
+=== The layout tree
 
-*********************************************************************************
-This section has not been updated for v4.0 yet, sorry! We wanted to release on
-time, but we will update this soon. Please talk to us on IRC if you need to
-know stuff *NOW* :).
-*********************************************************************************
-
-/////////////////////////////////////////////////////////////////////////////////
-To accomplish flexible layouts, we decided to simply use a table. The table
-grows and shrinks as you need it. Each cell holds a container which then holds
-windows (see picture below). You can use different layouts for each container
-(default layout and stacking layout).
+The data structure which i3 uses to keep track of your windows is a tree. Every
+node in the tree is a container (type +Con+). Some containers represent actual
+windows (every container with a +window != NULL+), some represent split
+containers and a few have special purposes: they represent workspaces, outputs
+(like VGA1, LVDS1, …) or the X11 root window.
 
 So, when you open a terminal and immediately open another one, they reside in
-the same container, in default layout. The layout table has exactly one column,
-one row and therefore one cell. When you move one of the terminals to the
-right, the table needs to grow. It will be expanded to two columns and one row.
-This enables you to have different layouts for each container. The table then
-looks like this:
+the same split container, which uses the default layout. In case of an empty
+workspace, the split container we are talking about is the workspace.
 
-[width="15%",cols="^,^"]
-|========
-| T1 | T2
-|========
-
-When moving terminal 2 to the bottom, the table will be expanded again.
-
-[width="15%",cols="^,^"]
-|========
-| T1 |
-|    | T2
-|========
-
-You can really think of the layout table like a traditional HTML table, if
-you’ve ever designed one. Especially col- and rowspan work similarly. Below,
-you see an example of colspan=2 for the first container (which has T1 as
-window).
-
-[width="15%",cols="^asciidoc"]
-|========
-| T1
-|
-[cols="^,^",frame="none"]
-!========
-! T2 ! T3
-!========
-|========
-
-Furthermore, you can freely resize table cells.
-/////////////////////////////////////////////////////////////////////////////////
+To get an impression of how different layouts are represented, just play around
+and look at the data structures -- they are exposed as a JSON hash. See
+http://i3wm.org/docs/ipc.html#_get_tree_reply for documentation on that and an
+example.
 
 == Files
 
@@ -160,7 +125,7 @@ src/debug.c::
 Contains debugging functions to print unhandled X events.
 
 src/ewmh.c::
-iFunctions to get/set certain EWMH properties easily.
+Functions to get/set certain EWMH properties easily.
 
 src/floating.c::
 Contains functions for floating mode (mostly resizing/dragging).
@@ -243,17 +208,13 @@ Legacy support for Xinerama. See +src/randr.c+ for the preferred API.
 
 == Data structures
 
-*********************************************************************************
-This section has not been updated for v4.0 yet, sorry! We wanted to release on
-time, but we will update this soon. Please talk to us on IRC if you need to
-know stuff *NOW* :).
-*********************************************************************************
-
-/////////////////////////////////////////////////////////////////////////////////
 
 See include/data.h for documented data structures. The most important ones are
 explained right here.
 
+/////////////////////////////////////////////////////////////////////////////////
+// TODO: update image
+
 image:bigpicture.png[The Big Picture]
 
 /////////////////////////////////////////////////////////////////////////////////
@@ -261,7 +222,7 @@ image:bigpicture.png[The Big Picture]
 So, the hierarchy is:
 
 . *X11 root window*, the root container
-. *Virtual screens* (Screen 0 in this example)
+. *Output container* (LVDS1 in this example)
 . *Content container* (there are also containers for dock windows)
 . *Workspaces* (Workspace 1 in this example, with horizontal orientation)
 . *Split container* (vertically split)
@@ -269,68 +230,57 @@ So, the hierarchy is:
 
 The data type is +Con+, in all cases.
 
-=== Virtual screens
+=== X11 root window
+
+The X11 root window is a single window per X11 display (a display is identified
+by +:0+ or +:1+ etc.). The root window is what you draw your background image
+on. It spans all the available outputs, e.g. +VGA1+ is a specific part of the
+root window and +LVDS1+ is a specific part of the root window.
 
-A virtual screen (type `i3Screen`) is generated from the connected outputs
-obtained through RandR. The difference to the raw RandR outputs as seen
-when using +xrandr(1)+ is that it falls back to the lowest common resolution of
-the actual enabled outputs.
+=== Output container
+
+Every active output obtained through RandR is represented by one output
+container. Outputs are considered active when a mode is configured (meaning
+something is actually displayed on the output) and the output is not a clone.
 
 For example, if your notebook has a screen resolution of 1280x800 px and you
 connect a video projector with a resolution of 1024x768 px, set it up in clone
-mode (+xrandr \--output VGA1 \--mode 1024x768 \--same-as LVDS1+), i3 will have
-one virtual screen.
+mode (+xrandr \--output VGA1 \--mode 1024x768 \--same-as LVDS1+), i3 will
+reduce the resolution to the lowest common resolution and disable one of the
+cloned outputs afterwards.
 
 However, if you configure it using +xrandr \--output VGA1 \--mode 1024x768
-\--right-of LVDS1+, i3 will generate two virtual screens. For each virtual
-screen, a new workspace will be assigned. New workspaces are created on the
-screen you are currently on.
+\--right-of LVDS1+, i3 will set both outputs active. For each output, a new
+workspace will be assigned. New workspaces are created on the output you are
+currently on.
+
+=== Content container
+
+Each output has multiple children. Two of them are dock containers which hold
+dock clients. The other one is the content container, which holds the actual
+content (workspaces) of this output.
 
 === Workspace
 
 A workspace is identified by its name. Basically, you could think of
 workspaces as different desks in your office, if you like the desktop
-methaphor. They just contain different sets of windows and are completely
+metaphor. They just contain different sets of windows and are completely
 separate of each other. Other window managers also call this ``Virtual
 desktops''.
 
-=== The layout table
-
-*********************************************************************************
-This section has not been updated for v4.0 yet, sorry! We wanted to release on
-time, but we will update this soon. Please talk to us on IRC if you need to
-know stuff *NOW* :).
-*********************************************************************************
+=== Split container
 
-/////////////////////////////////////////////////////////////////////////////////
-
-Each workspace has a table, which is just a two-dimensional dynamic array
-containing Containers (see below). This table grows and shrinks as you need it
-(by moving windows to the right you can create a new column in the table, by
-moving them to the bottom you create a new row).
-
-/////////////////////////////////////////////////////////////////////////////////
+A split container is a container which holds an arbitrary amount of split
+containers or X11 window containers. It has an orientation (horizontal or
+vertical) and a layout.
 
-=== Container
+Split containers (and X11 window containers, which are a subtype of split
+containers) can have different border styles.
 
-*********************************************************************************
-This section has not been updated for v4.0 yet, sorry! We wanted to release on
-time, but we will update this soon. Please talk to us on IRC if you need to
-know stuff *NOW* :).
-*********************************************************************************
+=== X11 window container
 
-/////////////////////////////////////////////////////////////////////////////////
-
-A container is the content of a table’s cell. It holds an arbitrary amount of
-windows and has a specific layout (default layout, stack layout or tabbed
-layout). Containers can consume multiple table cells by modifying their
-colspan/rowspan attribute.
-
-/////////////////////////////////////////////////////////////////////////////////
-
-=== Client
-
-A client is x11-speak for a window.
+An X11 window container holds exactly one X11 window. These are the leaf nodes
+of the layout tree, they cannot have any children.
 
 == List/queue macros
 
@@ -421,8 +371,9 @@ After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see
 whether this window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for
 example. Docks are handled differently, they don’t have decorations and are not
 assigned to a specific container. Instead, they are positioned at the bottom
-of the screen. To get the height which needs to be reserved for the window,
-the `_NET_WM_STRUT_PARTIAL` property is used.
+or top of the screen (in the appropriate dock area containers). To get the
+height which needs to be reserved for the window, the `_NET_WM_STRUT_PARTIAL`
+property is used.
 
 Furthermore, the list of assignments (to other workspaces, which may be on
 other screens) is checked. If the window matches one of the user’s criteria,
@@ -484,64 +435,218 @@ src/layout.c, function resize_client().
 
 == Rendering (src/layout.c, render_layout() and render_container())
 
-*********************************************************************************
-This section has not been updated for v4.0 yet, sorry! We wanted to release on
-time, but we will update this soon. Please talk to us on IRC if you need to
-know stuff *NOW* :).
-*********************************************************************************
-
-/////////////////////////////////////////////////////////////////////////////////
-
-
-There are several entry points to rendering: `render_layout()`,
-`render_workspace()` and `render_container()`. The former one calls
-`render_workspace()` for every screen, which in turn will call
-`render_container()` for every container inside its layout table. Therefore, if
-you need to render only a single container, for example because a window was
-removed, added or changed its title, you should directly call
-render_container().
-
-Rendering consists of two steps: In the first one, in `render_workspace()`, each
-container gets its position (screen offset + offset in the table) and size
-(container's width times colspan/rowspan). Then, `render_container()` is called,
-which takes different approaches, depending on the mode the container is in:
-
-=== Common parts
-
-On the frame (the window which was created around the client’s window for the
-decorations), a black rectangle is drawn as a background for windows like
-MPlayer, which do not completely fit into the frame.
-
-=== Default mode
-
-Each clients gets the container’s width and an equal amount of height.
-
-=== Stack mode
-
-In stack mode, a window containing the decorations of all windows inside the
-container is placed at the top. The currently focused window is then given the
-whole remaining space.
-
-=== Tabbed mode
-
-Tabbed mode is like stack mode, except that the window decorations are drawn
-in one single line at the top of the container.
-
-=== Window decorations
+Rendering in i3 version 4 is the step which assigns the correct sizes for
+borders, decoration windows, child windows and the stacking order of all
+windows. In a separate step (+x_push_changes()+), these changes are pushed to
+X11.
+
+Keep in mind that all these properties (+rect+, +window_rect+ and +deco_rect+)
+are temporary, meaning they will be overwritten by calling +render_con+.
+Persistent position/size information is kept in +geometry+.
+
+The entry point for every rendering operation (except for the case of moving
+floating windows around) currently is +tree_render()+ which will re-render
+everything that’s necessary (for every output, only the currently displayed
+workspace is rendered). This behavior is expected to change in the future,
+since for a lot of updates, re-rendering everything is not actually necessary.
+Focus was on getting it working correct, not getting it work very fast.
+
+What +tree_render()+ actually does is calling +render_con()+ on the root
+container and then pushing the changes to X11. The following sections talk
+about the different rendering steps, in the order of "top of the tree" (root
+container) to the bottom.
+
+=== Rendering the root container
+
+The i3 root container (`con->type == CT_ROOT`) represents the X11 root window.
+It contains one child container for every output (like LVDS1, VGA1, …), which
+is available on your computer.
+
+Rendering the root will first render all tiling windows and then all floating
+windows. This is necessary because a floating window can be positioned in such
+a way that it is visible on two different outputs. Therefore, by first
+rendering all the tiling windows (of all outputs), we make sure that floating
+windows can never be obscured by tiling windows.
+
+Essentially, though, this code path will just call +render_con()+ for every
+output and +x_raise_con(); render_con()+ for every floating window.
+
+In the special case of having a "global fullscreen" window (fullscreen mode
+spanning all outputs), a shortcut is taken and +x_raise_con(); render_con()+ is
+only called for the global fullscreen window.
+
+=== Rendering an output
+
+Output containers (`con->layout == L_OUTPUT`) represent a hardware output like
+LVDS1, VGA1, etc. An output container has three children (at the moment): One
+content container (having workspaces as children) and the top/bottom dock area
+containers.
+
+The rendering happens in the function +render_l_output()+ in the following
+steps:
+
+1. Find the content container (`con->type == CT_CON`)
+2. Get the currently visible workspace (+con_get_fullscreen_con(content,
+   CF_OUTPUT)+).
+3. If there is a fullscreened window on that workspace, directly render it and
+   return, thus ignoring the dock areas.
+4. Sum up the space used by all the dock windows (they have a variable height
+   only).
+5. Set the workspace rects (x/y/width/height) based on the position of the
+   output (stored in `con->rect`) and the usable space
+   (`con->rect.{width,height}` without the space used for dock windows).
+6. Recursively raise and render the output’s child containers (meaning dock
+   area containers and the content container).
+
+=== Rendering a workspace or split container
+
+From here on, there really is no difference anymore. All containers are of
+`con->type == CT_CON` (whether workspace or split container) and some of them
+have a `con->window`, meaning they represent an actual window instead of a
+split container.
+
+==== Default layout
+
+In default layout, containers are placed horizontally or vertically next to
+each other (depending on the `con->orientation`). If a child is a leaf node (as
+opposed to a split container) and has border style "normal", appropriate space
+will be reserved for its window decoration.
+
+==== Stacked layout
+
+In stacked layout, only the focused window is actually shown (this is achieved
+by calling +x_raise_con()+ in reverse focus order at the end of +render_con()+).
+
+The available space for the focused window is the size of the container minus
+the height of the window decoration for all windows inside this stacked
+container.
 
-The window decorations consist of a rectangle in the appropriate color (depends
-on whether this window is the currently focused one, the last focused one in a
-not focused container or not focused at all) forming the background.
-Afterwards, two lighter lines are drawn and the last step is drawing the
-window’s title (see WM_NAME) onto it.
+If border style is "1pixel" or "none", no window decoration height will be
+reserved (or displayed later on), unless there is more than one window inside
+the stacked container.
+
+==== Tabbed layout
+
+Tabbed layout works precisely like stacked layout, but the window decoration
+position/size is different: They are placed next to each other on a single line
+(fixed height).
+
+==== Dock area layout
+
+This is a special case. Users cannot chose the dock area layout, but it will be
+set for the dock area containers. In the dockarea layout (at the moment!),
+windows will be placed above each other.
+
+=== Rendering a window
+
+A window’s size and position will be determined in the following way:
+
+1. Subtract the border if border style is not "none" (but "normal" or "1pixel").
+2. Subtract the X11 border, if the window has an X11 border > 0.
+3. Obey the aspect ratio of the window (think MPlayer).
+4. Obey the height- and width-increments of the window (think terminal emulator
+   which can only be resized in one-line or one-character steps).
+
+== Pushing updates to X11 / Drawing
+
+A big problem with i3 before version 4 was that we just sent requests to X11
+anywhere in the source code. This was bad because nobody could understand the
+entirety of our interaction with X11, it lead to subtle bugs and a lot of edge
+cases which we had to consider all over again.
+
+Therefore, since version 4, we have a single file, +src/x.c+, which is
+responsible for repeatedly transferring parts of our tree datastructure to X11.
+
++src/x.c+ consists of multiple parts:
+
+1. The state pushing: +x_push_changes()+, which calls +x_push_node()+.
+2. State modification functions: +x_con_init+, +x_reinit+,
+   +x_reparent_child+, +x_move_win+, +x_con_kill+, +x_raise_con+, +x_set_name+
+   and +x_set_warp_to+.
+3. Expose event handling (drawing decorations): +x_deco_recurse()+ and
+   +x_draw_decoration()+.
+
+=== Pushing state to X11
+
+In general, the function +x_push_changes+ should be called to push state
+changes. Only when the scope of the state change is clearly defined (for
+example only the title of a window) and its impact is known beforehand, one can
+optimize this and call +x_push_node+ on the appropriate con directly.
+
++x_push_changes+ works in the following steps:
+
+1. Clear the eventmask for all mapped windows. This leads to not getting
+   useless ConfigureNotify or EnterNotify events which are caused by our
+   requests. In general, we only want to handle user input.
+2. Stack windows above each other, in reverse stack order (starting with the
+   most obscured/bottom window). This is relevant for floating windows which
+   can overlap each other, but also for tiling windows in stacked or tabbed
+   containers. We also update the +_NET_CLIENT_LIST_STACKING+ hint which is
+   necessary for tab drag and drop in Chromium.
+3. +x_push_node+ will be called for the root container, recursively calling
+   itself for the container’s children. This function actually pushes the
+   state, see the next paragraph.
+4. If the pointer needs to be warped to a different position (for example when
+   changing focus to a differnt output), it will be warped now.
+5. The eventmask is restored for all mapped windows.
+6. Window decorations will be rendered by calling +x_deco_recurse+ on the root
+   container, which then recursively calls itself for the children.
+7. If the input focus needs to be changed (because the user focused a different
+   window), it will be updated now.
+8. +x_push_node_unmaps+ will be called for the root container. This function
+   only pushes UnmapWindow requests. Separating the state pushing is necessary
+   to handle fullscreen windows (and workspace switches) in a smooth fashion:
+   The newly visible windows should be visible before the old windows are
+   unmapped.
+
++x_push_node+ works in the following steps:
+
+1. Update the window’s +WM_NAME+, if changed (the +WM_NAME+ is set on i3
+   containers mainly for debugging purposes).
+2. Reparents a child window into the i3 container if the container was created
+   for a specific managed window.
+3. If the size/position of the i3 container changed (due to opening a new
+   window or switching layouts for example), the window will be reconfigured.
+   Also, the pixmap which is used to draw the window decoration/border on is
+   reconfigured (pixmaps are size-dependent).
+4. Size/position for the child window is adjusted.
+5. The i3 container is mapped if it should be visible and was not yet mapped.
+   When mapping, +WM_STATE+ is set to +WM_STATE_NORMAL+. Also, the eventmask of
+   the child window is updated and the i3 container’s contents are copied from
+   the pixmap.
+6. +x_push_node+ is called recursively for all children of the current
+   container.
+
++x_push_node_unmaps+ handles the remaining case of an i3 container being
+unmapped if it should not be visible anymore. +WM_STATE+ will be set to
++WM_STATE_WITHDRAWN+.
+
+
+=== Drawing window decorations/borders/backgrounds
+
++x_draw_decoration+ draws window decorations. It is run for every leaf
+container (representing an actual X11 window) and for every non-leaf container
+which is in a stacked/tabbed container (because stacked/tabbed containers
+display a window decoration for split containers, which at the moment just says
+"another container").
+
+Then, parameters are collected to be able to determine whether this decoration
+drawing is actually necessary or was already done. This saves a substantial
+number of redraws (depending on your workload, but far over 50%).
+
+Assuming that we need to draw this decoration, we start by filling the empty
+space around the child window (think of MPlayer with a specific aspect ratio)
+in the user-configured client background color.
+
+Afterwards, we draw the appropriate border (in case of border styles "normal"
+and "1pixel") and the top bar (in case of border style "normal").
+
+The last step is drawing the window title on the top bar.
 
-=== Fullscreen windows
 
-For fullscreen windows, the `rect` (x, y, width, height) is not changed to
-allow the client to easily go back to its previous position. Instead,
-fullscreen windows are skipped when rendering.
+/////////////////////////////////////////////////////////////////////////////////
 
-=== Resizing containers
+== Resizing containers
 
 By clicking and dragging the border of a container, you can resize the whole
 column (respectively row) which this container is in. This is necessary to keep
@@ -567,41 +672,123 @@ floating windows:
 
 /////////////////////////////////////////////////////////////////////////////////
 
-== User commands / commandmode (src/cmdparse.{l,y})
-
-*********************************************************************************
-This section has not been updated for v4.0 yet, sorry! We wanted to release on
-time, but we will update this soon. Please talk to us on IRC if you need to
-know stuff *NOW* :).
-*********************************************************************************
-
-/////////////////////////////////////////////////////////////////////////////////
-
-
-Like in vim, you can control i3 using commands. They are intended to be a
-powerful alternative to lots of shortcuts, because they can be combined. There
-are a few special commands, which are the following:
-
-exec <command>::
-Starts the given command by passing it to `/bin/sh`.
-
-restart::
-Restarts i3 by executing `argv[0]` (the path with which you started i3) without
-forking.
-
-w::
-"With". This is used to select a bunch of windows. Currently, only selecting
-the whole container in which the window is in, is supported by specifying "w".
-
-f, s, d::
-Toggle fullscreen, stacking, default mode for the current window/container.
-
-The other commands are to be combined with a direction. The directions are h,
-j, k and l, like in vim (h = left, j = down, k = up, l = right). When you just
-specify the direction keys, i3 will move the focus in that direction. You can
-provide "m" or "s" before the direction to move a window respectively or snap.
-
-/////////////////////////////////////////////////////////////////////////////////
+== User commands (parser-specs/commands.spec)
+
+In the configuration file and when using i3 interactively (with +i3-msg+, for
+example), you use commands to make i3 do things, like focus a different window,
+set a window to fullscreen, and so on. An example command is +floating enable+,
+which enables floating mode for the currently focused window. See the
+appropriate section in the link:userguide.html[User’s Guide] for a reference of
+all commands.
+
+In earlier versions of i3, interpreting these commands was done using lex and
+yacc, but experience has shown that lex and yacc are not well suited for our
+command language. Therefore, starting from version 4.2, we use a custom parser
+for user commands (not yet for the configuration file).
+The input specification for this parser can be found in the file
++parser-specs/commands.spec+. Should you happen to use Vim as an editor, use
+:source parser-specs/highlighting.vim to get syntax highlighting for this file
+(highlighting files for other editors are welcome).
+
+.Excerpt from commands.spec
+-----------------------------------------------------------------------
+state INITIAL:
+  '[' -> call cmd_criteria_init(); CRITERIA
+  'move' -> MOVE
+  'exec' -> EXEC
+  'workspace' -> WORKSPACE
+  'exit' -> call cmd_exit()
+  'restart' -> call cmd_restart()
+  'reload' -> call cmd_reload()
+-----------------------------------------------------------------------
+
+The input specification is written in an extremely simple format. The
+specification is then converted into C code by the Perl script
+generate-commands-parser.pl (the output file names begin with GENERATED and the
+files are stored in the +include+ directory). The parser implementation
++src/commands_parser.c+ includes the generated C code at compile-time.
+
+The above excerpt from commands.spec illustrates nearly all features of our
+specification format: You describe different states and what can happen within
+each state. State names are all-caps; the state in the above excerpt is called
+INITIAL. A list of tokens and their actions (separated by an ASCII arrow)
+follows. In the excerpt, all tokens are literals, that is, simple text strings
+which will be compared with the input. An action is either the name of a state
+in which the parser will transition into, or the keyword 'call', followed by
+the name of a function (and optionally a state).
+
+=== Example: The WORKSPACE state
+
+Let’s have a look at the WORKSPACE state, which is a good example of all
+features. This is its definition:
+
+.WORKSPACE state (commands.spec)
+----------------------------------------------------------------
+# workspace next|prev|next_on_output|prev_on_output
+# workspace back_and_forth
+# workspace <name>
+state WORKSPACE:
+  direction = 'next_on_output', 'prev_on_output', 'next', 'prev'
+      -> call cmd_workspace($direction)
+  'back_and_forth'
+      -> call cmd_workspace_back_and_forth()
+  workspace = string
+      -> call cmd_workspace_name($workspace)
+----------------------------------------------------------------
+
+As you can see from the commands, there are multiple different valid variants
+of the workspace command:
+
+workspace <direction>::
+       The word 'workspace' can be followed by any of the tokens 'next',
+       'prev', 'next_on_output' or 'prev_on_output'. This command will
+       switch to the next or previous workspace (optionally on the same
+       output). +
+       There is one function called +cmd_workspace+, which is defined
+       in +src/commands.c+. It will handle this kind of command. To know which
+       direction was specified, the direction token is stored on the stack
+       with the name "direction", which is what the "direction = " means in
+       the beginning. +
+
+NOTE: Note that you can specify multiple literals in the same line. This has
+       exactly the same effect as if you specified `direction =
+       'next_on_output' -> call cmd_workspace($direction)` and so forth. +
+
+NOTE: Also note that the order of literals is important here: If 'next' were
+       ordered before 'next_on_output', then 'next_on_output' would never
+       match.
+
+workspace back_and_forth::
+       This is a very simple case: When the literal 'back_and_forth' is found
+       in the input, the function +cmd_workspace_back_and_forth+ will be
+       called without parameters and the parser will return to the INITIAL
+       state (since no other state was specified).
+workspace <name>::
+       In this case, the workspace command is followed by an arbitrary string,
+       possibly in quotes, for example "workspace 3" or "workspace bleh". +
+       This is the first time that the token is actually not a literal (not in
+       single quotes), but just called string. Other possible tokens are word
+       (the same as string, but stops matching at a whitespace) and end
+       (matches the end of the input).
+
+=== Introducing a new command
+
+The following steps have to be taken in order to properly introduce a new
+command (or possibly extend an existing command):
+
+1. Define a function beginning with +cmd_+ in the file +src/commands.c+. Copy
+   the prototype of an existing function.
+2. After adding a comment on what the function does, copy the comment and
+   function definition to +include/commands.h+. Make the comment in the header
+   file use double asterisks to make doxygen pick it up.
+3. Write a test case (or extend an existing test case) for your feature, see
+   link:testsuite.html[i3 testsuite]. For now, it is sufficient to simply call
+   your command in all the various possible ways.
+4. Extend the parser specification in +parser-specs/commands.spec+. Run the
+   testsuite and see if your new function gets called with the appropriate
+   arguments for the appropriate input.
+5. Actually implement the feature.
+6. Document the feature in the link:userguide.html[User’s Guide].
 
 == Moving containers
 
@@ -749,6 +936,11 @@ Without much ado, here is the list of cases which need to be considered:
   leads to code which looks like it works fine but which does not work under
   certain conditions.
 
+* Forgetting to call `floating_fix_coordinates(con, old_rect, new_rect)` after
+  moving workspaces across outputs. Coordinates for floating containers are
+  not relative to workspace boundaries, so you must correct their coordinates
+  or those containers will show up in the wrong workspace or not at all.
+
 == Using git / sending patches
 
 For a short introduction into using git, see
@@ -780,3 +972,73 @@ git format-patch origin
 -----------------------
 
 Just send us the generated file via email.
+
+== Thought experiments
+
+In this section, we collect thought experiments, so that we don’t forget our
+thoughts about specific topics. They are not necessary to get into hacking i3,
+but if you are interested in one of the topics they cover, you should read them
+before asking us why things are the way they are or why we don’t implement
+things.
+
+=== Using cgroups per workspace
+
+cgroups (control groups) are a linux-only feature which provides the ability to
+group multiple processes. For each group, you can individually set resource
+limits, like allowed memory usage. Furthermore, and more importantly for our
+purposes, they serve as a namespace, a label which you can attach to processes
+and their children.
+
+One interesting use for cgroups is having one cgroup per workspace (or
+container, doesn’t really matter). That way, you could set different priorities
+and have a workspace for important stuff (say, writing a LaTeX document or
+programming) and a workspace for unimportant background stuff (say,
+JDownloader). Both tasks can obviously consume a lot of I/O resources, but in
+this example it doesn’t really matter if JDownloader unpacks the download a
+minute earlier or not. However, your compiler should work as fast as possible.
+Having one cgroup per workspace, you would assign more resources to the
+programming workspace.
+
+Another interesting feature is that an inherent problem of the workspace
+concept could be solved by using cgroups: When starting an application on
+workspace 1, then switching to workspace 2, you will get the application’s
+window(s) on workspace 2 instead of the one you started it on. This is because
+the window manager does not have any mapping between the process it starts (or
+gets started in any way) and the window(s) which appear.
+
+Imagine for example using dmenu: The user starts dmenu by pressing Mod+d, dmenu
+gets started with PID 3390. The user then decides to launch Firefox, which
+takes a long time. So he enters firefox into dmenu and presses enter. Firefox
+gets started with PID 4001. When it finally finishes loading, it creates an X11
+window and uses MapWindow to make it visible. This is the first time i3
+actually gets in touch with Firefox. It decides to map the window, but it has
+no way of knowing that this window (even though it has the _NET_WM_PID property
+set to 4001) belongs to the dmenu the user started before.
+
+How do cgroups help with this? Well, when pressing Mod+d to launch dmenu, i3
+would create a new cgroup, let’s call it i3-3390-1. It launches dmenu in that
+cgroup, which gets PID 3390. As before, the user enters firefox and Firefox
+gets launched with PID 4001. This time, though, the Firefox process with PID
+4001 is *also* member of the cgroup i3-3390-1 (because fork()ing in a cgroup
+retains the cgroup property). Therefore, when mapping the window, i3 can look
+up in which cgroup the process is and can establish a mapping between the
+workspace and the window.
+
+There are multiple problems with this approach:
+
+. Every application has to properly set +_NET_WM_PID+. This is acceptable and
+  patches can be written for the few applications which don’t set the hint yet.
+. It does only work on Linux, since cgroups are a Linux-only feature. Again,
+  this is acceptable.
+. The main problem is that some applications create X11 windows completely
+  independent of UNIX processes. An example for this is Chromium (or
+  gnome-terminal), which, when being started a second time, communicates with
+  the first process and lets the first process open a new window. Therefore, if
+  you have a Chromium window on workspace 2 and you are currently working on
+  workspace 3, starting +chromium+ does not lead to the desired result (the
+  window will open on workspace 2).
+
+Therefore, my conclusion is that the only proper way of fixing the "window gets
+opened on the wrong workspace" problem is in the application itself. Most
+modern applications support freedesktop startup-notifications  which can be
+used for this.
diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol
new file mode 100644 (file)
index 0000000..f66c7a9
--- /dev/null
@@ -0,0 +1,148 @@
+i3bar input protocol
+====================
+Michael Stapelberg <michael@i3wm.org>
+February 2012
+
+This document explains the protocol in which i3bar expects its input. It
+provides support for colors, urgency, shortening and easy manipulation.
+
+== Rationale for chosing JSON
+
+Before describing the protocol, let’s cover why JSON is a building block of
+this protocol.
+
+1. Other bar display programs such as dzen2 or xmobar are using in-band
+   signaling: they recognize certain sequences (like ^fg(#330000) in your input
+   text). We would like to avoid that and separate information from
+   meta-information. By information, we mean the actual output, like the IP
+   address of your ethernet adapter and by meta-information, we mean in which
+   color it should be displayed right now.
+2. It is easy to write a simple script which manipulates part(s) of the input.
+   Each block of information (like a block for the disk space indicator, a block
+   for the current IP address, etc.) can be identified specifically and modified
+   in whichever way you like.
+3. It remains easy to write a simple script which just suffixes (or prefixes) a
+   status line input, because tools like i3status will output their JSON in
+   such a way that each line array will be terminated by a newline. Therefore,
+   you are not required to use a streaming JSON parser, but you can use any
+   JSON parser and write your script in any programming language. In fact, you
+   can decide to not bother with the JSON parsing at all and just inject your
+   output at a specific position (beginning or end).
+4. Relying on JSON does not introduce any new dependencies. In fact, the IPC
+   interface of i3 also uses JSON, therefore i3bar already depends on JSON.
+
+The only point against using JSON is computational complexity. If that really
+bothers you, just use the plain text input format (which i3bar will continue to
+support).
+
+== The protocol
+
+The first message of the protocol is a header block, which contains (at least)
+the version of the protocol to be used. In case there are significant changes
+(not only additions), the version will be incremented. i3bar will still
+understand the old protocol version, but in order to use the new one, you need
+to provide the correct version. The header block is terminated by a newline and
+consists of a single JSON hash:
+
+*Example*:
+----------------
+{ "version": 1 }
+----------------
+
+What follows is an infinite array (so it should be parsed by a streaming JSON
+parser, but as described above you can go for a simpler solution), whose
+elements are one array per status line. A status line is one unit of
+information which should be displayed at a time. i3bar will not display any
+input until the status line is complete. In each status line, every block will
+be represented by a JSON hash:
+
+*Example*:
+------
+[
+
+ [
+  {
+   "full_text": "E: 10.0.0.1 (1000 Mbit/s)",
+   "color": "#00ff00"
+  },
+  {
+   "full_text": "2012-01-05 20:00:01"
+  }
+ ],
+
+ [
+  {
+   "full_text": "E: 10.0.0.1 (1000 Mbit/s)",
+   "color": "#00ff00"
+  },
+  {
+   "full_text": "2012-01-05 20:00:02"
+  }
+ ],
+ …
+------
+
+Please note that this example was pretty printed for human consumption.
+i3status and others will output single statuslines in one line, separated by
+\n.
+
+=== Blocks in detail
+
+full_text::
+       The most simple block you can think of is one which just includes the
+       only required key, the +full_text+ key. i3bar will display the string
+       value and that’s it.
+short_text::
+       Where appropriate, the +short_text+ (string) entry should also be
+       provided. It will be used in case the status line needs to be shortened
+       because it uses more space than your screen provides. For example, when
+       displaying an IPv6 address, the prefix is usually (!) more relevant
+       than the suffix, because the latter stays constant when using autoconf,
+       while the prefix changes. When displaying the date, the time is more
+       important than the date (it is more likely that you know which day it
+       is than what time it is).
+color::
+       To make the current state of the information easy to spot, colors can
+       be used. For example, the wireless block could be displayed in red
+       (using the +color+ (string) entry) if the card is not associated with
+       any network and in green or yellow (depending on the signal strength)
+       when it is associated.
+       Colors are specified in hex (like in HTML), starting with a leading
+       hash sign. For example, +#ff0000+ means red.
+name and instance::
+       Every block should have a unique +name+ (string) entry so that it can
+       be easily identified in scripts which process the output. i3bar
+       completely ignores the name and instance fields. Make sure to also
+       specify an +instance+ (string) entry where appropriate. For example,
+       the user can have multiple disk space blocks for multiple mount points.
+urgent::
+       A boolean which specifies whether the current value is urgent. Examples
+       are battery charge values below 1 percent or no more available disk
+       space (for non-root users). The presentation of urgency is up to i3bar.
+
+If you want to put in your own entries into a block, prefix the key with an
+underscore (_). i3bar will ignore all keys it doesn’t understand, and prefixing
+them with an underscore makes it clear in every script that they are not part
+of the i3bar protocol.
+
+*Example*:
+------------------------------------------
+{
+ "full_text": "E: 10.0.0.1 (1000 Mbit/s)",
+ "_ethernet_vendor": "Intel"
+}
+------------------------------------------
+
+An example of a block which uses all possible entries follows:
+
+*Example*:
+------------------------------------------
+{
+ "full_text": "E: 10.0.0.1 (1000 Mbit/s)",
+ "short_text": "10.0.0.1",
+ "color": "#00ff00",
+ "urgent": false,
+ "name": "ethernet",
+ "instance": "eth0"
+}
+------------------------------------------
index 61a85c0a7a70ca91b98f80c3e57beacb27f8034b..1ac97fecab020220b12e4e4629894c1cdb08bec3 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -1,7 +1,7 @@
 IPC interface (interprocess communication)
 ==========================================
-Michael Stapelberg <michael+i3@stapelberg.de>
-October 2011
+Michael Stapelberg <michael@i3wm.org>
+February 2012
 
 This document describes how to interface with i3 from a separate process. This
 is useful for example to remote-control i3 (to write test cases for example) or
@@ -48,7 +48,7 @@ Currently implemented message types are the following:
 COMMAND (0)::
        The payload of the message is a command for i3 (like the commands you
        can bind to keys in the configuration file) and will be executed
-       directly after receiving it. There is no reply to this message.
+       directly after receiving it.
 GET_WORKSPACES (1)::
        Gets the current workspaces. The reply will be a JSON-encoded list of
        workspaces (see the reply section).
@@ -113,17 +113,17 @@ The following reply types are implemented:
 
 COMMAND (0)::
        Confirmation/Error code for the COMMAND message.
-GET_WORKSPACES (1)::
+WORKSPACES (1)::
        Reply to the GET_WORKSPACES message.
 SUBSCRIBE (2)::
        Confirmation/Error code for the SUBSCRIBE message.
-GET_OUTPUTS (3)::
+OUTPUTS (3)::
        Reply to the GET_OUTPUTS message.
-GET_TREE (4)::
+TREE (4)::
        Reply to the GET_TREE message.
-GET_MARKS (5)::
+MARKS (5)::
        Reply to the GET_MARKS message.
-GET_BAR_CONFIG (6)::
+BAR_CONFIG (6)::
        Reply to the GET_BAR_CONFIG message.
 
 === COMMAND reply
@@ -136,7 +136,7 @@ property is +success (bool)+, but this will be expanded in future versions.
 { "success": true }
 -------------------
 
-=== GET_WORKSPACES reply
+=== WORKSPACES reply
 
 The reply consists of a serialized list of workspaces. Each workspace has the
 following properties:
@@ -250,7 +250,7 @@ rect (map)::
 ]
 -------------------
 
-=== GET_TREE reply
+=== TREE reply
 
 The reply consists of a serialized tree. Each node in the tree (representing
 one container) has at least the properties listed below. While the nodes might
@@ -433,7 +433,7 @@ JSON dump:
 }
 ------------------------
 
-=== GET_MARKS reply
+=== 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
@@ -442,7 +442,7 @@ contents are not unique).
 
 If no window has a mark the response will be the empty array [].
 
-=== GET_BAR_CONFIG reply
+=== 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
@@ -624,3 +624,4 @@ Perl::
        http://search.cpan.org/search?query=AnyEvent::I3
 Python::
        http://github.com/thepub/i3ipc
+       http://github.com/jzib/i3-py (includes higher-level features)
diff --git a/docs/slides-2012-01-25/TdilE.jpg b/docs/slides-2012-01-25/TdilE.jpg
new file mode 100644 (file)
index 0000000..9b939ab
Binary files /dev/null and b/docs/slides-2012-01-25/TdilE.jpg differ
diff --git a/docs/slides-2012-01-25/Ubuntu_Linux_Jaunty_screenshot.png b/docs/slides-2012-01-25/Ubuntu_Linux_Jaunty_screenshot.png
new file mode 100644 (file)
index 0000000..95a2cdb
Binary files /dev/null and b/docs/slides-2012-01-25/Ubuntu_Linux_Jaunty_screenshot.png differ
diff --git a/docs/slides-2012-01-25/i3.tex b/docs/slides-2012-01-25/i3.tex
new file mode 100644 (file)
index 0000000..7487195
--- /dev/null
@@ -0,0 +1,146 @@
+% vim:ts=4:sw=4:expandtab
+% © 2012 Michael Stapelberg
+%
+% use xelatex %<
+%
+\documentclass[xetex,serif,compress]{beamer}
+\usepackage{fontspec}
+\usepackage{xunicode} % Unicode extras!
+\usepackage{xltxtra}  % Fixes
+\usepackage{listings}
+\setmainfont{Trebuchet MS}
+\setmonofont{Inconsolata}
+\usetheme{default}
+
+\setbeamertemplate{frametitle}{
+    \color{black}
+    \vspace*{0.5cm}
+    \hspace*{0.25cm}
+    \textbf{\insertframetitle}
+    \par
+}
+
+% Hide the navigation icons at the bottom of the page
+\setbeamertemplate{navigation symbols}{}
+
+% No margins on any side
+\setbeamersize{text margin left=0cm,text margin right=0cm}
+
+
+\begin{document}
+
+% slide with bullet points
+\newcommand{\mslide}[2]{
+    \begin{frame}{#1}
+        \begin{center}
+        \begin{list}{$\bullet$}{\itemsep=1em}
+            #2
+        \end{list}
+        \end{center}
+    \end{frame}
+}
+
+\frame{
+\begin{center}
+\vspace{1.5cm}
+{\huge i3}\\
+{\large improved tiling window manager}\\
+\vspace{3cm}
+Michael Stapelberg\\
+\vspace{0.5cm}
+2012-01-25\\
+\end{center}
+}
+
+\begin{frame}{}
+\begin{center}
+\huge
+"Interesting, what is this?"
+
+\vspace*{1cm}
+
+vs.
+
+\vspace*{1cm}
+
+"What?! \textbf{Another} window manager?"
+\end{center}
+\end{frame}
+
+
+\begin{frame}{}
+    % talk about the difference between a desktop environment and a window manager:
+    % a desktop environment (like GNOME, KDE, Xfce) is a collection of
+    % programs, libraries (including a graphical toolkit) and configuration.
+    % it usually aims for a coherent look and feel and comes with a number of
+    % tools (g*, like gedit, geeqie, …)
+    % One of the programs of a DE is a window manager.
+    \begin{figure}
+    \includegraphics[width=0.97\textwidth]{Ubuntu_Linux_Jaunty_screenshot.png}
+    % source: http://en.wikipedia.org/wiki/File:Ubuntu_Linux_Jaunty_screenshot.png
+    \end{figure}
+\end{frame}
+
+
+\begin{frame}{}
+\begin{center}
+% compare this to a screenshot of i3:
+% notice the little amount of toolbars.
+% notice the lack of fancy window decorations
+% notice the absence of a desktop.
+% instead, you get to use the full screen.
+    \begin{figure}
+    \includegraphics[width=0.97\textwidth]{TdilE.jpg}
+    % source: jrd in #i3
+    \end{figure}
+\end{center}
+\end{frame}
+
+
+\mslide{i3: history and features}{
+    \item started from scratch in february 2009
+    \item successor* to wmii, which we couldn’t hack
+    \item clean, readable, documented code. and documentation
+    \item proper multi-monitor support, utf-8 clean
+    \item fast and lightweight, aimed at power users
+}
+
+% live demo here, just like at FrOSCon
+% include: the docs, with the keyboard layout
+% include: the configuration file
+
+\mslide{Inter-process communication}{
+    \item UNIX socket, JSON for serialization
+    \item i3-msg (C), AnyEvent::I3 (Perl), i3-ipc (Ruby), i3ipc (Python)
+    \item send any command, like \texttt{floating enable}
+    \item receive events (like focus change)
+    \item access the layout tree (!)
+}
+
+% demo: change a workspace
+% demo: testsuite
+
+\mslide{Example workflows}{
+    \item Urgency hint
+    \item Scratchpad
+    \item Web development (browser, editor, syslog)
+    \item Coding (C): two editors (code, test), quickly opening docs
+}
+
+\mslide{i3 in numbers}{
+    \item 3149 commits by 39 different people
+    \item > 600 tickets (about 60 open)
+    \item about 10.000 SLOC (mostly C, a bit of Perl)
+    \item testsuite: > 1000 test instructions in 96 files
+    \item conservative guess of > 1000 users
+}
+
+\mslide{Thanks for your attention}{
+    \item See \url{http://www.i3wm.org/} for everything
+    \item Ubuntu: upgrade to our repository: \url{http://i3wm.org/docs/repositories.html}
+    \item Debian: upgrade to the version in Debian testing
+    \item Any questions?
+    \item (pictures Creative Commons Attribution-Share Alike 3.0 Unported)
+}
+
+\end{document}
index e75f1ca10e7e2acb470402b10ec760b2031b2540..32879e6fa4681414fb1edf774743365bca013053 100644 (file)
@@ -1,7 +1,7 @@
 i3 User’s Guide
 ===============
 Michael Stapelberg <michael+i3@stapelberg.de>
-October 2011
+January 2012
 
 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
@@ -89,7 +89,7 @@ To display a window in fullscreen mode or to go out of fullscreen mode again,
 press +mod+f+.
 
 There is also a global fullscreen mode in i3 in which the client will span all
-available outputs.
+available outputs (the command is +fullscreen global+).
 
 === Opening other applications
 
@@ -370,6 +370,27 @@ floating_modifier <Modifiers>
 floating_modifier Mod1
 --------------------------------
 
+=== Constraining floating window size
+
+The maximum and minimum dimensions of floating windows can be specified. If
+either dimension of +floating_maximum_size+ is specified as -1, that dimension
+will be unconstrained with respect to its maximum value. If either dimension of
++floating_maximum_size+ is undefined, or specified as 0, i3 will use a default
+value to constrain the maximum size. +floating_minimum_size+ is treated in a
+manner analogous to +floating_maximum_size+.
+
+*Syntax*:
+----------------------------------------
+floating_minimum_size <width> x <height>
+floating_maximum_size <width> x <height>
+----------------------------------------
+
+*Example*:
+--------------------------------------
+floating_minimum_size 75 x 50
+floating_maximum_size -1 x -1
+--------------------------------------
+
 === Orientation for new workspaces
 
 New workspaces get a reasonable default orientation: Wide-screen monitors
@@ -591,7 +612,7 @@ You can change all colors which i3 uses to draw the window decorations.
 
 *Syntax*:
 --------------------------------------------
-colorclass border background text
+colorclass border background text indicator
 --------------------------------------------
 
 Where colorclass can be one of:
@@ -624,18 +645,24 @@ area of the terminal and the i3 border.
 Colors are in HTML hex format (#rrggbb), see the following example:
 
 *Examples (default colors)*:
------------------------------------------------
-# class                 border  backgr. text
-client.focused          #4c7899 #285577 #ffffff
-client.focused_inactive #333333 #5f676a #ffffff
-client.unfocused        #333333 #222222 #888888
-client.urgent           #2f343a #900000 #ffffff
------------------------------------------------
+---------------------------------------------------------
+# class                 border  backgr. text    indicator
+client.focused          #4c7899 #285577 #ffffff #2e9ef4
+client.focused_inactive #333333 #5f676a #ffffff #484e50
+client.unfocused        #333333 #222222 #888888 #292d2e
+client.urgent           #2f343a #900000 #ffffff #900000
+---------------------------------------------------------
 
 Note that for the window decorations, the color around the child window is the
 background color, and the border color is only the two thin lines at the top of
 the window.
 
+The indicator color is used for indicating where a new window will be opened.
+For horizontal split containers, the right border will be painted in indicator
+color, for vertical split containers, the bottom border. This only applies to
+single windows within a split container, which are otherwise indistinguishable
+from single windows outside of a split container.
+
 === Interprocess communication
 
 i3 uses unix sockets to provide an IPC interface. This allows third-party
@@ -799,6 +826,29 @@ bar {
 }
 ---------------------------
 
+=== i3bar command
+
+By default i3 will just pass +i3bar+ and let your shell handle the execution,
+searching your +$PATH+ for a correct version.
+If you have a different +i3bar+ somewhere or the binary is not in your +$PATH+ you can
+tell i3 what to execute.
+
+The specified command will be passed to +sh -c+, so you can use globbing and
+have to have correct quoting etc.
+
+*Syntax*:
+----------------------
+i3bar_command command
+----------------------
+
+*Example*:
+-------------------------------------------------
+bar {
+    i3bar_command /home/user/bin/i3bar
+}
+-------------------------------------------------
+
+[[status_command]]
 === Statusline command
 
 i3bar can run a program and display every line of its +stdout+ output on the
@@ -815,31 +865,41 @@ status_command command
 
 *Example*:
 -------------------------------------------------
-status_command i3status --config ~/.i3status.conf
+bar {
+    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).
+mode). The modifier key can be configured using the +modifier+ option.
 
 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.
+The default is dock mode; in hide mode, the default modifier is Mod4 (usually
+the windows key).
 
 *Syntax*:
 ----------------
 mode <dock|hide>
+modifier <Modifier>
 ----------------
 
 *Example*:
 ----------------
-mode hide
+bar {
+    mode hide
+    modifier Mod1
+}
 ----------------
 
+Available modifiers are Mod1-Mod5, Shift, Control (see +xmodmap(1)+).
+
+[[i3bar_position]]
 === Position
 
 This option determines in which edge of the screen i3bar should show up.
@@ -853,7 +913,9 @@ position <top|bottom>
 
 *Example*:
 ---------------------
-position top
+bar {
+    position top
+}
 ---------------------
 
 === Output(s)
@@ -862,6 +924,9 @@ 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.
 
+To make a particular i3bar instance handle multiple outputs, specify the output
+directive multiple times.
+
 *Syntax*:
 ---------------
 output <output>
@@ -871,18 +936,20 @@ output <output>
 -------------------------------
 # big monitor: everything
 bar {
-       output HDMI2
-       status_command i3status
+    # The display is connected either via HDMI or via DisplayPort
+    output HDMI2
+    output DP2
+    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
-       }
+    output LVDS1
+    status_command i3status --config ~/.i3status-small.conf
+    colors {
+        background #000000
+        statusline #ffffff
+    }
 }
 -------------------------------
 
@@ -902,10 +969,14 @@ tray_output <none|output>
 *Example*:
 -------------------------
 # disable system tray
-tray_output none
+bar {
+    tray_output none
+}
 
 # show tray icons on the big monitor
-tray_output HDMI2
+bar {
+    tray_output HDMI2
+}
 -------------------------
 
 === Font
@@ -920,7 +991,9 @@ font <font>
 
 *Example*:
 --------------------------------------------------------------
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+bar {
+    font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+}
 --------------------------------------------------------------
 
 === Workspace buttons
@@ -937,7 +1010,9 @@ workspace_buttons <yes|no>
 
 *Example*:
 --------------------
-workspace_buttons no
+bar {
+    workspace_buttons no
+}
 --------------------
 
 === Colors
@@ -950,19 +1025,19 @@ background::
 statusline::
        Text color to be used for the statusline.
 focused_workspace::
-       Text color/background color for a workspace button when the workspace
+       Border, background and text color for a workspace button when the workspace
        has focus.
 active_workspace::
-       Text color/background color for a workspace button when the workspace
+       Border, background and text 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
+       Border, background and text 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
+       Border, background and text color for a workspace button when the workspace
        window with the urgency hint set.
 
 *Syntax*:
@@ -971,20 +1046,22 @@ colors {
     background <color>
     statusline <color>
 
-    colorclass <foreground> <background>
+    colorclass <border> <background> <text>
 }
 ----------------------------------------
 
-*Example*:
+*Example (default colors)*:
 --------------------------------------
-colors {
-    background #000000
-    statusline #ffffff
-
-    focused_workspace  #ffffff #285577
-    active_workspace   #ffffff #333333
-    inactive_workspace #888888 #222222
-    urgent_workspace   #ffffff #900000
+bar {
+    colors {
+        background #000000
+        statusline #ffffff
+
+        focused_workspace  #4c7899 #285577 #ffffff
+        active_workspace   #333333 #5f676a #ffffff
+        inactive_workspace #333333 #222222 #888888
+        urgent_workspace   #2f343a #900000 #ffffff
+    }
 }
 --------------------------------------
 
@@ -1005,9 +1082,9 @@ specific workspace and immediately switch to that workspace, you can configure
 the following keybinding:
 
 *Example*:
--------------------------------------------
-bindsym mod+x move workspace 3; workspace 3
--------------------------------------------
+--------------------------------------------------------
+bindsym mod+x move container to workspace 3; workspace 3
+--------------------------------------------------------
 
 [[command_criteria]]
 
@@ -1036,6 +1113,10 @@ id::
        Compares the X11 window ID, which you can get via +xwininfo+ for example.
 title::
        Compares the X11 window title (_NET_WM_NAME or WM_NAME as fallback).
+urgent::
+       Compares the urgent state of the window. Can be "latest" or "oldest".
+       Matches the latest or oldest urgent window, respectively.
+       (The following aliases are also available: newest, last, recent, first)
 con_mark::
        Compares the mark set for this container, see <<vim_like_marks>>.
 con_id::
@@ -1066,7 +1147,7 @@ exec [--no-startup-id] command
 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
+bindsym mod+Return exec --no-startup-id urxvt
 ------------------------------
 
 The +--no-startup-id+ parameter disables startup-notification support for this
@@ -1123,7 +1204,8 @@ bindsym mod+t floating toggle
 
 === Focusing/Moving containers
 
-To change the focus, use the focus command: +focus left+, +focus right+, +focus down+ and +focus up+.
+To change the focus, use the focus command: +focus left+, +focus right+, +focus
+down+ and +focus up+.
 
 There are a few special parameters you can use for the focus command:
 
@@ -1138,6 +1220,9 @@ tiling::
        Sets focus to the last focused tiling container.
 mode_toggle::
        Toggles between floating/tiling containers.
+output::
+       Followed by a direction or an output name, this will focus the
+       corresponding output.
 
 For moving, use +move left+, +move right+, +move down+ and +move up+.
 
@@ -1145,6 +1230,7 @@ For moving, use +move left+, +move right+, +move down+ and +move up+.
 -----------------------------------
 focus <left|right|down|up>
 focus <parent|child|floating|tiling|mode_toggle>
+focus output <<left|right|down|up>|output>
 move <left|right|down|up> [<px> px]
 -----------------------------------
 
@@ -1165,6 +1251,12 @@ bindsym mod+u focus parent
 # Focus last floating/tiling container
 bindsym mod+g focus mode_toggle
 
+# Focus the output right to the current one
+bindsym mod+x focus output right
+
+# Focus the big output
+bindsym mod+x focus output HDMI-2
+
 # Move container to the left, bottom, top, right:
 bindsym mod+j move left
 bindsym mod+k move down
@@ -1180,22 +1272,29 @@ bindsym mod+j move left 20 px
 
 To change to a specific workspace, use the +workspace+ command, followed by the
 number or name of the workspace. To move containers to specific workspaces, use
-+move workspace+.
++move container to workspace+.
 
 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. Similarly, you can use +move workspace next+ and +move workspace
-prev+ to move a container to the next/previous workspace.
+combination. To restrict those to the current output, use +workspace
+next_on_output+ and +workspace prev_on_output+. Similarly, you can use +move
+container to workspace next+ and +move container to 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 next output in the specified direction.
+use the +move container to 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 next output in the specified direction.
+
+To move a whole workspace to another xrandr output such as +LVDS1+ or +VGA1+,
+you can use the +move workspace to 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 next output in the specified direction.
 
 *Examples*:
 -------------------------
@@ -1203,12 +1302,15 @@ bindsym mod+1 workspace 1
 bindsym mod+2 workspace 2
 ...
 
-bindsym mod+Shift+1 move workspace 1
-bindsym mod+Shift+2 move workspace 2
+bindsym mod+Shift+1 move container to workspace 1
+bindsym mod+Shift+2 move container to workspace 2
 ...
 
 # switch between the current and the previously focused one
 bindsym mod+b workspace back_and_forth
+
+# move the whole workspace to the next output
+bindsym mod+x move workspace to output right
 -------------------------
 
 ==== Named workspaces
@@ -1413,6 +1515,40 @@ bindsym mod+Shift+w reload
 bindsym mod+Shift+e exit
 ----------------------------
 
+=== Scratchpad
+
+There are two commands to use any existing window as scratchpad window. +move
+scratchpad+ will move a window to the scratchpad workspace. This will make it
+invisible until you show it again. There is no way to open that workspace.
+Instead, when using +scratchpad show+, the window will be shown again, as a
+floating window, centered on your current workspace (using +scratchpad show+ on
+a visible scratchpad window will make it hidden again, so you can have a
+keybinding to toggle).
+
+As the name indicates, this is useful for having a window with your favorite
+editor always at hand. However, you can also use this for other permanently
+running applications which you don’t want to see all the time: Your music
+player, alsamixer, maybe even your mail client…?
+
+*Syntax*:
+---------------
+move scratchpad
+
+scratchpad show
+---------------
+
+*Examples*:
+------------------------------------------------
+# Make the currently focused window a scratchpad
+bindsym mod+Shift+minus move scratchpad
+
+# Show the first scratchpad window
+bindsym mod+minus scratchpad show
+
+# Show the sup-mail scratchpad window, if any.
+bindsym mod4+s [title="^Sup ::"] scratchpad show
+------------------------------------------------
+
 [[multi_monitor]]
 
 == Multiple monitors
@@ -1534,14 +1670,13 @@ If you don’t already have your favorite way of generating such a status line
 this task. It was written in C with the goal of using as few syscalls as
 possible to reduce the time your CPU is woken up from sleep states. Because
 i3status only spits out text, you need to combine it with some other tool, like
-i3bar. Use a pipe to connect them: +i3status | i3bar -d+.
+i3bar. See <<status_command>> for how to display i3status in i3bar.
 
 Regardless of which application you use to display the status line, you
 want to make sure that it registers as a dock window using EWMH hints. i3 will
 position the window either at the top or at the bottom of the screen, depending
-on which hint the application sets. With i3bar, you can use +-d+ or +-dbottom+
-for positioning it at the bottom and +-dtop+ to position it at the top of the
-screen.
+on which hint the application sets. With i3bar, you can configure its position,
+see <<i3bar_position>>.
 
 === Giving presentations (multi-monitor)
 
diff --git a/generate-command-parser.pl b/generate-command-parser.pl
new file mode 100755 (executable)
index 0000000..993c64f
--- /dev/null
@@ -0,0 +1,202 @@
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+#
+# i3 - an improved dynamic tiling window manager
+# © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+#
+# generate-command-parser.pl: script to generate parts of the command parser
+# from its specification file parser-specs/commands.spec.
+#
+# Requires only perl >= 5.10, no modules.
+
+use strict;
+use warnings;
+use Data::Dumper;
+use v5.10;
+
+# reads in a whole file
+sub slurp {
+    open my $fh, '<', shift;
+    local $/;
+    <$fh>;
+}
+
+# Stores the different states.
+my %states;
+
+# XXX: don’t hardcode input and output
+my $input = '../parser-specs/commands.spec';
+my @raw_lines = split("\n", slurp($input));
+my @lines;
+
+# XXX: In the future, we might switch to a different way of parsing this. The
+# parser is in many ways not good — one obvious one is that it is hand-crafted
+# without a good reason, also it preprocesses lines and forgets about line
+# numbers. Luckily, this is just an implementation detail and the specification
+# for the i3 command parser is in-tree (not user input).
+# -- michael, 2012-01-12
+
+# First step of preprocessing:
+# Join token definitions which are spread over multiple lines.
+for my $line (@raw_lines) {
+    next if $line =~ /^\s*#/ || $line =~ /^\s*$/;
+
+    if ($line =~ /^\s+->/) {
+        # This is a continued token definition, append this line to the
+        # previous one.
+        $lines[$#lines] = $lines[$#lines] . $line;
+    } else {
+        push @lines, $line;
+        next;
+    }
+}
+
+# First step: We build up the data structure containing all states and their
+# token rules.
+
+my $current_state;
+
+for my $line (@lines) {
+    if (my ($state) = ($line =~ /^state ([A-Z_]+):$/)) {
+        #say "got a new state: $state";
+        $current_state = $state;
+    } else {
+        # Must be a token definition:
+        # [identifier = ] <tokens> -> <action>
+        #say "token definition: $line";
+
+        my ($identifier, $tokens, $action) =
+            ($line =~ /
+                ^\s*                  # skip leading whitespace
+                ([a-z_]+ \s* = \s*|)  # optional identifier
+                (.*?) -> \s*          # token 
+                (.*)                  # optional action
+             /x);
+
+        # Cleanup the identifier (if any).
+        $identifier =~ s/^\s*(\S+)\s*=\s*$/$1/g;
+
+        # Cleanup the tokens (remove whitespace).
+        $tokens =~ s/\s*//g;
+
+        # The default action is to stay in the current state.
+        $action = $current_state if length($action) == 0;
+
+        #say "identifier = *$identifier*, token = *$tokens*, action = *$action*";
+        for my $token (split(',', $tokens)) {
+            my $store_token = {
+                token => $token,
+                identifier => $identifier,
+                next_state => $action,
+            };
+            if (exists $states{$current_state}) {
+                push @{$states{$current_state}}, $store_token;
+            } else {
+                $states{$current_state} = [ $store_token ];
+            }
+        }
+    }
+}
+
+# Second step: Generate the enum values for all states.
+
+# It is important to keep the order the same, so we store the keys once.
+my @keys = keys %states;
+
+open(my $enumfh, '>', 'GENERATED_enums.h');
+
+# XXX: we might want to have a way to do this without a trailing comma, but gcc
+# seems to eat it.
+say $enumfh 'typedef enum {';
+my $cnt = 0;
+for my $state (@keys, '__CALL') {
+    say $enumfh "    $state = $cnt,";
+    $cnt++;
+}
+say $enumfh '} cmdp_state;';
+close($enumfh);
+
+# Third step: Generate the call function.
+open(my $callfh, '>', 'GENERATED_call.h');
+say $callfh 'static void GENERATED_call(const int call_identifier, struct CommandResult *result) {';
+say $callfh '    switch (call_identifier) {';
+my $call_id = 0;
+for my $state (@keys) {
+    my $tokens = $states{$state};
+    for my $token (@$tokens) {
+        next unless $token->{next_state} =~ /^call /;
+        my ($cmd) = ($token->{next_state} =~ /^call (.*)/);
+        my ($next_state) = ($cmd =~ /; ([A-Z_]+)$/);
+        $cmd =~ s/; ([A-Z_]+)$//;
+        # Go back to the INITIAL state unless told otherwise.
+        $next_state ||= 'INITIAL';
+        my $fmt = $cmd;
+        # Replace the references to identified literals (like $workspace) with
+        # calls to get_string().
+        $cmd =~ s/\$([a-z_]+)/get_string("$1")/g;
+        # Used only for debugging/testing.
+        $fmt =~ s/\$([a-z_]+)/%s/g;
+        $fmt =~ s/"([a-z0-9_]+)"/%s/g;
+
+        say $callfh "         case $call_id:";
+        say $callfh '#ifndef TEST_PARSER';
+        my $real_cmd = $cmd;
+        if ($real_cmd =~ /\(\)/) {
+            $real_cmd =~ s/\(/(&current_match, result/;
+        } else {
+            $real_cmd =~ s/\(/(&current_match, result, /;
+        }
+        say $callfh "             $real_cmd;";
+        say $callfh '#else';
+        # debug
+        $cmd =~ s/[^(]+\(//;
+        $cmd =~ s/\)$//;
+        $cmd = ", $cmd" if length($cmd) > 0;
+        say $callfh qq|           printf("$fmt\\n"$cmd);|;
+        say $callfh '#endif';
+        say $callfh "             state = $next_state;";
+        say $callfh "             break;";
+        $token->{next_state} = "call $call_id";
+        $call_id++;
+    }
+}
+say $callfh '        default:';
+say $callfh '            printf("BUG in the parser. state = %d\n", call_identifier);';
+say $callfh '    }';
+say $callfh '}';
+close($callfh);
+
+# Fourth step: Generate the token datastructures.
+
+open(my $tokfh, '>', 'GENERATED_tokens.h');
+
+for my $state (@keys) {
+    my $tokens = $states{$state};
+    say $tokfh 'cmdp_token tokens_' . $state . '[' . scalar @$tokens . '] = {';
+    for my $token (@$tokens) {
+        my $call_identifier = 0;
+        my $token_name = $token->{token};
+        if ($token_name =~ /^'/) {
+            # To make the C code simpler, we leave out the trailing single
+            # quote of the literal. We can do strdup(literal + 1); then :).
+            $token_name =~ s/'$//;
+        }
+        my $next_state = $token->{next_state};
+        if ($next_state =~ /^call /) {
+            ($call_identifier) = ($next_state =~ /^call ([0-9]+)$/);
+            $next_state = '__CALL';
+        }
+        my $identifier = $token->{identifier};
+        say $tokfh qq|    { "$token_name", "$identifier", $next_state, { $call_identifier } }, |;
+    }
+    say $tokfh '};';
+}
+
+say $tokfh 'cmdp_token_ptr tokens[' . scalar @keys . '] = {';
+for my $state (@keys) {
+    my $tokens = $states{$state};
+    say $tokfh '    { tokens_' . $state . ', ' . scalar @$tokens . ' },';
+}
+say $tokfh '};';
+
+close($tokfh);
index 27d5bf543c5e339e58bae65ffa8754e11205dcbf..75d4684f8bcd56f919a3957f54b23646ed398706 100644 (file)
@@ -26,12 +26,12 @@ $(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
 
 cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS}
        echo "[i3-config-wizard] LEX $<"
-       flex -i -o$(@:.o=.c) $<
+       $(FLEX) -i -o$(@:.o=.c) $<
        $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(@:.o=.c)
 
 cfgparse.y.o: cfgparse.y ${HEADERS}
        echo "[i3-config-wizard] YACC $<"
-       bison --debug --verbose -b $(basename $< .y) -d $<
+       $(BISON) --debug --verbose -b $(basename $< .y) -d $<
        $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(<:.y=.tab.c)
 
 
index 9c2bbe550f9b9e8bab9244aef1c265fe8dc041d7..be042673f2c53a02a1bbe8855fa13f38ced2b9c0 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-config-wizard: Program to convert configs using keycodes to configs using
  *                   keysyms.
@@ -13,7 +13,7 @@
 #endif
 
 /* For systems without getline, fall back to fgetln */
-#if defined(__APPLE__) || (defined(__FreeBSD__) && __FreeBSD_version < 800000)
+#if defined(__APPLE__) || (defined(__FreeBSD__) && __FreeBSD_version < 800000) || defined(__OpenBSD__)
 #define USE_FGETLN
 #elif defined(__FreeBSD__)
 /* Defining this macro before including stdio.h is necessary in order to have
@@ -125,13 +125,15 @@ static int handle_expose() {
     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(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
+    set_font(&font);
 
-#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) \
+    draw_text(text, strlen(text), false, pixmap, pixmap_gc,\
+            x, (row - 1) * font.height + 4, 300 - x * 2)
 
     if (current_step == STEP_WELCOME) {
         /* restore font color */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
+        set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
 
         txt(10, 2, "You have not configured i3 yet.");
         txt(10, 3, "Do you want me to generate ~/.i3/config?");
@@ -139,16 +141,16 @@ static int handle_expose() {
         txt(85, 7, "No, I will use the defaults");
 
         /* green */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#00FF00") });
+        set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
         txt(25, 5, "<Enter>");
 
         /* red */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
+        set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
         txt(31, 7, "<ESC>");
     }
 
     if (current_step == STEP_GENERATE) {
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
+        set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
 
         txt(10, 2, "Please choose either:");
         txt(85, 4, "Win as default modifier");
@@ -163,19 +165,19 @@ static int handle_expose() {
         else txt(31, 4, "<Win>");
 
         /* the selected modifier */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ bold_font.id });
+        set_font(&bold_font);
+        set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
         if (modifier == MOD_Mod4)
-            txt(31, 4, "<Win>");
-        else txt(31, 5, "<Alt>");
+            txt(10, 4, "-> <Win>");
+        else txt(10, 5, "-> <Alt>");
 
         /* green */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_FONT,
-                      (uint32_t[]) { get_colorpixel("#00FF00"), font.id });
-
+        set_font(&font);
+        set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
         txt(25, 9, "<Enter>");
 
         /* red */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
+        set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
         txt(31, 10, "<ESC>");
     }
 
@@ -434,7 +436,7 @@ int main(int argc, char *argv[]) {
     unlink(config_path);
 
     if (socket_path == NULL)
-        socket_path = socket_path_from_x11();
+        socket_path = root_atom_contents("I3_SOCKET_PATH");
 
     if (socket_path == NULL)
         socket_path = "/tmp/i3-ipc.sock";
diff --git a/i3-dump-log/Makefile b/i3-dump-log/Makefile
new file mode 100644 (file)
index 0000000..18076e5
--- /dev/null
@@ -0,0 +1,32 @@
+# Default value so one can compile i3-dump-log 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 "[i3-dump-log] CC $<"
+       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+all: i3-dump-log
+
+i3-dump-log: ${FILES}
+       echo "[i3-dump-log] LINK i3-dump-log"
+       $(CC) $(LDFLAGS) -o i3-dump-log ${FILES} $(LIBS)
+
+install: all
+       echo "[i3-dump-log] INSTALL"
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+       $(INSTALL) -m 0755 i3-dump-log $(DESTDIR)$(PREFIX)/bin/
+
+clean:
+       rm -f *.o
+
+distclean: clean
+       rm -f i3-dump-log
diff --git a/i3-dump-log/main.c b/i3-dump-log/main.c
new file mode 100644 (file)
index 0000000..30dd514
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * i3-dump-log/main.c: Dumps the i3 SHM log to stdout.
+ *
+ */
+#include <stdio.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include "libi3.h"
+#include "shmlog.h"
+#include <i3/ipc.h>
+
+int main(int argc, char *argv[]) {
+    int o, option_index = 0;
+    bool verbose = false;
+
+    static struct option long_options[] = {
+        {"version", no_argument, 0, 'v'},
+        {"verbose", no_argument, 0, 'V'},
+        {"help", no_argument, 0, 'h'},
+        {0, 0, 0, 0}
+    };
+
+    char *options_string = "s:vVh";
+
+    while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
+        if (o == 'v') {
+            printf("i3-dump-log " I3_VERSION "\n");
+            return 0;
+        } else if (o == 'V') {
+            verbose = true;
+        } else if (o == 'h') {
+            printf("i3-dump-log " I3_VERSION "\n");
+            printf("i3-dump-log [-s <socket>]\n");
+            return 0;
+        }
+    }
+
+    char *shmname = root_atom_contents("I3_SHMLOG_PATH");
+    if (shmname == NULL)
+        errx(EXIT_FAILURE, "Cannot get I3_SHMLOG_PATH atom contents. Is i3 running on this display?");
+
+    if (*shmname == '\0')
+        errx(EXIT_FAILURE, "Cannot dump log: SHM logging is disabled in i3.");
+
+    struct stat statbuf;
+
+    int logbuffer_shm = shm_open(shmname, O_RDONLY, 0);
+    if (logbuffer_shm == -1)
+        err(EXIT_FAILURE, "Could not shm_open SHM segment for the i3 log (%s)", shmname);
+
+    if (fstat(logbuffer_shm, &statbuf) != 0)
+        err(EXIT_FAILURE, "stat(%s)", shmname);
+
+    char *logbuffer = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, logbuffer_shm, 0);
+    if (logbuffer == MAP_FAILED)
+        err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log");
+
+    i3_shmlog_header *header = (i3_shmlog_header*)logbuffer;
+
+    if (verbose)
+        printf("next_write = %d, last_wrap = %d, logbuffer_size = %d, shmname = %s\n",
+               header->offset_next_write, header->offset_last_wrap, header->size, shmname);
+    int chars;
+    char *walk = logbuffer + header->offset_next_write;
+    /* Skip the first line, it very likely is mangled. Not a problem, though,
+     * the log is chatty enough to have plenty lines left. */
+    while (*walk != '\0')
+        walk++;
+
+    /* Print the oldest log lines. We use printf("%s") to stop on \0. */
+    while (walk < (logbuffer + header->offset_last_wrap)) {
+        chars = printf("%s", walk);
+        /* Shortcut: If there are two consecutive \0 bytes, this part of the
+         * buffer was never touched. To not call printf() for every byte of the
+         * buffer, we directly exit the loop. */
+        if (*walk == '\0' && *(walk+1) == '\0')
+            break;
+        walk += (chars > 0 ? chars : 1);
+    }
+
+    /* Then start from the beginning and print the newer lines */
+    walk = logbuffer + sizeof(i3_shmlog_header);
+    while (walk < (logbuffer + header->offset_next_write)) {
+        chars = printf("%s", walk);
+        walk += (chars > 0 ? chars : 1);
+    }
+
+    return 0;
+}
index d97807d1749decec42982bf935e50fb37d6028f3..f494cbd56704c260c4806e486a17fd301948c4b8 100644 (file)
@@ -14,7 +14,4 @@ while (0)
 
 extern xcb_window_t root;
 
-char *convert_ucs_to_utf8(char *input);
-char *convert_utf8_to_ucs2(char *input, int *real_strlen);
-
 #endif
index def6848177464d7a86eec2aa8b91ef3ad4b7f2f7..b570952397ccfd5f1e55ef75d1eb625f3d0f89e4 100644 (file)
@@ -50,7 +50,7 @@ static char *glyphs_utf8[512];
 static int input_position;
 static i3Font font;
 static char *prompt;
-static int prompt_len;
+static size_t prompt_len;
 static int limit;
 xcb_window_t root;
 xcb_connection_t *conn;
@@ -94,7 +94,9 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
     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") });
+    set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
+
+    /* draw the text */
     uint8_t *con = concat_strings(glyphs_ucs, input_position);
     char *full_text = (char*)con;
     if (prompt != NULL) {
@@ -104,8 +106,8 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
         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);
+    if (input_position + prompt_len != 0)
+        draw_text(full_text, input_position + prompt_len, true, pixmap, pixmap_gc, 4, 4, 492);
 
     /* 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);
@@ -260,14 +262,14 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
 
     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);
+    char *out = convert_ucs2_to_utf8((xcb_char2b_t*)inp, 1);
     printf("converted to %s\n", out);
 
     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);
+    glyphs_utf8[input_position] = out;
     input_position++;
 
     if (input_position == limit)
@@ -340,7 +342,7 @@ int main(int argc, char *argv[]) {
     printf("using format \"%s\"\n", format);
 
     if (socket_path == NULL)
-        socket_path = socket_path_from_x11();
+        socket_path = root_atom_contents("I3_SOCKET_PATH");
 
     if (socket_path == NULL)
         socket_path = "/tmp/i3-ipc.sock";
@@ -348,7 +350,7 @@ int main(int argc, char *argv[]) {
     sockfd = ipc_connect(socket_path);
 
     if (prompt != NULL)
-        prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
+        prompt = (char*)convert_utf8_to_ucs2(prompt, &prompt_len);
 
     int screens;
     conn = xcb_connect(NULL, &screens);
@@ -361,6 +363,7 @@ int main(int argc, char *argv[]) {
     symbols = xcb_key_symbols_alloc(conn);
 
     font = load_font(pattern, true);
+    set_font(&font);
 
     /* Open an input window */
     win = xcb_generate_id(conn);
@@ -393,9 +396,6 @@ int main(int argc, char *argv[]) {
      * 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);
 
diff --git a/i3-input/ucs2_to_utf8.c b/i3-input/ucs2_to_utf8.c
deleted file mode 100644 (file)
index df112ee..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.
- *
- */
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <err.h>
-#include <iconv.h>
-
-#include "libi3.h"
-
-static iconv_t conversion_descriptor = 0;
-static iconv_t conversion_descriptor2 = 0;
-
-/*
- * Returns the input string, but converted from UCS-2 to UTF-8. Memory will be
- * allocated, thus the caller has to free the output.
- *
- */
-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 = 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;
-}
-
-/*
- * Converts the given string to UCS-2 big endian for use with
- * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
- * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
- * returned. It has to be freed when done.
- *
- */
-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 = 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 = 0;
-        return NULL;
-    }
-
-    if (real_strlen != NULL)
-        *real_strlen = ((buffer_size - output_size) / 2) - 1;
-
-    return buffer;
-}
-
index 4f4d0134cd15715d47f2ab8c0ba0bdfc3e09f035..c8ff41c933bf368efad8677c7983aea2d2fafaff 100755 (executable)
@@ -316,10 +316,10 @@ sub convert_command {
     if ($command =~ /^m[0-9]+/) {
         my ($number) = ($command =~ /^m([0-9]+)/);
         if (exists $workspace_names{$number}) {
-            print qq|$statement $key move workspace $workspace_names{$number}\n|;
+            print qq|$statement $key move container to workspace $workspace_names{$number}\n|;
             return;
         } else {
-            print qq|$statement $key move workspace $number\n|;
+            print qq|$statement $key move container to workspace $number\n|;
             return;
         }
     }
index 6a3b29d3eb77bad86150d88fd8a0431055423396..ccf6e10f276a54ce8d2716b593db7d9895890442 100644 (file)
@@ -91,7 +91,7 @@ int main(int argc, char *argv[]) {
     }
 
     if (socket_path == NULL)
-        socket_path = socket_path_from_x11();
+        socket_path = root_atom_contents("I3_SOCKET_PATH");
 
     /* Fall back to the default socket path */
     if (socket_path == NULL)
index 4d4e253a496a0cdabdd1a18a7cb3077bdfe98ccf..1dbd77369b28a184a07ccd7973f1e0443dc57e06 100644 (file)
@@ -131,17 +131,15 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
 
     /* restore font color */
-    uint32_t values[3];
-    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);
+    set_font_colors(pixmap_gc, color_text, color_background);
+    draw_text(prompt, strlen(prompt), false, pixmap, pixmap_gc,
+            4 + 4, 4 + 4, rect.width - 4 - 4);
 
     /* render close button */
     int line_width = 4;
     int w = 20;
     int y = rect.width;
+    uint32_t values[3];
     values[0] = color_button_background;
     values[1] = line_width;
     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
@@ -159,12 +157,10 @@ 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] = 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");
+    values[0] = 1;
+    set_font_colors(pixmap_gc, color_text, color_button_background);
+    draw_text("X", 1, false, pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
+            4 + 4 - 1, rect.width - y + w + line_width - w / 2 + 4);
     y -= w;
 
     y -= 20;
@@ -193,9 +189,9 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
 
         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);
+        set_font_colors(pixmap_gc, color_text, color_button_background);
+        draw_text(buttons[c].label, strlen(buttons[c].label), false, pixmap, pixmap_gc,
+                y - w - line_width + 6, 4 + 3, rect.width - y + w + line_width - 6);
 
         y -= w;
     }
@@ -256,7 +252,7 @@ int main(int argc, char *argv[]) {
                 break;
             case 'h':
                 printf("i3-nagbar " I3_VERSION "\n");
-                printf("i3-nagbar [-m <message>] [-b <button> <action>] [-f <font>] [-v]\n");
+                printf("i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
                 return 0;
             case 'b':
                 buttons = realloc(buttons, sizeof(button_t) * (buttoncnt + 1));
@@ -304,6 +300,7 @@ int main(int argc, char *argv[]) {
     }
 
     font = load_font(pattern, true);
+    set_font(&font);
 
     /* Open an input window */
     win = xcb_generate_id(conn);
@@ -387,9 +384,6 @@ int main(int argc, char *argv[]) {
     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(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
-
     /* Grab the keyboard to get all input */
     xcb_flush(conn);
 
@@ -431,9 +425,6 @@ int main(int argc, char *argv[]) {
 
                 xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, rect.width, rect.height);
                 xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
-
-                /* Create graphics context */
-                xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
                 break;
             }
         }
index d71a440bb1fe0c5c408ca8e18883f8ed114bb592..b3afceb7fa691ce3df5b8eeff19c97da48bdb4d3 100755 (executable)
@@ -1,14 +1,16 @@
 #!/bin/sh
+#
+# This code is released in public domain by Han Boetes <han@mijncomputer.nl>
+#
 # 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.
-[ -n "$VISUAL" ] && which $VISUAL >/dev/null && exec $VISUAL "$@"
-[ -n "$EDITOR" ] && which $EDITOR >/dev/null && exec $EDITOR "$@"
+# Distributions/packagers can enhance this script with a distribution-specific
+# mechanism to find the preferred 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 "$@"
+for editor in $VISUAL $EDITOR nano vim vi emacs pico qe mg jed gedit mc-edit; do
+    if which $editor > /dev/null 2>&1; then
+        exec $editor "$@"
+    fi
+done
index 32f30aff895e670d39a3755e07870bf287e5358f..df463251dffea61737ba9511e607f9cebb972d3b 100755 (executable)
@@ -1,15 +1,18 @@
 #!/bin/sh
+#
+# This code is released in public domain by Han Boetes <han@mijncomputer.nl>
+
 # 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.
-[ -n "$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 "$@"
+# Hopefully one of these is installed (no flamewars about preference please!):
+# We don't use 'more' because it will exit if the file is too short.
+# Worst case scenario we'll open the file in your editor.
+for pager in $PAGER less most w3m i3-sensible-editor; do
+    if which $pager > /dev/null 2>&1; then
+        exec $pager "$@"
+    fi
+done
index e5bf2718be1c3afa8623f6d3b577a6373bffad7b..a9975740864c07c51e2c18e1095f8eb43511ea65 100755 (executable)
@@ -1,15 +1,15 @@
 #!/bin/sh
+#
+# This code is released in public domain by Han Boetes <han@mijncomputer.nl>
+#
 # 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:
-[ -n "$TERMINAL" ] && 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 "$@"
+for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do
+    if which $terminal > /dev/null 2>&1; then
+        exec $terminal "$@"
+    fi
+done
diff --git a/i3.applications.desktop b/i3.applications.desktop
new file mode 100644 (file)
index 0000000..4dfa7e0
--- /dev/null
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Type=Application
+Encoding=UTF-8
+Name=i3
+Comment=improved dynamic tiling window manager
+Exec=i3
+X-GNOME-WMName=i3
+X-GNOME-Autostart-Phase=WindowManager
+X-GNOME-Provides=windowmanager
+X-GNOME-Autostart-Notify=false
index f5dc13f6401f355656b3ea6d19aa2a9cd5804101..4ee61d4b450af84175655a5f5f3960e595c35753 100644 (file)
--- a/i3.config
+++ b/i3.config
@@ -87,16 +87,16 @@ bindsym Mod1+9 workspace 9
 bindsym Mod1+0 workspace 10
 
 # move focused container to workspace
-bindsym Mod1+Shift+1 move workspace 1
-bindsym Mod1+Shift+2 move workspace 2
-bindsym Mod1+Shift+3 move workspace 3
-bindsym Mod1+Shift+4 move workspace 4
-bindsym Mod1+Shift+5 move workspace 5
-bindsym Mod1+Shift+6 move workspace 6
-bindsym Mod1+Shift+7 move workspace 7
-bindsym Mod1+Shift+8 move workspace 8
-bindsym Mod1+Shift+9 move workspace 9
-bindsym Mod1+Shift+0 move workspace 10
+bindsym Mod1+Shift+1 move container to workspace 1
+bindsym Mod1+Shift+2 move container to workspace 2
+bindsym Mod1+Shift+3 move container to workspace 3
+bindsym Mod1+Shift+4 move container to workspace 4
+bindsym Mod1+Shift+5 move container to workspace 5
+bindsym Mod1+Shift+6 move container to workspace 6
+bindsym Mod1+Shift+7 move container to workspace 7
+bindsym Mod1+Shift+8 move container to workspace 8
+bindsym Mod1+Shift+9 move container to workspace 9
+bindsym Mod1+Shift+0 move container to workspace 10
 
 # reload the configuration file
 bindsym Mod1+Shift+c reload
index dcab5196a2a14400272922f3e4462107a0ea489c..af081de0e14cc0db3004dadc4016070fc29441e3 100644 (file)
@@ -88,16 +88,16 @@ bindcode $mod+18 workspace 9
 bindcode $mod+19 workspace 10
 
 # move focused container to workspace
-bindcode $mod+Shift+10 move workspace 1
-bindcode $mod+Shift+11 move workspace 2
-bindcode $mod+Shift+12 move workspace 3
-bindcode $mod+Shift+13 move workspace 4
-bindcode $mod+Shift+14 move workspace 5
-bindcode $mod+Shift+15 move workspace 6
-bindcode $mod+Shift+16 move workspace 7
-bindcode $mod+Shift+17 move workspace 8
-bindcode $mod+Shift+18 move workspace 9
-bindcode $mod+Shift+19 move workspace 10
+bindcode $mod+Shift+10 move container to workspace 1
+bindcode $mod+Shift+11 move container to workspace 2
+bindcode $mod+Shift+12 move container to workspace 3
+bindcode $mod+Shift+13 move container to workspace 4
+bindcode $mod+Shift+14 move container to workspace 5
+bindcode $mod+Shift+15 move container to workspace 6
+bindcode $mod+Shift+16 move container to workspace 7
+bindcode $mod+Shift+17 move container to workspace 8
+bindcode $mod+Shift+18 move container to workspace 9
+bindcode $mod+Shift+19 move container to workspace 10
 
 # reload the configuration file
 bindcode $mod+Shift+54 reload
diff --git a/i3.desktop b/i3.desktop
deleted file mode 100644 (file)
index abe9e5f..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-[Desktop Entry]
-Encoding=UTF-8
-Name=i3
-Comment=improved dynamic tiling window manager
-Exec=i3
-Type=XSession
diff --git a/i3.xsession.desktop b/i3.xsession.desktop
new file mode 100644 (file)
index 0000000..abe9e5f
--- /dev/null
@@ -0,0 +1,6 @@
+[Desktop Entry]
+Encoding=UTF-8
+Name=i3
+Comment=improved dynamic tiling window manager
+Exec=i3
+Type=XSession
index 3b6967faf347cdace0c353c9b30680ac032eb82f..212b9dd1d8d55d887de02c2aa6f693a21b4d6b3e 100644 (file)
@@ -9,6 +9,9 @@
 #define COMMON_H_
 
 #include <stdbool.h>
+#include <xcb/xcb.h>
+#include <xcb/xproto.h>
+#include "queue.h"
 
 typedef struct rect_t rect;
 
@@ -23,7 +26,27 @@ struct rect_t {
     int h;
 };
 
-#include "queue.h"
+/* This data structure represents one JSON dictionary, multiple of these make
+ * up one status line. */
+struct status_block {
+    char *full_text;
+
+    char *color;
+
+    /* full_text, but converted to UCS-2. This variable is only temporarily
+     * used in refresh_statusline(). */
+    xcb_char2b_t *ucs2_full_text;
+    size_t glyph_count_full_text;
+
+    /* The amount of pixels necessary to render this block. This variable is
+     * only temporarily used in refresh_statusline(). */
+    uint32_t width;
+
+    TAILQ_ENTRY(status_block) blocks;
+};
+
+TAILQ_HEAD(statusline_head, status_block) statusline_head;
+
 #include "child.h"
 #include "ipc.h"
 #include "outputs.h"
@@ -31,7 +54,6 @@ struct rect_t {
 #include "workspaces.h"
 #include "trayclients.h"
 #include "xcb.h"
-#include "ucs2_to_utf8.h"
 #include "config.h"
 #include "libi3.h"
 
index 1015cc5f2c0fc66d81e0331bc7252bc7fa5282fe..4f6e8858f2f8798809fb17e3198d22e4f59cef56 100644 (file)
@@ -20,6 +20,7 @@ typedef enum {
 
 typedef struct config_t {
     int          hide_on_modifier;
+    int          modifier;
     position_t   position;
     int          verbose;
     struct xcb_color_strings_t colors;
diff --git a/i3bar/include/ucs2_to_utf8.h b/i3bar/include/ucs2_to_utf8.h
deleted file mode 100644 (file)
index a77ed20..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * 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 8067a19382919a24805a0018c5ac2ec35d741ec9..c5a507877c9444c744dd3faaf163633bd4b0ab30 100644 (file)
@@ -30,12 +30,16 @@ struct xcb_color_strings_t {
     char *bar_bg;
     char *active_ws_fg;
     char *active_ws_bg;
+    char *active_ws_border;
     char *inactive_ws_fg;
     char *inactive_ws_bg;
+    char *inactive_ws_border;
     char *focus_ws_bg;
     char *focus_ws_fg;
+    char *focus_ws_border;
     char *urgent_ws_bg;
     char *urgent_ws_fg;
+    char *urgent_ws_border;
 };
 
 typedef struct xcb_colors_t xcb_colors_t;
@@ -103,12 +107,4 @@ void draw_bars();
  */
 void redraw_bars();
 
-/*
- * Predicts the length of text based on cached data.
- * The string has to be encoded in ucs2 and glyph_len has to be the length
- * of the string (in glyphs).
- *
- */
-uint32_t predict_text_extents(xcb_char2b_t *text, uint32_t length);
-
 #endif
index e294fb9abe6886424a3efb029ed8a7226e4575f9..c98befba3a1ca694e24f6fcbc7b40bcf33361e2b 100644 (file)
@@ -18,6 +18,9 @@
 #include <errno.h>
 #include <err.h>
 #include <ev.h>
+#include <yajl/yajl_common.h>
+#include <yajl/yajl_parse.h>
+#include <yajl/yajl_version.h>
 
 #include "common.h"
 
@@ -28,7 +31,25 @@ pid_t child_pid;
 ev_io    *stdin_io;
 ev_child *child_sig;
 
+/* JSON parser for stdin */
+bool first_line = true;
+bool plaintext = false;
+yajl_callbacks callbacks;
+yajl_handle parser;
+
+typedef struct parser_ctx {
+    /* A copy of the last JSON map key. */
+    char *last_map_key;
+
+    /* The current block. Will be filled, then copied and put into the list of
+     * blocks. */
+    struct status_block block;
+} parser_ctx;
+
+parser_ctx parser_context;
+
 /* The buffer statusline points to */
+struct statusline_head statusline_head = TAILQ_HEAD_INITIALIZER(statusline_head);
 char *statusline_buffer = NULL;
 
 /*
@@ -50,6 +71,78 @@ void cleanup() {
     }
 }
 
+/*
+ * The start of a new array is the start of a new status line, so we clear all
+ * previous entries.
+ *
+ */
+static int stdin_start_array(void *context) {
+    struct status_block *first;
+    while (!TAILQ_EMPTY(&statusline_head)) {
+        first = TAILQ_FIRST(&statusline_head);
+        FREE(first->full_text);
+        FREE(first->color);
+        TAILQ_REMOVE(&statusline_head, first, blocks);
+        free(first);
+    }
+    return 1;
+}
+
+/*
+ * The start of a map is the start of a single block of the status line.
+ *
+ */
+static int stdin_start_map(void *context) {
+    parser_ctx *ctx = context;
+    memset(&(ctx->block), '\0', sizeof(struct status_block));
+    return 1;
+}
+
+#if YAJL_MAJOR >= 2
+static int stdin_map_key(void *context, const unsigned char *key, size_t len) {
+#else
+static int stdin_map_key(void *context, const unsigned char *key, unsigned int len) {
+#endif
+    parser_ctx *ctx = context;
+    FREE(ctx->last_map_key);
+    sasprintf(&(ctx->last_map_key), "%.*s", len, key);
+    return 1;
+}
+
+#if YAJL_MAJOR >= 2
+static int stdin_string(void *context, const unsigned char *val, size_t len) {
+#else
+static int stdin_string(void *context, const unsigned char *val, unsigned int len) {
+#endif
+    parser_ctx *ctx = context;
+    if (strcasecmp(ctx->last_map_key, "full_text") == 0) {
+        sasprintf(&(ctx->block.full_text), "%.*s", len, val);
+    }
+    if (strcasecmp(ctx->last_map_key, "color") == 0) {
+        sasprintf(&(ctx->block.color), "%.*s", len, val);
+    }
+    return 1;
+}
+
+static int stdin_end_map(void *context) {
+    parser_ctx *ctx = context;
+    struct status_block *new_block = smalloc(sizeof(struct status_block));
+    memcpy(new_block, &(ctx->block), sizeof(struct status_block));
+    TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
+    return 1;
+}
+
+static int stdin_end_array(void *context) {
+    DLOG("dumping statusline:\n");
+    struct status_block *current;
+    TAILQ_FOREACH(current, &statusline_head, blocks) {
+        DLOG("full_text = %s\n", current->full_text);
+        DLOG("color = %s\n", current->color);
+    }
+    DLOG("end of dump\n");
+    return 1;
+}
+
 /*
  * Callbalk for stdin. We read a line from stdin and store the result
  * in statusline
@@ -60,25 +153,19 @@ 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 = smalloc(buffer_len);
+    unsigned char *buffer = smalloc(buffer_len);
     buffer[0] = '\0';
     while(1) {
         n = read(fd, buffer + rec, buffer_len - rec);
         if (n == -1) {
             if (errno == EAGAIN) {
-                /* remove trailing newline and finish up */
-                buffer[rec-1] = '\0';
+                /* finish up */
                 break;
             }
             ELOG("read() failed!: %s\n", strerror(errno));
             exit(EXIT_FAILURE);
         }
         if (n == 0) {
-            if (rec != 0) {
-                /* remove trailing newline and finish up */
-                buffer[rec-1] = '\0';
-            }
-
             /* end of file, kill the watcher */
             ELOG("stdin: received EOF\n");
             cleanup();
@@ -96,13 +183,41 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
         FREE(buffer);
         return;
     }
-    FREE(statusline_buffer);
-    statusline = statusline_buffer = buffer;
-    for (n = 0; buffer[n] != '\0'; ++n) {
-        if (buffer[n] == '\n')
-            statusline = &buffer[n + 1];
+
+    unsigned char *json_input = buffer;
+    if (first_line) {
+        DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
+        /* Detect whether this is JSON or plain text. */
+        plaintext = (strncasecmp((char*)buffer, "{\"version\":", strlen("{\"version\":")) != 0);
+        if (plaintext) {
+            /* In case of plaintext, we just add a single block and change its
+             * full_text pointer later. */
+            struct status_block *new_block = scalloc(sizeof(struct status_block));
+            TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
+        } else {
+            /* At the moment, we don’t care for the version. This might change
+             * in the future, but for now, we just discard it. */
+            while (*json_input != '\n' && *json_input != '\0') {
+                json_input++;
+                rec--;
+            }
+        }
+        first_line = false;
+    }
+    if (!plaintext) {
+        yajl_status status = yajl_parse(parser, json_input, rec);
+        if (status != yajl_status_ok) {
+            fprintf(stderr, "[i3bar] Could not parse JSON input: %.*s\n",
+                    rec, json_input);
+        }
+        free(buffer);
+    } else {
+        struct status_block *first = TAILQ_FIRST(&statusline_head);
+        /* Remove the trailing newline and terminate the string at the same
+         * time. */
+        buffer[rec-1] = '\0';
+        first->full_text = (char*)buffer;
     }
-    DLOG("%s\n", statusline);
     draw_bars();
 }
 
@@ -126,6 +241,22 @@ void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
  *
  */
 void start_child(char *command) {
+    /* Allocate a yajl parser which will be used to parse stdin. */
+    memset(&callbacks, '\0', sizeof(yajl_callbacks));
+    callbacks.yajl_map_key = stdin_map_key;
+    callbacks.yajl_string = stdin_string;
+    callbacks.yajl_start_array = stdin_start_array;
+    callbacks.yajl_end_array = stdin_end_array;
+    callbacks.yajl_start_map = stdin_start_map;
+    callbacks.yajl_end_map = stdin_end_map;
+#if YAJL_MAJOR < 2
+    yajl_parser_config parse_conf = { 0, 0 };
+
+    parser = yajl_alloc(&callbacks, &parse_conf, NULL, (void*)&parser_context);
+#else
+    parser = yajl_alloc(&callbacks, NULL, &parser_context);
+#endif
+
     child_pid = 0;
     if (command != NULL) {
         int fd[2];
index 5f3338a6eb9e5aee15fd803929587d4edd13756f..69355b69b916c8bf75fafdae32da2ee3e62a9467 100644 (file)
@@ -15,6 +15,8 @@
 #include <yajl/yajl_parse.h>
 #include <yajl/yajl_version.h>
 
+#include <X11/Xlib.h>
+
 #include "common.h"
 
 static char *cur_key;
@@ -65,8 +67,8 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
 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"))
+    /* The id and socket_path are ignored, we already know them. */
+    if (!strcmp(cur_key, "id") || !strcmp(cur_key, "socket_path"))
         return 1;
 
     if (!strcmp(cur_key, "mode")) {
@@ -75,6 +77,41 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
         return 1;
     }
 
+    if (!strcmp(cur_key, "modifier")) {
+        DLOG("modifier = %.*s\n", len, val);
+        if (len == 5 && !strncmp((const char*)val, "shift", strlen("shift"))) {
+            config.modifier = ShiftMask;
+            return 1;
+        }
+        if (len == 4 && !strncmp((const char*)val, "ctrl", strlen("ctrl"))) {
+            config.modifier = ControlMask;
+            return 1;
+        }
+        if (len == 4 && !strncmp((const char*)val, "Mod", strlen("Mod"))) {
+            switch (val[3]) {
+                case '1':
+                    config.modifier = Mod1Mask;
+                    return 1;
+                case '2':
+                    config.modifier = Mod2Mask;
+                    return 1;
+                case '3':
+                    config.modifier = Mod3Mask;
+                    return 1;
+                /*
+                case '4':
+                    config.modifier = Mod4Mask;
+                    return 1;
+                */
+                case '5':
+                    config.modifier = Mod5Mask;
+                    return 1;
+            }
+        }
+        config.modifier = Mod4Mask;
+        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);
@@ -124,14 +161,18 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
 
     COLOR(statusline, bar_fg);
     COLOR(background, bar_bg);
-    COLOR(focused_workspace_text, focus_ws_fg);
+    COLOR(focused_workspace_border, focus_ws_border);
     COLOR(focused_workspace_bg, focus_ws_bg);
-    COLOR(active_workspace_text, active_ws_fg);
+    COLOR(focused_workspace_text, focus_ws_fg);
+    COLOR(active_workspace_border, active_ws_border);
     COLOR(active_workspace_bg, active_ws_bg);
-    COLOR(inactive_workspace_text, inactive_ws_fg);
+    COLOR(active_workspace_text, active_ws_fg);
+    COLOR(inactive_workspace_border, inactive_ws_border);
     COLOR(inactive_workspace_bg, inactive_ws_bg);
-    COLOR(urgent_workspace_text, urgent_ws_fg);
+    COLOR(inactive_workspace_text, inactive_ws_fg);
+    COLOR(urgent_workspace_border, urgent_ws_border);
     COLOR(urgent_workspace_bg, urgent_ws_bg);
+    COLOR(urgent_workspace_text, urgent_ws_fg);
 
     printf("got unexpected string %.*s for cur_key = %s\n", len, val, cur_key);
 
@@ -221,12 +262,16 @@ void free_colors(struct xcb_color_strings_t *colors) {
     FREE_COLOR(bar_bg);
     FREE_COLOR(active_ws_fg);
     FREE_COLOR(active_ws_bg);
+    FREE_COLOR(active_ws_border);
     FREE_COLOR(inactive_ws_fg);
     FREE_COLOR(inactive_ws_bg);
+    FREE_COLOR(inactive_ws_border);
     FREE_COLOR(urgent_ws_fg);
     FREE_COLOR(urgent_ws_bg);
+    FREE_COLOR(urgent_ws_border);
     FREE_COLOR(focus_ws_fg);
     FREE_COLOR(focus_ws_bg);
+    FREE_COLOR(focus_ws_border);
 #undef FREE_COLOR
 }
 
diff --git a/i3bar/src/ucs2_to_utf8.c b/i3bar/src/ucs2_to_utf8.c
deleted file mode 100644 (file)
index 642a72f..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.
- */
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <err.h>
-#include <iconv.h>
-
-#include "libi3.h"
-
-static iconv_t conversion_descriptor = 0;
-static iconv_t conversion_descriptor2 = 0;
-
-/*
- * Returns the input string, but converted from UCS-2 to UTF-8. Memory will be
- * allocated, thus the caller has to free the output.
- *
- */
-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 = 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) {
-            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");
-        free(buffer);
-        return NULL;
-    }
-
-    return buffer;
-}
-
-/*
- * Converts the given string to UCS-2 big endian for use with
- * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
- * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
- * returned. It has to be freed when done.
- *
- */
-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 = 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) {
-            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");
-        free(buffer);
-        if (real_strlen != NULL)
-            *real_strlen = 0;
-        return NULL;
-    }
-
-    if (real_strlen != NULL)
-        *real_strlen = ((buffer_size - output_size) / 2) - 1;
-
-    return buffer;
-}
index 7cfbeffd4e3e4ba912250611392bc7b5260021af..5df1899f33a408f4a8429a52acaa3a2b060c8f7a 100644 (file)
@@ -119,13 +119,13 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, unsigne
             params->workspaces_walk->name[len] = '\0';
 
             /* Convert the name to ucs2, save its length in glyphs and calculate its rendered width */
-            int ucs2_len;
+            size_t ucs2_len;
             xcb_char2b_t *ucs2_name = (xcb_char2b_t*) convert_utf8_to_ucs2(params->workspaces_walk->name, &ucs2_len);
             params->workspaces_walk->ucs2_name = ucs2_name;
             params->workspaces_walk->name_glyphs = ucs2_len;
             params->workspaces_walk->name_width =
-                predict_text_extents(params->workspaces_walk->ucs2_name,
-                params->workspaces_walk->name_glyphs);
+                predict_text_width((char *)params->workspaces_walk->ucs2_name,
+                params->workspaces_walk->name_glyphs, true);
 
             DLOG("Got Workspace %s, name_width: %d, glyphs: %d\n",
                  params->workspaces_walk->name,
index afcfaa397420791d68f21928a3f87646ccd4b0ea..4d4dd3848c114807e0e78d0b7dd96f737bb782c0 100644 (file)
@@ -49,12 +49,12 @@ xcb_connection_t *xcb_connection;
 int              screen;
 xcb_screen_t     *xcb_screen;
 xcb_window_t     xcb_root;
-xcb_font_t       xcb_font;
 
-/* We need to cache some data to speed up text-width-prediction */
-xcb_query_font_reply_t *font_info;
-int                    font_height;
-xcb_charinfo_t         *font_table;
+/* This is needed for integration with libi3 */
+xcb_connection_t *conn;
+
+/* The font we'll use */
+static i3Font font;
 
 /* These are only relevant for XKB, which we only need for grabbing modifiers */
 Display          *xkb_dpy;
@@ -80,12 +80,16 @@ struct xcb_colors_t {
     uint32_t bar_bg;
     uint32_t active_ws_fg;
     uint32_t active_ws_bg;
+    uint32_t active_ws_border;
     uint32_t inactive_ws_fg;
     uint32_t inactive_ws_bg;
+    uint32_t inactive_ws_border;
     uint32_t urgent_ws_bg;
     uint32_t urgent_ws_fg;
+    uint32_t urgent_ws_border;
     uint32_t focus_ws_bg;
     uint32_t focus_ws_fg;
+    uint32_t focus_ws_border;
 };
 struct xcb_colors_t colors;
 
@@ -100,100 +104,56 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
     return 0;
 }
 
-/*
- * Predicts the length of text based on cached data.
- * The string has to be encoded in ucs2 and glyph_len has to be the length
- * of the string (in glyphs).
- *
- */
-uint32_t predict_text_extents(xcb_char2b_t *text, uint32_t length) {
-    /* If we don't have per-character data, return the maximum width */
-    if (font_table == NULL) {
-        return (font_info->max_bounds.character_width * length);
-    }
-
-    uint32_t width = 0;
-    uint32_t i;
-
-    for (i = 0; i < length; i++) {
-        xcb_charinfo_t *info;
-        int row = text[i].byte1;
-        int col = text[i].byte2;
-
-        if (row < font_info->min_byte1 || row > font_info->max_byte1 ||
-            col < font_info->min_char_or_byte2 || col > font_info->max_char_or_byte2) {
-            continue;
-        }
-
-        /* Don't you ask me, how this one works… */
-        info = &font_table[((row - font_info->min_byte1) *
-                            (font_info->max_char_or_byte2 - font_info->min_char_or_byte2 + 1)) +
-                           (col - font_info->min_char_or_byte2)];
-
-        if (info->character_width != 0 ||
-            (info->right_side_bearing |
-             info->left_side_bearing |
-             info->ascent |
-             info->descent) != 0) {
-            width += info->character_width;
-        }
-    }
-
-    return width;
-}
-
-/*
- * Draws text given in UCS-2-encoding to a given drawable and position
- *
- */
-void draw_text(xcb_drawable_t drawable, xcb_gcontext_t ctx, int16_t x, int16_t y,
-               xcb_char2b_t *text, uint32_t glyph_count) {
-    int offset = 0;
-    int16_t pos_x = x;
-    int16_t font_ascent = font_info->font_ascent;
-
-    while (glyph_count > 0) {
-        uint8_t chunk_size = MIN(255, glyph_count);
-        uint32_t chunk_width = predict_text_extents(text + offset, chunk_size);
-
-        xcb_image_text_16(xcb_connection,
-                          chunk_size,
-                          drawable,
-                          ctx,
-                          pos_x, y + font_ascent,
-                          text + offset);
-
-        offset += chunk_size;
-        pos_x += chunk_width;
-        glyph_count -= chunk_size;
-    }
-}
-
 /*
  * Redraws the statusline to the buffer
  *
  */
 void refresh_statusline() {
-    int glyph_count;
+    struct status_block *block;
 
-    if (statusline == NULL) {
-        return;
+    uint32_t old_statusline_width = statusline_width;
+    statusline_width = 0;
+
+    /* Convert all blocks from UTF-8 to UCS-2 and predict the text width (in
+     * pixels). */
+    TAILQ_FOREACH(block, &statusline_head, blocks) {
+        block->ucs2_full_text = (xcb_char2b_t*)convert_utf8_to_ucs2(block->full_text, &(block->glyph_count_full_text));
+        block->width = predict_text_width((char*)block->ucs2_full_text, block->glyph_count_full_text, true);
+        /* If this is not the last block, add some pixels for a separator. */
+        if (TAILQ_NEXT(block, blocks) != NULL)
+            block->width += 9;
+        statusline_width += block->width;
     }
 
-    xcb_char2b_t *text = (xcb_char2b_t*) convert_utf8_to_ucs2(statusline, &glyph_count);
-    uint32_t old_statusline_width = statusline_width;
-    statusline_width = predict_text_extents(text, glyph_count);
     /* If the statusline is bigger than our screen we need to make sure that
      * the pixmap provides enough space, so re-allocate if the width grew */
     if (statusline_width > xcb_screen->width_in_pixels &&
         statusline_width > old_statusline_width)
         realloc_sl_buffer();
 
-    xcb_rectangle_t rect = { 0, 0, xcb_screen->width_in_pixels, font_height };
+    /* Clear the statusline pixmap. */
+    xcb_rectangle_t rect = { 0, 0, xcb_screen->width_in_pixels, font.height };
     xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
-    draw_text(statusline_pm, statusline_ctx, 0, 0, text, glyph_count);
 
-    FREE(text);
+    /* Draw the text of each block. */
+    uint32_t x = 0;
+    TAILQ_FOREACH(block, &statusline_head, blocks) {
+        uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
+        set_font_colors(statusline_ctx, colorpixel, colors.bar_bg);
+        draw_text((char*)block->ucs2_full_text, block->glyph_count_full_text,
+                  true, statusline_pm, statusline_ctx, x, 0, block->width);
+        x += block->width;
+
+        if (TAILQ_NEXT(block, blocks) != NULL) {
+            /* This is not the last block, draw a separator. */
+            set_font_colors(statusline_ctx, get_colorpixel("#666666"), colors.bar_bg);
+            xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm,
+                          statusline_ctx, 2,
+                          (xcb_point_t[]){ { x - 5, 2 }, { x - 5, font.height - 2 } });
+        }
+
+        FREE(block->ucs2_full_text);
+    }
 }
 
 /*
@@ -243,9 +203,9 @@ void unhide_bars() {
         values[0] = walk->rect.x;
         if (config.position == POS_TOP)
             values[1] = walk->rect.y;
-        else values[1] = walk->rect.y + walk->rect.h - font_height - 6;
+        else values[1] = walk->rect.y + walk->rect.h - font.height - 6;
         values[2] = walk->rect.w;
-        values[3] = font_height + 6;
+        values[3] = font.height + 6;
         values[4] = XCB_STACK_MODE_ABOVE;
         DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
         cookie = xcb_configure_window_checked(xcb_connection,
@@ -273,12 +233,16 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
     PARSE_COLOR(bar_bg, "#000000");
     PARSE_COLOR(active_ws_fg, "#FFFFFF");
     PARSE_COLOR(active_ws_bg, "#333333");
+    PARSE_COLOR(active_ws_border, "#333333");
     PARSE_COLOR(inactive_ws_fg, "#888888");
     PARSE_COLOR(inactive_ws_bg, "#222222");
+    PARSE_COLOR(inactive_ws_border, "#333333");
     PARSE_COLOR(urgent_ws_fg, "#FFFFFF");
     PARSE_COLOR(urgent_ws_bg, "#900000");
+    PARSE_COLOR(urgent_ws_border, "#2f343a");
     PARSE_COLOR(focus_ws_fg, "#FFFFFF");
     PARSE_COLOR(focus_ws_bg, "#285577");
+    PARSE_COLOR(focus_ws_border, "#4c7899");
 #undef PARSE_COLOR
 }
 
@@ -327,10 +291,10 @@ void handle_button(xcb_button_press_event_t *event) {
              * and set cur_ws accordingly */
             TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
                 DLOG("x = %d\n", x);
-                if (x < cur_ws->name_width + 10) {
+                if (x >= 0 && x < cur_ws->name_width + 10) {
                     break;
                 }
-                x -= cur_ws->name_width + 10;
+                x -= cur_ws->name_width + 11;
             }
             if (cur_ws == NULL) {
                 return;
@@ -354,10 +318,36 @@ void handle_button(xcb_button_press_event_t *event) {
             break;
     }
 
-    const size_t len = strlen(cur_ws->name) + strlen("workspace \"\"") + 1;
-    char buffer[len];
-    snprintf(buffer, len, "workspace \"%s\"", cur_ws->name);
+    /* To properly handle workspace names with double quotes in them, we need
+     * to escape the double quotes. Unfortunately, that’s rather ugly in C: We
+     * first count the number of double quotes, then we allocate a large enough
+     * buffer, then we copy character by character. */
+    int num_quotes = 0;
+    size_t namelen = 0;
+    for (char *walk = cur_ws->name; *walk != '\0'; walk++) {
+        if (*walk == '"')
+            num_quotes++;
+        /* While we’re looping through the name anyway, we can save one
+         * strlen(). */
+        namelen++;
+    }
+
+    const size_t len = namelen + strlen("workspace \"\"") + 1;
+    char *buffer = scalloc(len+num_quotes);
+    strncpy(buffer, "workspace \"", strlen("workspace \""));
+    int inpos, outpos;
+    for (inpos = 0, outpos = strlen("workspace \"");
+         inpos < namelen;
+         inpos++, outpos++) {
+        if (cur_ws->name[inpos] == '"') {
+            buffer[outpos] = '\\';
+            outpos++;
+        }
+        buffer[outpos] = cur_ws->name[inpos];
+    }
+    buffer[outpos] = '"';
     i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer);
+    free(buffer);
 }
 
 /*
@@ -379,8 +369,8 @@ static void configure_trayclients() {
             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));
+                 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,
@@ -423,17 +413,24 @@ static void handle_client_message(xcb_client_message_event_t* event) {
             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_generic_error_t *error;
+            xembedc = xcb_get_property(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);
+                                                                       &error);
+            if (error != NULL) {
+                ELOG("Error getting _XEMBED_INFO property: error_code %d\n",
+                     error->error_code);
+                free(error);
+                return;
+            }
             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);
@@ -466,7 +463,7 @@ static void handle_client_message(xcb_client_message_event_t* event) {
             xcb_reparent_window(xcb_connection,
                                 client,
                                 output->bar,
-                                output->rect.w - font_height - 2,
+                                output->rect.w - font.height - 2,
                                 2);
             /* We reconfigure the window to use a reasonable size. The systray
              * specification explicitly says:
@@ -474,8 +471,8 @@ static void handle_client_message(xcb_client_message_event_t* event) {
              *   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;
+            values[0] = font.height;
+            values[1] = font.height;
             xcb_configure_window(xcb_connection,
                                  client,
                                  mask,
@@ -650,10 +647,10 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
                 continue;
 
             xcb_rectangle_t rect;
-            rect.x = output->rect.w - (clients * (font_height + 2));
+            rect.x = output->rect.w - (clients * (font.height + 2));
             rect.y = 2;
-            rect.width = font_height;
-            rect.height = font_height;
+            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);
@@ -752,19 +749,47 @@ void xkb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
         }
 
         unsigned int mods = ev.state.mods;
-        modstate = mods & Mod4Mask;
+        modstate = mods & config.modifier;
     }
 
+#define DLOGMOD(modmask, status, barfunc) \
+    do { \
+        switch (modmask) { \
+            case ShiftMask: \
+                DLOG("ShiftMask got " #status "!\n"); \
+                break; \
+            case ControlMask: \
+                DLOG("ControlMask got " #status "!\n"); \
+                break; \
+            case Mod1Mask: \
+                DLOG("Mod1Mask got " #status "!\n"); \
+                break; \
+            case Mod2Mask: \
+                DLOG("Mod2Mask got " #status "!\n"); \
+                break; \
+            case Mod3Mask: \
+                DLOG("Mod3Mask got " #status "!\n"); \
+                break; \
+            case Mod4Mask: \
+                DLOG("Mod4Mask got " #status "!\n"); \
+                break; \
+            case Mod5Mask: \
+                DLOG("Mod5Mask got " #status "!\n"); \
+                break; \
+        } \
+        barfunc(); \
+    } while (0)
+
     if (modstate != mod_pressed) {
         if (modstate == 0) {
-            DLOG("Mod4 got released!\n");
-            hide_bars();
+            DLOGMOD(config.modifier, released, hide_bars);
         } else {
-            DLOG("Mod4 got pressed!\n");
-            unhide_bars();
+            DLOGMOD(config.modifier, pressed, unhide_bars);
         }
         mod_pressed = modstate;
     }
+
+#undef DLOGMOD
 }
 
 /*
@@ -779,6 +804,7 @@ char *init_xcb_early() {
         ELOG("Cannot open display\n");
         exit(EXIT_FAILURE);
     }
+    conn = xcb_connection;
     DLOG("Connected to xcb\n");
 
     /* We have to request the atoms we need */
@@ -800,14 +826,12 @@ char *init_xcb_early() {
                                                                mask,
                                                                vals);
 
-    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,
                                                             statusline_ctx,
                                                             xcb_root,
-                                                            mask,
-                                                            vals);
+                                                            0,
+                                                            NULL);
 
     statusline_pm = xcb_generate_id(xcb_connection);
     xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
@@ -870,29 +894,13 @@ char *init_xcb_early() {
  *
  */
 void init_xcb_late(char *fontname) {
-    if (fontname == NULL) {
-        /* XXX: font fallback to 'misc' like i3 does it would be good. */
+    if (fontname == NULL)
         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 });
+    /* Load the font */
+    font = load_font(fontname, true);
+    set_font(&font);
+    DLOG("Calculated Font-height: %d\n", font.height);
 
     xcb_flush(xcb_connection);
 
@@ -937,25 +945,6 @@ void init_xcb_late(char *fontname) {
         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,
-                                     NULL);
-
-    if (xcb_request_failed(open_font_cookie, "Could not open font")) {
-        exit(EXIT_FAILURE);
-    }
-
-    font_height = font_info->font_ascent + font_info->font_descent;
-
-    if (xcb_query_font_char_infos_length(font_info) == 0) {
-        font_table = NULL;
-    } else {
-        font_table = xcb_query_font_char_infos(font_info);
-    }
-
-    DLOG("Calculated Font-height: %d\n", font_height);
 }
 
 /*
@@ -1085,7 +1074,6 @@ void clean_xcb() {
     FREE(xcb_chk);
     FREE(xcb_prep);
     FREE(xcb_io);
-    FREE(font_info);
 }
 
 /*
@@ -1138,7 +1126,7 @@ void realloc_sl_buffer() {
                                                                xcb_screen->height_in_pixels);
 
     uint32_t mask = XCB_GC_FOREGROUND;
-    uint32_t vals[3] = { colors.bar_bg, colors.bar_bg, xcb_font };
+    uint32_t vals[2] = { colors.bar_bg, colors.bar_bg };
     xcb_free_gc(xcb_connection, statusline_clear);
     statusline_clear = xcb_generate_id(xcb_connection);
     xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection,
@@ -1147,7 +1135,7 @@ void realloc_sl_buffer() {
                                                                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_free_gc(xcb_connection, statusline_ctx);
@@ -1208,8 +1196,8 @@ void reconfig_windows() {
                                                                      xcb_screen->root_depth,
                                                                      walk->bar,
                                                                      xcb_root,
-                                                                     walk->rect.x, walk->rect.y + walk->rect.h - font_height - 6,
-                                                                     walk->rect.w, font_height + 6,
+                                                                     walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6,
+                                                                     walk->rect.w, font.height + 6,
                                                                      1,
                                                                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
                                                                      xcb_screen->root_visual,
@@ -1283,12 +1271,12 @@ void reconfig_windows() {
                 case POS_NONE:
                     break;
                 case POS_TOP:
-                    strut_partial.top = font_height + 6;
+                    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 POS_BOT:
-                    strut_partial.bottom = font_height + 6;
+                    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;
                     break;
@@ -1305,13 +1293,11 @@ void reconfig_windows() {
             /* We also want a graphics-context for the bars (it defines the properties
              * with which we draw to them) */
             walk->bargc = xcb_generate_id(xcb_connection);
-            mask = XCB_GC_FONT;
-            values[0] = xcb_font;
             xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection,
                                                                 walk->bargc,
                                                                 walk->bar,
-                                                                mask,
-                                                                values);
+                                                                0,
+                                                                NULL);
 
             /* We finally map the bar (display it on screen), unless the modifier-switch is on */
             xcb_void_cookie_t map_cookie;
@@ -1344,9 +1330,9 @@ void reconfig_windows() {
                    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;
+            values[1] = walk->rect.y + walk->rect.h - font.height - 6;
             values[2] = walk->rect.w;
-            values[3] = font_height + 6;
+            values[3] = font.height + 6;
             values[4] = XCB_STACK_MODE_ABOVE;
 
             DLOG("Destroying buffer for output %s", walk->name);
@@ -1402,14 +1388,14 @@ void draw_bars() {
                       outputs_walk->bargc,
                       XCB_GC_FOREGROUND,
                       &color);
-        xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font_height + 6 };
+        xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font.height + 6 };
         xcb_poly_fill_rectangle(xcb_connection,
                                 outputs_walk->buffer,
                                 outputs_walk->bargc,
                                 1,
                                 &rect);
 
-        if (statusline != NULL) {
+        if (!TAILQ_EMPTY(&statusline_head)) {
             DLOG("Printing statusline!\n");
 
             /* Luckily we already prepared a seperate pixmap containing the rendered
@@ -1423,7 +1409,7 @@ void draw_bars() {
                 /* 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;
+                traypx += font.height + 2;
             }
             /* Add 2px of padding if there are any tray icons */
             if (traypx > 0)
@@ -1434,7 +1420,7 @@ void draw_bars() {
                           outputs_walk->bargc,
                           MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0,
                           MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3,
-                          MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font_height);
+                          MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height);
         }
 
         if (config.disable_ws) {
@@ -1443,48 +1429,56 @@ void draw_bars() {
 
         i3_ws *ws_walk;
         TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
-            DLOG("Drawing Button for WS %s at x = %d\n", ws_walk->name, i);
+            DLOG("Drawing Button for WS %s at x = %d, len = %d\n", ws_walk->name, i, ws_walk->name_width);
             uint32_t fg_color = colors.inactive_ws_fg;
             uint32_t bg_color = colors.inactive_ws_bg;
+            uint32_t border_color = colors.inactive_ws_border;
             if (ws_walk->visible) {
                 if (!ws_walk->focused) {
                     fg_color = colors.active_ws_fg;
                     bg_color = colors.active_ws_bg;
+                    border_color = colors.active_ws_border;
                 } else {
                     fg_color = colors.focus_ws_fg;
                     bg_color = colors.focus_ws_bg;
+                    border_color = colors.focus_ws_border;
                 }
             }
             if (ws_walk->urgent) {
                 DLOG("WS %s is urgent!\n", ws_walk->name);
                 fg_color = colors.urgent_ws_fg;
                 bg_color = colors.urgent_ws_bg;
+                border_color = colors.urgent_ws_border;
                 /* The urgent-hint should get noticed, so we unhide the bars shortly */
                 unhide_bars();
             }
             uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
+            uint32_t vals_border[] = { border_color, border_color };
+            xcb_change_gc(xcb_connection,
+                          outputs_walk->bargc,
+                          mask,
+                          vals_border);
+            xcb_rectangle_t rect_border = { i, 0, ws_walk->name_width + 10, font.height + 4 };
+            xcb_poly_fill_rectangle(xcb_connection,
+                                    outputs_walk->buffer,
+                                    outputs_walk->bargc,
+                                    1,
+                                    &rect_border);
             uint32_t vals[] = { bg_color, bg_color };
             xcb_change_gc(xcb_connection,
                           outputs_walk->bargc,
                           mask,
                           vals);
-            xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font_height + 4 };
+            xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font.height + 2 };
             xcb_poly_fill_rectangle(xcb_connection,
                                     outputs_walk->buffer,
                                     outputs_walk->bargc,
                                     1,
                                     &rect);
-            xcb_change_gc(xcb_connection,
-                          outputs_walk->bargc,
-                          XCB_GC_FOREGROUND,
-                          &fg_color);
-            xcb_image_text_16(xcb_connection,
-                              ws_walk->name_glyphs,
-                              outputs_walk->buffer,
-                              outputs_walk->bargc,
-                              i + 5, font_info->font_ascent + 2,
-                              ws_walk->ucs2_name);
-            i += 10 + ws_walk->name_width;
+            set_font_colors(outputs_walk->bargc, fg_color, bg_color);
+            draw_text((char*)ws_walk->ucs2_name, ws_walk->name_glyphs, true,
+                    outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, ws_walk->name_width);
+            i += 10 + ws_walk->name_width + 1;
         }
 
         i = 0;
index 8ae4e0a29ac26f8f24cb65a8d1fc1413e458ce43..648b0e0ac40727115d0b5f9e238cd9969a1c71fd 100644 (file)
@@ -72,5 +72,8 @@
 #include "regex.h"
 #include "libi3.h"
 #include "startup.h"
+#include "scratchpad.h"
+#include "commands.h"
+#include "commands_parser.h"
 
 #endif
index f08a90d5585abbbf60292b4dcea27d99c1362ba5..b907f41e9ca2f67f60a87e794a72c02da0acfd9a 100644 (file)
@@ -26,3 +26,4 @@ xmacro(WM_WINDOW_ROLE)
 xmacro(I3_SOCKET_PATH)
 xmacro(I3_CONFIG_PATH)
 xmacro(I3_SYNC)
+xmacro(I3_SHMLOG_PATH)
diff --git a/include/commands.h b/include/commands.h
new file mode 100644 (file)
index 0000000..2b69422
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * commands.c: all command functions (see commands_parser.c)
+ *
+ */
+#ifndef _COMMANDS_H
+#define _COMMANDS_H
+
+#include "commands_parser.h"
+
+/** The beginning of the prototype for every cmd_ function. */
+#define I3_CMD Match *current_match, struct CommandResult *cmd_output
+
+/*
+ * Helper data structure for an operation window (window on which the operation
+ * will be performed). Used to build the TAILQ owindows.
+ *
+ */
+typedef struct owindow {
+    Con *con;
+    TAILQ_ENTRY(owindow) owindows;
+} owindow;
+
+typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
+
+/**
+ * Initializes the specified 'Match' data structure and the initial state of
+ * commands.c for matching target windows of a command.
+ *
+ */
+void cmd_criteria_init(I3_CMD);
+
+/**
+ * A match specification just finished (the closing square bracket was found),
+ * so we filter the list of owindows.
+ *
+ */
+void cmd_criteria_match_windows(I3_CMD);
+
+/**
+ * Interprets a ctype=cvalue pair and adds it to the current match
+ * specification.
+ *
+ */
+void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue);
+
+/**
+ * Implementation of 'move [window|container] [to] workspace
+ * next|prev|next_on_output|prev_on_output'.
+ *
+ */
+void cmd_move_con_to_workspace(I3_CMD, char *which);
+
+/**
+ * Implementation of 'move [window|container] [to] workspace <name>'.
+ *
+ */
+void cmd_move_con_to_workspace_name(I3_CMD, char *name);
+
+/**
+ * Implementation of 'resize grow|shrink <direction> [<px> px] [or <ppt> ppt]'.
+ *
+ */
+void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resize_ppt);
+
+/**
+ * Implementation of 'border normal|none|1pixel|toggle'.
+ *
+ */
+void cmd_border(I3_CMD, char *border_style_str);
+
+/**
+ * Implementation of 'nop <comment>'.
+ *
+ */
+void cmd_nop(I3_CMD, char *comment);
+
+/**
+ * Implementation of 'append_layout <path>'.
+ *
+ */
+void cmd_append_layout(I3_CMD, char *path);
+
+/**
+ * Implementation of 'workspace next|prev|next_on_output|prev_on_output'.
+ *
+ */
+void cmd_workspace(I3_CMD, char *which);
+
+/**
+ * Implementation of 'workspace back_and_forth'.
+ *
+ */
+void cmd_workspace_back_and_forth(I3_CMD);
+
+/**
+ * Implementation of 'workspace <name>'
+ *
+ */
+void cmd_workspace_name(I3_CMD, char *name);
+
+/**
+ * Implementation of 'mark <mark>'
+ *
+ */
+void cmd_mark(I3_CMD, char *mark);
+
+/**
+ * Implementation of 'mode <string>'.
+ *
+ */
+void cmd_mode(I3_CMD, char *mode);
+
+/**
+ * Implementation of 'move [window|container] [to] output <str>'.
+ *
+ */
+void cmd_move_con_to_output(I3_CMD, char *name);
+
+/**
+ * Implementation of 'floating enable|disable|toggle'
+ *
+ */
+void cmd_floating(I3_CMD, char *floating_mode);
+
+/**
+ * Implementation of 'move workspace to [output] <str>'.
+ *
+ */
+void cmd_move_workspace_to_output(I3_CMD, char *name);
+
+/**
+ * Implementation of 'split v|h|vertical|horizontal'.
+ *
+ */
+void cmd_split(I3_CMD, char *direction);
+
+/**
+ * Implementaiton of 'kill [window|client]'.
+ *
+ */
+void cmd_kill(I3_CMD, char *kill_mode_str);
+
+/**
+ * Implementation of 'exec [--no-startup-id] <command>'.
+ *
+ */
+void cmd_exec(I3_CMD, char *nosn, char *command);
+
+/**
+ * Implementation of 'focus left|right|up|down'.
+ *
+ */
+void cmd_focus_direction(I3_CMD, char *direction);
+
+/**
+ * Implementation of 'focus tiling|floating|mode_toggle'.
+ *
+ */
+void cmd_focus_window_mode(I3_CMD, char *window_mode);
+
+/**
+ * Implementation of 'focus parent|child'.
+ *
+ */
+void cmd_focus_level(I3_CMD, char *level);
+
+/**
+ * Implementation of 'focus'.
+ *
+ */
+void cmd_focus(I3_CMD);
+
+/**
+ * Implementation of 'fullscreen [global]'.
+ *
+ */
+void cmd_fullscreen(I3_CMD, char *fullscreen_mode);
+
+/**
+ * Implementation of 'move <direction> [<pixels> [px]]'.
+ *
+ */
+void cmd_move_direction(I3_CMD, char *direction, char *move_px);
+
+/**
+ * Implementation of 'layout default|stacked|stacking|tabbed'.
+ *
+ */
+void cmd_layout(I3_CMD, char *layout_str);
+
+/**
+ * Implementaiton of 'exit'.
+ *
+ */
+void cmd_exit(I3_CMD);
+
+/**
+ * Implementaiton of 'reload'.
+ *
+ */
+void cmd_reload(I3_CMD);
+
+/**
+ * Implementaiton of 'restart'.
+ *
+ */
+void cmd_restart(I3_CMD);
+
+/**
+ * Implementaiton of 'open'.
+ *
+ */
+void cmd_open(I3_CMD);
+
+/**
+ * Implementation of 'focus output <output>'.
+ *
+ */
+void cmd_focus_output(I3_CMD, char *name);
+
+/**
+ * Implementation of 'move scratchpad'.
+ *
+ */
+void cmd_move_scratchpad(I3_CMD);
+
+/**
+ * Implementation of 'scratchpad show'.
+ *
+ */
+void cmd_scratchpad_show(I3_CMD);
+
+#endif
diff --git a/include/commands_parser.h b/include/commands_parser.h
new file mode 100644 (file)
index 0000000..5a4472d
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * commands.c: all command functions (see commands_parser.c)
+ *
+ */
+#ifndef _COMMANDS_PARSER_H
+#define _COMMANDS_PARSER_H
+
+/*
+ * Holds the result of a call to any command. When calling
+ * parse_command("floating enable, border none"), the parser will internally
+ * use a struct CommandResult when calling cmd_floating and cmd_border.
+ * parse_command will also return another struct CommandResult, whose
+ * json_output is set to a map of individual json_outputs and whose
+ * needs_tree_trender is true if any individual needs_tree_render was true.
+ *
+ */
+struct CommandResult {
+    /* The JSON-serialized output of this command. */
+    char *json_output;
+
+    /* Whether the command requires calling tree_render. */
+    bool needs_tree_render;
+};
+
+struct CommandResult *parse_command(const char *input);
+
+#endif
index b4128cafc6a1ca4381d9814e9b3e7543ede5114e..c1705d4f65d1720ba7fd4d1c888c982a0fcc6f81 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 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
@@ -54,6 +54,7 @@ struct Colortriple {
     uint32_t border;
     uint32_t background;
     uint32_t text;
+    uint32_t indicator;
 };
 
 /**
@@ -149,6 +150,12 @@ struct Config {
      * buttons to do things with floating windows (move, resize) */
     uint32_t floating_modifier;
 
+    /** Maximum and minimum dimensions of a floating window */
+    int32_t floating_maximum_width;
+    int32_t floating_maximum_height;
+    int32_t floating_minimum_width;
+    int32_t floating_minimum_height;
+
     /* Color codes are stored here */
     struct config_client {
         uint32_t background;
@@ -198,9 +205,26 @@ struct Barconfig {
     /** Bar display mode (hide unless modifier is pressed or show in dock mode) */
     enum { M_DOCK = 0, M_HIDE = 1 } mode;
 
+    /** Bar modifier (to show bar when in hide mode). */
+    enum {
+        M_NONE = 0,
+        M_CONTROL = 1,
+        M_SHIFT = 2,
+        M_MOD1 = 3,
+        M_MOD2 = 4,
+        M_MOD3 = 5,
+        M_MOD4 = 6,
+        M_MOD5 = 7
+    } modifier;
+
     /** Bar position (bottom by default). */
     enum { P_BOTTOM = 0, P_TOP = 1 } position;
 
+    /** Command that should be run to execute i3bar, give a full path if i3bar is not
+     * in your $PATH.
+     * By default just 'i3bar' is executed. */
+    char *i3bar_command;
+
     /** Command that should be run to get a statusline, for example 'i3status'.
      * Will be passed to the shell. */
     char *status_command;
@@ -220,17 +244,21 @@ struct Barconfig {
         char *background;
         char *statusline;
 
-        char *focused_workspace_text;
+        char *focused_workspace_border;
         char *focused_workspace_bg;
+        char *focused_workspace_text;
 
-        char *active_workspace_text;
+        char *active_workspace_border;
         char *active_workspace_bg;
+        char *active_workspace_text;
 
-        char *inactive_workspace_text;
+        char *inactive_workspace_border;
         char *inactive_workspace_bg;
+        char *inactive_workspace_text;
 
-        char *urgent_workspace_text;
+        char *urgent_workspace_border;
         char *urgent_workspace_bg;
+        char *urgent_workspace_text;
     } colors;
 
     TAILQ_ENTRY(Barconfig) configs;
index 10fc16d25aba594f1a22d932b5a841575062c8d2..6cf3515e64dc362fbfc98febf1e5e24d354e434e 100644 (file)
 #include <xcb/xcb_atom.h>
 #include <stdbool.h>
 #include <pcre.h>
+#include <sys/time.h>
 
 #include "queue.h"
 
 /*
- * To get the big concept: There are helper structures like struct Colorpixel
- * or struct Stack_Window. Everything which is also defined as type (see
+ * To get the big concept: There are helper structures like struct
+ * Workspace_Assignment. Every struct which is also defined as type (see
  * forward definitions) is considered to be a major structure, thus important.
  *
- * Let’s start from the biggest to the smallest:
+ * The following things are all stored in a 'Con', from very high level (the
+ * biggest Cons) to very small (a single window):
  *
- * TODO
+ * 1) X11 root window (as big as all your outputs combined)
+ * 2) output (like LVDS1)
+ * 3) content container, dockarea containers
+ * 4) workspaces
+ * 5) split containers
+ * ... (you can arbitrarily nest split containers)
+ * 6) X11 window containers
  *
  */
 
@@ -119,7 +127,6 @@ struct deco_render_params {
     Rect con_deco_rect;
     uint32_t background;
     bool con_is_leaf;
-    xcb_font_t font;
 };
 
 /**
@@ -178,7 +185,7 @@ struct regex {
 
 /**
  * Holds a keybinding, consisting of a keycode combined with modifiers and the
- * command which is executed as soon as the key is pressed (see src/command.c)
+ * command which is executed as soon as the key is pressed (see src/cfgparse.y)
  *
  */
 struct Binding {
@@ -257,6 +264,11 @@ struct xoutput {
     TAILQ_ENTRY(xoutput) outputs;
 };
 
+/**
+ * A 'Window' is a type which contains an xcb_window_t and all the related
+ * information (hints like _NET_WM_NAME for that window).
+ *
+ */
 struct Window {
     xcb_window_t id;
 
@@ -285,7 +297,7 @@ struct Window {
     char *name_json;
 
     /** The length of the name in glyphs (not bytes) */
-    int name_len;
+    size_t name_len;
 
     /** Whether the application used _NET_WM_NAME */
     bool uses_net_wm_name;
@@ -293,6 +305,9 @@ struct Window {
     /** Whether the application needs to receive WM_TAKE_FOCUS */
     bool needs_take_focus;
 
+    /** When this window was marked urgent. 0 means not urgent */
+    struct timeval urgent;
+
     /** Whether this window accepts focus. We store this inverted so that the
      * default will be 'accepts focus'. */
     bool doesnt_accept_focus;
@@ -309,6 +324,14 @@ struct Window {
     Assignment **ran_assignments;
 };
 
+/**
+ * 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.
+ *
+ */
 struct Match {
     struct regex *title;
     struct regex *application;
@@ -316,6 +339,11 @@ struct Match {
     struct regex *instance;
     struct regex *mark;
     struct regex *role;
+    enum {
+        U_DONTCHECK = -1,
+        U_LATEST = 0,
+        U_OLDEST = 1
+    } urgent;
     enum {
         M_DONTCHECK = -1,
         M_NODOCK = 0,
@@ -338,15 +366,20 @@ struct Match {
      */
     enum { M_HERE = 0, M_ASSIGN_WS, M_BELOW } insert_where;
 
+    /* Whether this match was generated when restarting i3 inplace.
+     * Leads to not setting focus when managing a new window, because the old
+     * focus stack should be restored. */
+    bool restart_mode;
+
     TAILQ_ENTRY(Match) matches;
 };
 
 /**
  * An Assignment makes specific windows go to a specific workspace/output or
  * run a command for that window. With this mechanism, the user can -- for
- * example -- make specific windows floating or assign his browser to workspace
- * "www". Checking if a window is assigned works by comparing the Match data
- * structure with the window (see match_matches_window()).
+ * example -- assign his browser to workspace "www". Checking if a window is
+ * assigned works by comparing the Match data structure with the window (see
+ * match_matches_window()).
  *
  */
 struct Assignment {
@@ -381,6 +414,10 @@ struct Assignment {
     TAILQ_ENTRY(Assignment) assignments;
 };
 
+/**
+ * A 'Con' represents everything from the X11 root window down to a single X11 window.
+ *
+ */
 struct Con {
     bool mapped;
     enum {
@@ -485,6 +522,16 @@ struct Con {
 
     /** callbacks */
     void(*on_remove_child)(Con *);
+
+    enum {
+        SCRATCHPAD_NONE = 0,
+        SCRATCHPAD_FRESH = 1,
+        SCRATCHPAD_CHANGED = 2
+    } scratchpad_state;
+
+    /* The ID of this container before restarting. Necessary to correctly
+     * interpret back-references in the JSON (such as the focus stack). */
+    int old_id;
 };
 
 #endif
index 75b7a9bf1b662d2f11c0c4e613368bb1e1f1ebb0..bd40f16b9eb8e79937d562937fab9fc743394a90 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3.h: global variables that are used all over i3.
  *
  * this before starting any other process, since we set RLIMIT_CORE to
  * RLIM_INFINITY for i3 debugging versions. */
 extern struct rlimit original_rlimit_core;
+/** Whether this version of i3 is a debug build or a release build. */
+extern bool debug_build;
+/** The number of file descriptors passed via socket activation. */
+extern int listen_fds;
 extern xcb_connection_t *conn;
 extern int conn_screen;
 /** The last timestamp we got from X11 (timestamps are included in some events
@@ -46,7 +50,14 @@ extern TAILQ_HEAD(ws_assignments_head, Workspace_Assignment) ws_assignments;
 extern TAILQ_HEAD(assignments_head, Assignment) assignments;
 extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins;
 extern xcb_screen_t *root_screen;
+
+/* Color depth, visual id and colormap to use when creating windows and
+ * pixmaps. Will use 32 bit depth and an appropriate visual, if available,
+ * otherwise the root window’s default (usually 24 bit TrueColor). */
 extern uint8_t root_depth;
+extern xcb_visualid_t visual_id;
+extern xcb_colormap_t colormap;
+
 extern bool xcursor_supported, xkb_supported;
 extern xcb_window_t root;
 extern struct ev_loop *main_loop;
index 71fba764540f41ab5c4ff8e2c1e6e50a48373ba0..973c885d7997697e0bd4ec86a6b2fff670831417 100644 (file)
@@ -27,10 +27,17 @@ typedef struct Font i3Font;
  *
  */
 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;
+
+    /** Font information gathered from the server */
+    xcb_query_font_reply_t *info;
+
+    /** Font table for this font (may be NULL) */
+    xcb_charinfo_t *table;
+
+    /** The height of the font, built from font_ascent + font_descent */
+    int height;
 };
 
 /* Since this file also gets included by utilities which don’t use the i3 log
@@ -40,13 +47,14 @@ struct Font {
 #endif
 
 /**
- * Try to get the socket path from X11 and return NULL if it doesn’t work.
+ * Try to get the contents of the given atom (for example I3_SOCKET_PATH) from
+ * the X11 root window and return NULL if it doesn’t work.
  *
- * The memory for the socket path is dynamically allocated and has to be
+ * The memory for the contents is dynamically allocated and has to be
  * free()d by the caller.
  *
  */
-char *socket_path_from_x11();
+char *root_atom_contents(const char *atomname);
 
 /**
  * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that
@@ -177,6 +185,56 @@ uint32_t get_mod_mask_for(uint32_t keysym,
  * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
  *
  */
-i3Font load_font(const char *pattern, bool fallback);
+i3Font load_font(const char *pattern, const bool fallback);
+
+/**
+ * Defines the font to be used for the forthcoming calls.
+ *
+ */
+void set_font(i3Font *font);
+
+/**
+ * Frees the resources taken by the current font.
+ *
+ */
+void free_font();
+
+/**
+ * Converts the given string to UTF-8 from UCS-2 big endian. The return value
+ * must be freed after use.
+ *
+ */
+char *convert_ucs2_to_utf8(xcb_char2b_t *text, size_t num_glyphs);
+
+/**
+ * Converts the given string to UCS-2 big endian for use with
+ * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
+ * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
+ * returned. It has to be freed when done.
+ *
+ */
+xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen);
+
+/**
+ * Defines the colors to be used for the forthcoming draw_text calls.
+ *
+ */
+void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background);
+
+/**
+ * Draws text onto the specified X drawable (normally a pixmap) at the
+ * specified coordinates (from the top left corner of the leftmost, uppermost
+ * glyph) and using the provided gc. Text can be specified as UCS-2 or UTF-8.
+ *
+ */
+void draw_text(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawable,
+        xcb_gcontext_t gc, int x, int y, int max_width);
+
+/**
+ * Predict the text width in pixels for the given text. Text can be specified
+ * as UCS-2 or UTF-8.
+ *
+ */
+int predict_text_width(char *text, size_t text_len, bool is_ucs2);
 
 #endif
index ef6deb20fe5a993e1e0224f37e70ad18caf5f861..0eb5574444c06fc8b30356721fd9a44ad3fbeb96 100644 (file)
@@ -21,6 +21,8 @@
 
 extern char *loglevels[];
 extern char *errorfilename;
+extern char *shmlogname;
+extern int shmlog_size;
 
 /**
  * Initializes logging by creating an error logfile in /tmp (or
diff --git a/include/scratchpad.h b/include/scratchpad.h
new file mode 100644 (file)
index 0000000..4fb7523
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * scratchpad.c: Scratchpad functions (TODO: more description)
+ *
+ */
+#ifndef _SCRATCHPAD_H
+#define _SCRATCHPAD_H
+
+/**
+ * Moves the specified window to the __i3_scratch workspace, making it floating
+ * and setting the appropriate scratchpad_state.
+ *
+ * Gets called upon the command 'move scratchpad'.
+ *
+ */
+void scratchpad_move(Con *con);
+
+/**
+ * Either shows the top-most scratchpad window (con == NULL) or shows the
+ * specified con (if it is scratchpad window).
+ *
+ * When called with con == NULL and the currently focused window is a
+ * scratchpad window, this serves as a shortcut to hide it again (so the user
+ * can press the same key to quickly look something up).
+ *
+ */
+void scratchpad_show(Con *con);
+
+#endif
diff --git a/include/shmlog.h b/include/shmlog.h
new file mode 100644 (file)
index 0000000..c513bab
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * The format of the shmlog data structure which i3 development versions use by
+ * default (ringbuffer for storing the debug log).
+ *
+ */
+#ifndef _I3_SHMLOG_H
+#define _I3_SHMLOG_H
+
+#include <stdint.h>
+
+typedef struct i3_shmlog_header {
+    uint32_t offset_next_write;
+    uint32_t offset_last_wrap;
+    uint32_t size;
+} i3_shmlog_header;
+
+#endif
index 4a5920d2fbfa692627f17a09bc34a3fe5e8d1b97..cd88863c282a90b976bd9ba02fb6b4c3c58ef3ca 100644 (file)
@@ -91,15 +91,6 @@ void exec_i3_utility(char *name, char *argv[]);
 void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie,
                  char *err_message);
 
-/**
- * Converts the given string to UCS-2 big endian for use with
- * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen, a
- * buffer containing the UCS-2 encoded string (16 bit per glyph) is
- * returned. It has to be freed when done.
- *
- */
-char *convert_utf8_to_ucs2(char *input, int *real_strlen);
-
 /**
  * This function resolves ~ in pathnames.
  * It may resolve wildcards in the first part of the path, but if no match
index 995499f2c939706877694b3f35ff9d0c777a6d52..8e682bb0298405504799c1652e590a8fbffb839b 100644 (file)
  */
 Con *workspace_get(const char *num, bool *created);
 
+/*
+ * Returns a pointer to a new workspace in the given output. The workspace
+ * is created attached to the tree hierarchy through the given content
+ * container.
+ *
+ */
+Con *create_workspace_on_output(Output *output, Con *content);
+
 #if 0
 /**
  * Sets the name (or just its number) for the given workspace. This has to
@@ -69,6 +77,18 @@ Con* workspace_next();
  */
 Con* workspace_prev();
 
+/**
+ * Returns the next workspace on the same output
+ *
+ */
+Con* workspace_next_on_output();
+
+/**
+ * Returns the previous workspace on the same output
+ *
+ */
+Con* workspace_prev_on_output();
+
 /**
  * Focuses the previously focused workspace.
  *
index 01e2b66728ec28b022e6eaa7b5308b78b9dd2f5e..8c7d5422b79c083ccbd312c05176c00b2f45db30 100644 (file)
@@ -94,13 +94,6 @@ void send_take_focus(xcb_window_t window);
  */
 void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window);
 
-/**
- * Calculate the width of the given text (16-bit characters, UCS) with given
- * real length (amount of glyphs) using the given font.
- *
- */
-int predict_text_width(char *text, int length);
-
 /**
  * Configures the given window to have the size/position specified by given rect
  *
diff --git a/libi3/font.c b/libi3/font.c
new file mode 100644 (file)
index 0000000..3a68cb7
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <err.h>
+
+#include "libi3.h"
+
+extern xcb_connection_t *conn;
+static const i3Font *savedFont = NULL;
+
+/*
+ * Loads a font for usage, also getting its metrics. If fallback is true,
+ * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
+ *
+ */
+i3Font load_font(const char *pattern, const bool fallback) {
+    i3Font font;
+
+    /* Send all our requests first */
+    font.id = xcb_generate_id(conn);
+    xcb_void_cookie_t font_cookie = xcb_open_font_checked(conn, font.id,
+            strlen(pattern), pattern);
+    xcb_query_font_cookie_t info_cookie = xcb_query_font(conn, font.id);
+
+    /* Check for errors. If errors, fall back to default font. */
+    xcb_generic_error_t *error;
+    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_query_font(conn, font.id);
+
+        /* 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_query_font(conn, font.id);
+
+            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 (!(font.info = xcb_query_font_reply(conn, info_cookie, NULL)))
+        errx(EXIT_FAILURE, "Could not load font \"%s\"", pattern);
+
+    /* Get the font table, if possible */
+    if (xcb_query_font_char_infos_length(font.info) == 0)
+        font.table = NULL;
+    else
+        font.table = xcb_query_font_char_infos(font.info);
+
+    /* Calculate the font height */
+    font.height = font.info->font_ascent + font.info->font_descent;
+
+    return font;
+}
+
+/*
+ * Defines the font to be used for the forthcoming calls.
+ *
+ */
+void set_font(i3Font *font) {
+    savedFont = font;
+}
+
+/*
+ * Frees the resources taken by the current font.
+ *
+ */
+void free_font() {
+    /* Close the font and free the info */
+    xcb_close_font(conn, savedFont->id);
+    if (savedFont->info)
+        free(savedFont->info);
+}
+
+/*
+ * Defines the colors to be used for the forthcoming draw_text calls.
+ *
+ */
+void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background) {
+    assert(savedFont != NULL);
+    uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
+    uint32_t values[] = { foreground, background, savedFont->id };
+    xcb_change_gc(conn, gc, mask, values);
+}
+
+/*
+ * Draws text onto the specified X drawable (normally a pixmap) at the
+ * specified coordinates (from the top left corner of the leftmost, uppermost
+ * glyph) and using the provided gc. Text can be specified as UCS-2 or UTF-8.
+ *
+ */
+void draw_text(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawable,
+               xcb_gcontext_t gc, int x, int y, int max_width) {
+    assert(savedFont != NULL);
+    assert(text_len != 0);
+
+    /* X11 coordinates for fonts start at the baseline */
+    int pos_y = y + savedFont->info->font_ascent;
+
+    /* As an optimization, check if we can bypass conversion */
+    if (!is_ucs2 && text_len <= 255) {
+        xcb_image_text_8(conn, text_len, drawable, gc, x, pos_y, text);
+        return;
+    }
+
+    /* Convert the text into UCS-2 so we can do basic pointer math */
+    char *input = (is_ucs2 ? text : (char*)convert_utf8_to_ucs2(text, &text_len));
+
+    /* The X11 protocol limits text drawing to 255 chars, so we may need
+     * multiple calls */
+    int pos_x = x;
+    int offset = 0;
+    for (;;) {
+        /* Calculate the size of this chunk */
+        int chunk_size = (text_len > 255 ? 255 : text_len);
+        xcb_char2b_t *chunk = (xcb_char2b_t*)input + offset;
+
+        /* Draw it */
+        xcb_image_text_16(conn, chunk_size, drawable, gc, pos_x, pos_y, chunk);
+
+        /* Advance the offset and length of the text to draw */
+        offset += chunk_size;
+        text_len -= chunk_size;
+
+        /* Check if we're done */
+        if (text_len == 0)
+            break;
+
+        /* Advance pos_x based on the predicted text width */
+        pos_x += predict_text_width((char*)chunk, chunk_size, true);
+    }
+
+    /* If we had to convert, free the converted string */
+    if (!is_ucs2)
+        free(input);
+}
+
+static int xcb_query_text_width(xcb_char2b_t *text, size_t text_len) {
+    /* Make the user know we’re using the slow path, but only once. */
+    static bool first_invocation = true;
+    if (first_invocation) {
+        fprintf(stderr, "Using slow code path for text extents\n");
+        first_invocation = false;
+    }
+
+    /* Query the text width */
+    xcb_generic_error_t *error;
+    xcb_query_text_extents_cookie_t cookie = xcb_query_text_extents(conn,
+            savedFont->id, text_len, (xcb_char2b_t*)text);
+    xcb_query_text_extents_reply_t *reply = xcb_query_text_extents_reply(conn,
+            cookie, &error);
+    if (reply == NULL) {
+        /* We return a safe estimate because a rendering error is better than
+         * a crash. Plus, the user will see the error in his log. */
+        fprintf(stderr, "Could not get text extents (X error code %d)\n",
+                error->error_code);
+        return savedFont->info->max_bounds.character_width * text_len;
+    }
+
+    int width = reply->overall_width;
+    free(reply);
+    return width;
+}
+
+/*
+ * Predict the text width in pixels for the given text. Text can be specified
+ * as UCS-2 or UTF-8.
+ *
+ */
+int predict_text_width(char *text, size_t text_len, bool is_ucs2) {
+    /* Convert the text into UTF-16 so we can do basic pointer math */
+    xcb_char2b_t *input;
+    if (is_ucs2)
+        input = (xcb_char2b_t*)text;
+    else
+        input = convert_utf8_to_ucs2(text, &text_len);
+
+    int width;
+    if (savedFont->table == NULL) {
+        /* If we don't have a font table, fall back to querying the server */
+        width = xcb_query_text_width(input, text_len);
+    } else {
+        /* Save some pointers for convenience */
+        xcb_query_font_reply_t *font_info = savedFont->info;
+        xcb_charinfo_t *font_table = savedFont->table;
+
+        /* Calculate the width using the font table */
+        width = 0;
+        for (size_t i = 0; i < text_len; i++) {
+            xcb_charinfo_t *info;
+            int row = input[i].byte1;
+            int col = input[i].byte2;
+
+            if (row < font_info->min_byte1 ||
+                row > font_info->max_byte1 ||
+                col < font_info->min_char_or_byte2 ||
+                col > font_info->max_char_or_byte2)
+                continue;
+
+            /* Don't you ask me, how this one works… (Merovius) */
+            info = &font_table[((row - font_info->min_byte1) *
+                    (font_info->max_char_or_byte2 - font_info->min_char_or_byte2 + 1)) +
+                (col - font_info->min_char_or_byte2)];
+
+            if (info->character_width != 0 ||
+                    (info->right_side_bearing |
+                     info->left_side_bearing |
+                     info->ascent |
+                     info->descent) != 0) {
+                width += info->character_width;
+            }
+        }
+    }
+
+    /* If we had to convert, free the converted string */
+    if (!is_ucs2)
+        free(input);
+
+    return width;
+}
index 73bbef3d52448ba6911f742132eeefc64253f60f..b093594e10484d74677752756798062b5178719b 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <stdlib.h>
@@ -32,5 +32,7 @@ uint32_t get_colorpixel(const char *hex) {
     uint8_t g = strtol(strgroups[1], NULL, 16);
     uint8_t b = strtol(strgroups[2], NULL, 16);
 
-    return (r << 16 | g << 8 | b);
+    /* We set the first 8 bits high to have 100% opacity in case of a 32 bit
+     * color depth visual. */
+    return (0xFF << 24) | (r << 16 | g << 8 | b);
 }
diff --git a/libi3/get_socket_path.c b/libi3/get_socket_path.c
deleted file mode 100644 (file)
index d623b6c..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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/load_font.c b/libi3/load_font.c
deleted file mode 100644 (file)
index acb52c0..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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/root_atom_contents.c b/libi3/root_atom_contents.c
new file mode 100644 (file)
index 0000000..927cc5f
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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 contents of the given atom (for example I3_SOCKET_PATH) from
+ * the X11 root window and return NULL if it doesn’t work.
+ *
+ * The memory for the contents is dynamically allocated and has to be
+ * free()d by the caller.
+ *
+ */
+char *root_atom_contents(const char *atomname) {
+    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(atomname), atomname);
+
+    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/ucs2_conversion.c b/libi3/ucs2_conversion.c
new file mode 100644 (file)
index 0000000..6f7cf28
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <err.h>
+#include <errno.h>
+#include <iconv.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libi3.h"
+
+static iconv_t utf8_conversion_descriptor = (iconv_t)-1;
+static iconv_t ucs2_conversion_descriptor = (iconv_t)-1;
+
+/*
+ * Converts the given string to UTF-8 from UCS-2 big endian. The return value
+ * must be freed after use.
+ *
+ */
+char *convert_ucs2_to_utf8(xcb_char2b_t *text, size_t num_glyphs) {
+    /* Allocate the output buffer (UTF-8 is at most 4 bytes per glyph) */
+    size_t buffer_size = num_glyphs * 4 * sizeof(char) + 1;
+    char *buffer = scalloc(buffer_size * sizeof(char));
+
+    /* We need to use an additional pointer, because iconv() modifies it */
+    char *output = buffer;
+    size_t output_size = buffer_size - 1;
+
+    if (utf8_conversion_descriptor == (iconv_t)-1) {
+        /* Get a new conversion descriptor */
+        utf8_conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
+        if (utf8_conversion_descriptor == (iconv_t)-1)
+            err(EXIT_FAILURE, "Error opening the conversion context");
+    } else {
+        /* Reset the existing conversion descriptor */
+        iconv(utf8_conversion_descriptor, NULL, NULL, NULL, NULL);
+    }
+
+    /* Do the conversion */
+    size_t input_len = num_glyphs * sizeof(xcb_char2b_t);
+    size_t rc = iconv(utf8_conversion_descriptor, (char**)&text,
+            &input_len, &output, &output_size);
+    if (rc == (size_t)-1) {
+        perror("Converting to UTF-8 failed");
+        free(buffer);
+        return NULL;
+    }
+
+    return buffer;
+}
+
+/*
+ * Converts the given string to UCS-2 big endian for use with
+ * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
+ * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
+ * returned. It has to be freed when done.
+ *
+ */
+xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen) {
+    /* Calculate the input buffer size (UTF-8 is strlen-safe) */
+    size_t input_size = strlen(input);
+
+    /* Calculate the output buffer size and allocate the buffer */
+    size_t buffer_size = input_size * sizeof(xcb_char2b_t);
+    xcb_char2b_t *buffer = smalloc(buffer_size);
+
+    /* We need to use an additional pointer, because iconv() modifies it */
+    size_t output_size = buffer_size;
+    xcb_char2b_t *output = buffer;
+
+    if (ucs2_conversion_descriptor == (iconv_t)-1) {
+        /* Get a new conversion descriptor */
+        ucs2_conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
+        if (ucs2_conversion_descriptor == (iconv_t)-1)
+            err(EXIT_FAILURE, "Error opening the conversion context");
+    } else {
+        /* Reset the existing conversion descriptor */
+        iconv(ucs2_conversion_descriptor, NULL, NULL, NULL, NULL);
+    }
+
+    /* Do the conversion */
+    size_t rc = iconv(ucs2_conversion_descriptor, (char**)&input,
+            &input_size, (char**)&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;
+    }
+
+    /* Return the resulting string's length */
+    if (real_strlen != NULL)
+        *real_strlen = (buffer_size - output_size) / sizeof(xcb_char2b_t);
+
+    return buffer;
+}
index 44b2df6e6e88e2337d30f934eaf5f6cba2033249..5c3747358a8e266f5e1ac51ae54a4e448bd7aa45 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 i3-sensible-editor.1 i3-sensible-pager.1 i3-sensible-terminal.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 i3-dump-log.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 i3-sensible-editor i3-sensible-pager i3-sensible-terminal); \
+       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 i3-dump-log); \
        do \
                rm -f $${file}.1 $${file}.html $${file}.xml; \
        done
diff --git a/man/i3-dump-log.man b/man/i3-dump-log.man
new file mode 100644 (file)
index 0000000..8e9094f
--- /dev/null
@@ -0,0 +1,32 @@
+i3-dump-log(1)
+==============
+Michael Stapelberg <michael+i3@stapelberg.de>
+v4.1, December 2011
+
+== NAME
+
+i3-dump-log - dumps the i3 SHM log
+
+== SYNOPSIS
+
+i3-dump-log [-s <socketpath>]
+
+== DESCRIPTION
+
+Debug versions of i3 automatically use 1% of your RAM (but 25 MiB max) to store
+full debug loglevel log output. This is extremely helpful for bugreports and
+figuring out what is going on, without permanently logging to a file.
+
+With i3-dump-log, you can dump the SHM log to stdout.
+
+== EXAMPLE
+
+i3-dump-log | gzip -9 > /tmp/i3-log.gz
+
+== SEE ALSO
+
+i3(1)
+
+== AUTHOR
+
+Michael Stapelberg and contributors
index 116195b723445c24284f8a8edd467b943317a4f9..891c6c283dd7ab17fa6db541f5a1007e96865361 100644 (file)
@@ -1,7 +1,7 @@
 i3-msg(1)
 =========
 Michael Stapelberg <michael+i3@stapelberg.de>
-v3.delta, November 2009
+v4.2, January 2012
 
 == NAME
 
@@ -9,19 +9,52 @@ i3-msg - send messages to i3 window manager
 
 == SYNOPSIS
 
-i3-msg "message"
+i3-msg [-t type] [message]
+
+== IPC MESSAGE TYPES
+
+command::
+The payload of the message is a command for i3 (like the commands you can bind
+to keys in the configuration file) and will be executed directly after
+receiving it.
+
+get_workspaces::
+Gets the current workspaces. The reply will be a JSON-encoded list of
+workspaces.
+
+get_outputs::
+Gets the current outputs. The reply will be a JSON-encoded list of outputs (see
+the reply section).
+
+get_tree::
+Gets the layout tree. i3 uses a tree as data structure which includes every
+container. The reply will be the JSON-encoded tree.
+
+get_marks::
+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.
+
+get_bar_config::
+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.
+
 
 == DESCRIPTION
 
 i3-msg is a sample implementation for a client using the unix socket IPC
-interface to i3. At the moment, it can only be used for sending commands
-(like in configuration file for key bindings), but this may change in the
-future (staying backwards-compatible, of course).
+interface to i3.
 
-== EXAMPLE
+== EXAMPLES
 
 ------------------------------------------------
-i3-msg "bp" # Use 1-px border for current client
+# Use 1-px border for current client
+i3-msg "border 1pixel"
+
+# You can leave out the quotes
+i3-msg border normal
+
+# Dump the layout tree
+i3-msg -t get_tree
 ------------------------------------------------
 
 == ENVIRONMENT
index 86fbf84058471a273fee7c74844fc34ea61712ba..2a0448813ecb48ecb97624cc4717b5df8f7ab540 100644 (file)
@@ -23,6 +23,12 @@ It tries to start one of the following (in that order):
 * vim
 * vi
 * emacs
+* pico
+* qe
+* mg
+* jed
+* gedit
+* mc-edit
 
 Please don’t complain about the order: If the user has any preference, he will
 have $VISUAL or $EDITOR set.
index 73bd2fda74b59c829902ecd160a9e05702008e03..6b04c4316fda10d0f8fe7a1876ad356df1db5984 100644 (file)
@@ -18,8 +18,9 @@ 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
+* most
+* w3m
 * i3-sensible-editor(1)
 
 Please don’t complain about the order: If the user has any preference, he will
index 140e412bed1a2e9af146de711d31f04ab0a8144c..7e32aab4f0337d1580739e2fa6cd79965c56a47a 100644 (file)
@@ -22,9 +22,13 @@ 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
+* terminator
+* Eterm
+* aterm
+* xterm
+* gnome-terminal
 * roxterm
 
 Please don’t complain about the order: If the user has any preference, he will
index 2e14fce77071e7cf75ef057c2cfcf04155a45cd9..9d34c71038a316e01601ebe7543bffb15e7038a7 100644 (file)
@@ -311,7 +311,7 @@ which is why this is not integrated into this manpage), the debugging guide,
 and the "how to hack" guide. If you are building from source, run:
  +make -C docs+
 
-You can also access these documents online at http://i3.zekjur.net/
+You can also access these documents online at http://i3wm.org/
 
 i3-input(1), i3-msg(1), i3-wsbar(1), i3-nagbar(1), i3-config-wizard(1),
 i3-migrate-config-to-v4(1)
diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec
new file mode 100644 (file)
index 0000000..bbd7ab2
--- /dev/null
@@ -0,0 +1,232 @@
+# vim:ts=2:sw=2:expandtab
+#
+# i3 - an improved dynamic tiling window manager
+# © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+#
+# parser-specs/commands.spec: Specification file for generate-command-parser.pl
+# which will generate the appropriate header files for our C parser.
+#
+# Use :source highlighting.vim in vim to get syntax highlighting
+# for this file.
+
+state INITIAL:
+  # We have an end token here for all the commands which just call some
+  # function without using an explicit 'end' token.
+  end ->
+  '[' -> call cmd_criteria_init(); CRITERIA
+  'move' -> MOVE
+  'exec' -> EXEC
+  'exit' -> call cmd_exit()
+  'restart' -> call cmd_restart()
+  'reload' -> call cmd_reload()
+  'border' -> BORDER
+  'layout' -> LAYOUT
+  'append_layout' -> APPEND_LAYOUT
+  'workspace' -> WORKSPACE
+  'focus' -> FOCUS
+  'kill' -> KILL
+  'open' -> call cmd_open()
+  'fullscreen' -> FULLSCREEN
+  'split' -> SPLIT
+  'floating' -> FLOATING
+  'mark' -> MARK
+  'resize' -> RESIZE
+  'nop' -> NOP
+  'scratchpad' -> SCRATCHPAD
+  'mode' -> MODE
+
+state CRITERIA:
+  ctype = 'class' -> CRITERION
+  ctype = 'instance' -> CRITERION
+  ctype = 'window_role' -> CRITERION
+  ctype = 'con_id' -> CRITERION
+  ctype = 'id' -> CRITERION
+  ctype = 'con_mark' -> CRITERION
+  ctype = 'title' -> CRITERION
+  ctype = 'urgent' -> CRITERION
+  ']' -> call cmd_criteria_match_windows(); INITIAL
+
+state CRITERION:
+  '=' -> CRITERION_STR
+
+state CRITERION_STR:
+  cvalue = word
+      -> call cmd_criteria_add($ctype, $cvalue); CRITERIA
+
+# exec [--no-startup-id] <command>
+state EXEC:
+  nosn = '--no-startup-id'
+      ->
+  command = string
+      -> call cmd_exec($nosn, $command)
+
+# border normal|none|1pixel|toggle
+state BORDER:
+  border_style = 'normal', 'none', '1pixel', 'toggle'
+      -> call cmd_border($border_style)
+
+# layout default|stacked|stacking|tabbed
+state LAYOUT:
+  layout_mode = 'default', 'stacked', 'stacking', 'tabbed'
+      -> call cmd_layout($layout_mode)
+
+# append_layout <path>
+state APPEND_LAYOUT:
+  path = string -> call cmd_append_layout($path)
+
+# workspace next|prev|next_on_output|prev_on_output
+# workspace back_and_forth
+# workspace <name>
+state WORKSPACE:
+  direction = 'next_on_output', 'prev_on_output', 'next', 'prev'
+      -> call cmd_workspace($direction)
+  'back_and_forth'
+      -> call cmd_workspace_back_and_forth()
+  workspace = string 
+      -> call cmd_workspace_name($workspace)
+
+# focus left|right|up|down
+# focus output <output>
+# focus tiling|floating|mode_toggle
+# focus parent|child
+# focus
+state FOCUS:
+  direction = 'left', 'right', 'up', 'down'
+      -> call cmd_focus_direction($direction)
+  'output'
+      -> FOCUS_OUTPUT
+  window_mode = 'tiling', 'floating', 'mode_toggle'
+      -> call cmd_focus_window_mode($window_mode)
+  level = 'parent', 'child'
+      -> call cmd_focus_level($level)
+  end
+      -> call cmd_focus()
+
+state FOCUS_OUTPUT:
+  output = string
+      -> call cmd_focus_output($output)
+
+# kill [window|client]
+state KILL:
+  kill_mode = 'window', 'client'
+      -> call cmd_kill($kill_mode)
+  end
+      -> call cmd_kill($kill_mode)
+
+# fullscreen [global]
+state FULLSCREEN:
+  fullscreen_mode = 'global'
+      -> call cmd_fullscreen($fullscreen_mode)
+  end
+      -> call cmd_fullscreen($fullscreen_mode)
+
+# split v|h|vertical|horizontal
+state SPLIT:
+  direction = 'v', 'h', 'vertical', 'horizontal'
+      -> call cmd_split($direction)
+
+# floating enable|disable|toggle
+state FLOATING:
+  floating = 'enable', 'disable', 'toggle'
+      -> call cmd_floating($floating)
+
+# mark <mark>
+state MARK:
+  mark = string
+      -> call cmd_mark($mark)
+
+# resize
+state RESIZE:
+  way = 'grow', 'shrink'
+      -> RESIZE_DIRECTION
+
+state RESIZE_DIRECTION:
+  direction = 'up', 'down', 'left', 'right'
+      -> RESIZE_PX
+
+state RESIZE_PX:
+  resize_px = word
+      -> RESIZE_TILING
+  end
+      -> call cmd_resize($way, $direction, "10", "10")
+
+state RESIZE_TILING:
+  'px'
+      ->
+  'or'
+      -> RESIZE_TILING_OR
+  end
+      -> call cmd_resize($way, $direction, $resize_px, "10")
+
+state RESIZE_TILING_OR:
+  'ppt'
+      ->
+  resize_ppt = word
+      ->
+  end
+      -> call cmd_resize($way, $direction, $resize_px, $resize_ppt)
+
+# move <direction> [<pixels> [px]]
+# move [window|container] [to] workspace <str>
+# move [window|container] [to] output <str>
+# move [window|container] [to] scratchpad
+# move workspace to [output] <str>
+# move scratchpad
+state MOVE:
+  'window'
+      ->
+  'container'
+      ->
+  'to'
+      ->
+  'workspace'
+      -> MOVE_WORKSPACE
+  'output'
+      -> MOVE_TO_OUTPUT
+  'scratchpad'
+      -> call cmd_move_scratchpad()
+  direction = 'left', 'right', 'up', 'down'
+      -> MOVE_DIRECTION
+
+state MOVE_DIRECTION:
+  pixels = word
+      -> MOVE_DIRECTION_PX
+  end
+      -> call cmd_move_direction($direction, "10")
+
+state MOVE_DIRECTION_PX:
+  'px'
+      -> call cmd_move_direction($direction, $pixels)
+  end
+      -> call cmd_move_direction($direction, $pixels)
+
+state MOVE_WORKSPACE:
+  'to'
+      -> MOVE_WORKSPACE_TO_OUTPUT
+  workspace = 'next', 'prev', 'next_on_output', 'prev_on_output'
+      -> call cmd_move_con_to_workspace($workspace)
+  workspace = string
+      -> call cmd_move_con_to_workspace_name($workspace)
+
+state MOVE_TO_OUTPUT:
+  output = string
+      -> call cmd_move_con_to_output($output)
+
+state MOVE_WORKSPACE_TO_OUTPUT:
+  'output'
+      ->
+  output = string
+      -> call cmd_move_workspace_to_output($output)
+
+# mode <string>
+state MODE:
+  mode = string
+      -> call cmd_mode($mode)
+
+state NOP:
+  comment = string
+      -> call cmd_nop($comment)
+
+state SCRATCHPAD:
+  'show'
+      -> call cmd_scratchpad_show()
diff --git a/parser-specs/highlighting.vim b/parser-specs/highlighting.vim
new file mode 100644 (file)
index 0000000..f3d1aab
--- /dev/null
@@ -0,0 +1,20 @@
+set filetype=i3cmd
+syntax case match
+syntax clear
+
+syntax keyword i3specStatement state call
+highlight link i3specStatement Statement
+
+syntax match i3specComment /#.*/
+highlight link i3specComment Comment
+
+syntax region i3specLiteral start=/'/ end=/'/
+syntax keyword i3specToken string word end
+highlight link i3specLiteral String
+highlight link i3specToken String
+
+syntax match i3specState /[A-Z_]\{3,}/
+highlight link i3specState PreProc
+
+syntax match i3specSpecial /[->]/
+highlight link i3specSpecial Special
diff --git a/show-download-count.sh b/show-download-count.sh
new file mode 100644 (file)
index 0000000..95b955a
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+# © 2012 Han Boetes <han@mijncomputer.nl> (see also: LICENSE)
+YEAR=`date "+%Y"`
+weblog=$(mktemp)
+zcat $(find /var/log/lighttpd/build.i3wm.org -type f -name "access.log.*.gz" | sort | tail -5) > $weblog
+# this will match the latest logfile, which is not yet gzipped
+find /var/log/lighttpd/build.i3wm.org/log$YEAR -type f \! -name "access.log.*.gz" -exec cat '{}' \; >> $weblog
+cat /var/log/lighttpd/build.i3wm.org/access.log >> $weblog
+gitlog=$(mktemp)
+# create a git output logfile. Only keep the first 6 chars of the release hash
+git log -150 --pretty='        %h %s' next > $gitlog
+
+awk '/i3-wm_.*\.deb/ {print $7}' $weblog|awk -F'/' '{print $NF}'|awk -F'_' '{print $2 }'|awk -F'-' '{print $NF}' |cut -c 2-8|sort |uniq -c | while read line; do
+    set -- $line
+    # $1 is the number of downloads, $2 is the release md5sum
+    sed -i "/$2/s|^        |$(printf '%3i' $1) d/l |" $gitlog
+done
+
+cat $gitlog
+rm $gitlog
+rm $weblog
index 9ef84707f13da20d53b72bc96cbf9b525170735a..09793c38d7b456f9f4375b760bde9b7a930c21db 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * assignments.c: Assignments for specific windows (for_window).
  *
@@ -17,6 +17,8 @@
 void run_assignments(i3Window *window) {
     DLOG("Checking if any assignments match this window\n");
 
+    bool needs_tree_render = false;
+
     /* Check if any assignments match */
     Assignment *current;
     TAILQ_FOREACH(current, &assignments, assignments) {
@@ -41,9 +43,13 @@ void run_assignments(i3Window *window) {
             DLOG("execute command %s\n", current->dest.command);
             char *full_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);
+            struct CommandResult *command_output = parse_command(full_command);
+            free(full_command);
+
+            if (command_output->needs_tree_render)
+                needs_tree_render = true;
+
+            free(command_output->json_output);
         }
 
         /* Store that we ran this assignment to not execute it again */
@@ -51,6 +57,10 @@ void run_assignments(i3Window *window) {
         window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment*) * window->nr_assignments);
         window->ran_assignments[window->nr_assignments-1] = current;
     }
+
+    /* If any of the commands required re-rendering, we will do that now. */
+    if (needs_tree_render)
+        tree_render();
 }
 
 /*
index 1566e24f879fabd4a162500cb28057a8b4461344..7fbbefcb3e098d6790ee7a8f85c942a89902604b 100644 (file)
@@ -37,7 +37,8 @@ int yycolumn = 1;
     yy_push_state(EAT_WHITESPACE); \
 } while (0)
 
-#define BAR_DOUBLE_COLOR do { \
+#define BAR_TRIPLE_COLOR do { \
+    yy_push_state(BAR_COLOR); \
     yy_push_state(BAR_COLOR); \
     yy_push_state(BAR_COLOR); \
 } while (0)
@@ -59,6 +60,7 @@ EOL     (\r?\n)
 %x BUFFER_LINE
 %x BAR
 %x BAR_MODE
+%x BAR_MODIFIER
 %x BAR_POSITION
 %x BAR_COLORS
 %x BAR_COLOR
@@ -88,7 +90,7 @@ EOL     (\r?\n)
 }
 
  /* This part of the lexer handles the bar {} blocks */
-<BAR,BAR_MODE,BAR_POSITION,BAR_COLORS,BAR_COLOR>[ \t]+ { /* ignore whitespace */ ; }
+<BAR,BAR_MODE,BAR_MODIFIER,BAR_POSITION,BAR_COLORS,BAR_COLOR>[ \t]+ { /* ignore whitespace */ ; }
 <BAR>"{"                        { return '{'; }
 <BAR>"}"                        { yy_pop_state(); return '}'; }
 <BAR>^[ \t]*#[^\n]*             { return TOKCOMMENT; }
@@ -98,10 +100,20 @@ EOL     (\r?\n)
 <BAR>mode                       { yy_push_state(BAR_MODE); return TOK_BAR_MODE; }
 <BAR_MODE>hide                  { yy_pop_state(); return TOK_BAR_HIDE; }
 <BAR_MODE>dock                  { yy_pop_state(); return TOK_BAR_DOCK; }
+<BAR>modifier                   { yy_push_state(BAR_MODIFIER); return TOK_BAR_MODIFIER; }
+<BAR_MODIFIER>control           { yy_pop_state(); return TOK_BAR_CONTROL; }
+<BAR_MODIFIER>ctrl              { yy_pop_state(); return TOK_BAR_CONTROL; }
+<BAR_MODIFIER>shift             { yy_pop_state(); return TOK_BAR_SHIFT; }
+<BAR_MODIFIER>Mod1              { yy_pop_state(); return TOK_BAR_MOD1; }
+<BAR_MODIFIER>Mod2              { yy_pop_state(); return TOK_BAR_MOD2; }
+<BAR_MODIFIER>Mod3              { yy_pop_state(); return TOK_BAR_MOD3; }
+<BAR_MODIFIER>Mod4              { yy_pop_state(); return TOK_BAR_MOD4; }
+<BAR_MODIFIER>Mod5              { yy_pop_state(); return TOK_BAR_MOD5; }
 <BAR>position                   { yy_push_state(BAR_POSITION); return TOK_BAR_POSITION; }
 <BAR_POSITION>bottom            { yy_pop_state(); return TOK_BAR_BOTTOM; }
 <BAR_POSITION>top               { yy_pop_state(); return TOK_BAR_TOP; }
 <BAR>status_command             { WS_STRING; return TOK_BAR_STATUS_COMMAND; }
+<BAR>i3bar_command              { WS_STRING; return TOK_BAR_I3BAR_COMMAND; }
 <BAR>font                       { WS_STRING; return TOK_BAR_FONT; }
 <BAR>workspace_buttons          { return TOK_BAR_WORKSPACE_BUTTONS; }
 <BAR>verbose                    { return TOK_BAR_VERBOSE; }
@@ -111,12 +123,18 @@ EOL     (\r?\n)
 <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_COLORS>focused_workspace   { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_FOCUSED_WORKSPACE; }
+<BAR_COLORS>active_workspace    { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_ACTIVE_WORKSPACE; }
+<BAR_COLORS>inactive_workspace  { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_INACTIVE_WORKSPACE; }
+<BAR_COLORS>urgent_workspace    { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_URGENT_WORKSPACE; }
 <BAR_COLOR>#[0-9a-fA-F]+        { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
-<BAR,BAR_COLORS,BAR_MODE,BAR_POSITION>[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; }
+<BAR_COLOR>{EOL}                {
+                                  yy_pop_state();
+                                  FREE(context->line_copy);
+                                  context->line_number++;
+                                  yy_push_state(BUFFER_LINE);
+                                }
+<BAR,BAR_COLORS,BAR_MODE,BAR_MODIFIER,BAR_POSITION>[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; }
 
 
 
@@ -142,16 +160,24 @@ EOL     (\r?\n)
 <OUTPUT_COND>[a-zA-Z0-9_-]+     { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; }
 ^[ \t]*#[^\n]*                  { return TOKCOMMENT; }
 <COLOR_COND>#[0-9a-fA-F]+       { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
+<COLOR_COND>{EOL}               {
+                                  yy_pop_state();
+                                  FREE(context->line_copy);
+                                  context->line_number++;
+                                  yy_push_state(BUFFER_LINE);
+                                }
 <ASSIGN_TARGET_COND>[ \t]*→[ \t]*     { BEGIN(WANT_STRING); }
 <ASSIGN_TARGET_COND>[ \t]+      { BEGIN(WANT_STRING); }
 <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; }
+[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_maximum_size           { return TOKFLOATING_MAXIMUM_SIZE; }
+floating_minimum_size           { return TOKFLOATING_MINIMUM_SIZE; }
 floating_modifier               { return TOKFLOATING_MODIFIER; }
 workspace                       { return TOKWORKSPACE; }
 output                          { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; }
@@ -202,10 +228,10 @@ rows                            { /* yylval.number = STACK_LIMIT_ROWS; */return
 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; }
+client.focused                  { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; }
+client.focused_inactive         { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; }
+client.unfocused                { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; }
+client.urgent                   { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; }
 bar.focused                     { yy_push_state(COLOR_COND); yylval.color = &config.bar.focused; return TOKCOLOR; }
 bar.unfocused                   { yy_push_state(COLOR_COND); yylval.color = &config.bar.unfocused; return TOKCOLOR; }
 bar.urgent                      { yy_push_state(COLOR_COND); yylval.color = &config.bar.urgent; return TOKCOLOR; }
@@ -226,6 +252,7 @@ id                              { yy_push_state(WANT_QSTRING); return TOK_ID; }
 con_id                          { yy_push_state(WANT_QSTRING); return TOK_CON_ID; }
 con_mark                        { yy_push_state(WANT_QSTRING); return TOK_MARK; }
 title                           { yy_push_state(WANT_QSTRING); return TOK_TITLE; }
+urgent                          { yy_push_state(WANT_QSTRING); return TOK_URGENT; }
 
 <*>{EOL}                        {
                                   FREE(context->line_copy);
index 073ff241400b1aaa2cc071529abb21be5eb22d7c..676e0d111181d0c9124a08e7c34e9386e1d63e47 100644 (file)
@@ -664,6 +664,8 @@ void parse_file(const char *f) {
 %token                  TOKCONTROL                  "control"
 %token                  TOKSHIFT                    "shift"
 %token                  TOKFLOATING_MODIFIER        "floating_modifier"
+%token                  TOKFLOATING_MAXIMUM_SIZE    "floating_maximum_size"
+%token                  TOKFLOATING_MINIMUM_SIZE    "floating_minimum_size"
 %token  <string>        QUOTEDSTRING                "<quoted string>"
 %token                  TOKWORKSPACE                "workspace"
 %token                  TOKOUTPUT                   "output"
@@ -708,10 +710,19 @@ void parse_file(const char *f) {
 %token                  TOK_BAR_MODE                "mode (bar)"
 %token                  TOK_BAR_HIDE                "hide"
 %token                  TOK_BAR_DOCK                "dock"
+%token                  TOK_BAR_MODIFIER            "modifier (bar)"
+%token                  TOK_BAR_CONTROL             "shift (bar)"
+%token                  TOK_BAR_SHIFT               "control (bar)"
+%token                  TOK_BAR_MOD1                "Mod1"
+%token                  TOK_BAR_MOD2                "Mod2"
+%token                  TOK_BAR_MOD3                "Mod3"
+%token                  TOK_BAR_MOD4                "Mod4"
+%token                  TOK_BAR_MOD5                "Mod5"
 %token                  TOK_BAR_POSITION            "position"
 %token                  TOK_BAR_BOTTOM              "bottom"
 %token                  TOK_BAR_TOP                 "top"
 %token                  TOK_BAR_STATUS_COMMAND      "status_command"
+%token                  TOK_BAR_I3BAR_COMMAND       "i3bar_command"
 %token                  TOK_BAR_FONT                "font (bar)"
 %token                  TOK_BAR_WORKSPACE_BUTTONS   "workspace_buttons"
 %token                  TOK_BAR_VERBOSE             "verbose"
@@ -731,6 +742,7 @@ void parse_file(const char *f) {
 %token              TOK_ID              "id"
 %token              TOK_CON_ID          "con_id"
 %token              TOK_TITLE           "title"
+%token              TOK_URGENT          "urgent"
 
 %type   <binding>       binding
 %type   <binding>       bindcode
@@ -747,6 +759,7 @@ void parse_file(const char *f) {
 %type   <number>        popup_setting
 %type   <number>        bar_position_position
 %type   <number>        bar_mode_mode
+%type   <number>        bar_modifier_modifier
 %type   <number>        optional_no_startup_id
 %type   <string>        command
 %type   <string>        word_or_number
@@ -767,6 +780,8 @@ line:
     | for_window
     | mode
     | bar
+    | floating_maximum_size
+    | floating_minimum_size
     | floating_modifier
     | orientation
     | workspace_layout
@@ -943,6 +958,20 @@ criterion:
         current_match.title = regex_new($3);
         free($3);
     }
+    | TOK_URGENT '=' STR
+    {
+        printf("criteria: urgent = %s\n", $3);
+        if (strcasecmp($3, "latest") == 0 ||
+            strcasecmp($3, "newest") == 0 ||
+            strcasecmp($3, "recent") == 0 ||
+            strcasecmp($3, "last") == 0) {
+            current_match.urgent = U_LATEST;
+        } else if (strcasecmp($3, "oldest") == 0 ||
+                   strcasecmp($3, "first") == 0) {
+            current_match.urgent = U_OLDEST;
+        }
+        free($3);
+    }
     ;
 
 qstring_or_number:
@@ -1036,10 +1065,12 @@ barlines:
 barline:
     comment
     | bar_status_command
+    | bar_i3bar_command
     | bar_output
     | bar_tray_output
     | bar_position
     | bar_mode
+    | bar_modifier
     | bar_font
     | bar_workspace_buttons
     | bar_verbose
@@ -1062,6 +1093,15 @@ bar_status_command:
     }
     ;
 
+bar_i3bar_command:
+    TOK_BAR_I3BAR_COMMAND STR
+    {
+        DLOG("should add i3bar_command %s\n", $2);
+        FREE(current_bar.i3bar_command);
+        current_bar.i3bar_command = $2;
+    }
+    ;
+
 bar_output:
     TOK_BAR_OUTPUT STR
     {
@@ -1108,6 +1148,23 @@ bar_mode_mode:
     | TOK_BAR_DOCK { $$ = M_DOCK; }
     ;
 
+bar_modifier:
+    TOK_BAR_MODIFIER bar_modifier_modifier
+    {
+        DLOG("modifier %d\n", $2);
+        current_bar.modifier = $2;
+    };
+
+bar_modifier_modifier:
+    TOK_BAR_CONTROL { $$ = M_CONTROL; }
+    | TOK_BAR_SHIFT { $$ = M_SHIFT; }
+    | TOK_BAR_MOD1  { $$ = M_MOD1; }
+    | TOK_BAR_MOD2  { $$ = M_MOD2; }
+    | TOK_BAR_MOD3  { $$ = M_MOD3; }
+    | TOK_BAR_MOD4  { $$ = M_MOD4; }
+    | TOK_BAR_MOD5  { $$ = M_MOD5; }
+    ;
+
 bar_font:
     TOK_BAR_FONT STR
     {
@@ -1172,36 +1229,90 @@ bar_color_statusline:
 bar_color_focused_workspace:
     TOK_BAR_COLOR_FOCUSED_WORKSPACE HEXCOLOR HEXCOLOR
     {
-        DLOG("focused_ws = %s and %s\n", $2, $3);
+        /* Old syntax: text / background */
+        DLOG("focused_ws = %s, %s (old)\n", $2, $3);
+        current_bar.colors.focused_workspace_bg = $3;
         current_bar.colors.focused_workspace_text = $2;
+    }
+    | TOK_BAR_COLOR_FOCUSED_WORKSPACE HEXCOLOR HEXCOLOR HEXCOLOR
+    {
+        /* New syntax: border / background / text */
+        DLOG("focused_ws = %s, %s and %s\n", $2, $3, $4);
+        current_bar.colors.focused_workspace_border = $2;
         current_bar.colors.focused_workspace_bg = $3;
+        current_bar.colors.focused_workspace_text = $4;
     }
     ;
 
 bar_color_active_workspace:
     TOK_BAR_COLOR_ACTIVE_WORKSPACE HEXCOLOR HEXCOLOR
     {
-        DLOG("active_ws = %s and %s\n", $2, $3);
+        /* Old syntax: text / background */
+        DLOG("active_ws = %s, %s (old)\n", $2, $3);
+        current_bar.colors.active_workspace_bg = $3;
         current_bar.colors.active_workspace_text = $2;
+    }
+    | TOK_BAR_COLOR_ACTIVE_WORKSPACE HEXCOLOR HEXCOLOR HEXCOLOR
+    {
+        /* New syntax: border / background / text */
+        DLOG("active_ws = %s, %s and %s\n", $2, $3, $4);
+        current_bar.colors.active_workspace_border = $2;
         current_bar.colors.active_workspace_bg = $3;
+        current_bar.colors.active_workspace_text = $4;
     }
     ;
 
 bar_color_inactive_workspace:
     TOK_BAR_COLOR_INACTIVE_WORKSPACE HEXCOLOR HEXCOLOR
     {
-        DLOG("inactive_ws = %s and %s\n", $2, $3);
+        /* Old syntax: text / background */
+        DLOG("inactive_ws = %s, %s (old)\n", $2, $3);
+        current_bar.colors.inactive_workspace_bg = $3;
         current_bar.colors.inactive_workspace_text = $2;
+    }
+    | TOK_BAR_COLOR_INACTIVE_WORKSPACE HEXCOLOR HEXCOLOR HEXCOLOR
+    {
+        DLOG("inactive_ws = %s, %s and %s\n", $2, $3, $4);
+        current_bar.colors.inactive_workspace_border = $2;
         current_bar.colors.inactive_workspace_bg = $3;
+        current_bar.colors.inactive_workspace_text = $4;
     }
     ;
 
 bar_color_urgent_workspace:
     TOK_BAR_COLOR_URGENT_WORKSPACE HEXCOLOR HEXCOLOR
     {
-        DLOG("urgent_ws = %s and %s\n", $2, $3);
+        /* Old syntax: text / background */
+        DLOG("urgent_ws = %s, %s (old)\n", $2, $3);
+        current_bar.colors.urgent_workspace_bg = $3;
         current_bar.colors.urgent_workspace_text = $2;
+    }
+    | TOK_BAR_COLOR_URGENT_WORKSPACE HEXCOLOR HEXCOLOR HEXCOLOR
+    {
+        DLOG("urgent_ws = %s, %s and %s\n", $2, $3, $4);
+        current_bar.colors.urgent_workspace_border = $2;
         current_bar.colors.urgent_workspace_bg = $3;
+        current_bar.colors.urgent_workspace_text = $4;
+    }
+    ;
+
+floating_maximum_size:
+    TOKFLOATING_MAXIMUM_SIZE NUMBER WORD NUMBER
+    {
+        printf("floating_maximum_width = %d\n", $2);
+        printf("floating_maximum_height = %d\n", $4);
+        config.floating_maximum_width = $2;
+        config.floating_maximum_height = $4;
+    }
+    ;
+
+floating_minimum_size:
+    TOKFLOATING_MINIMUM_SIZE NUMBER WORD NUMBER
+    {
+        printf("floating_minimum_width = %d\n", $2);
+        printf("floating_minimum_height = %d\n", $4);
+        config.floating_minimum_width = $2;
+        config.floating_minimum_height = $4;
     }
     ;
 
@@ -1553,6 +1664,7 @@ font:
     TOKFONT STR
     {
         config.font = load_font($2, true);
+        set_font(&config.font);
         printf("font %s\n", $2);
         FREE(font_pattern);
         font_pattern = $2;
@@ -1576,6 +1688,15 @@ color:
         dest->background = $3;
         dest->text = $4;
     }
+    | TOKCOLOR colorpixel colorpixel colorpixel colorpixel
+    {
+        struct Colortriple *dest = $1;
+
+        dest->border = $2;
+        dest->background = $3;
+        dest->text = $4;
+        dest->indicator = $5;
+    }
     ;
 
 colorpixel:
diff --git a/src/cmdparse.l b/src/cmdparse.l
deleted file mode 100644 (file)
index 5a72765..0000000
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
- *
- * cmdparse.l: the lexer for commands you send to i3 (or bind on keys)
- *
- */
-%option nounput
-%option noinput
-%option noyy_top_state
-%option stack
-
-%{
-#include <stdio.h>
-#include <string.h>
-#include "cmdparse.tab.h"
-
-#include "config.h"
-#include "util.h"
-#include "libi3.h"
-
-int cmdyycolumn = 1;
-
-#define YY_DECL int yylex (struct context *context)
-
-#define YY_USER_ACTION { \
-    context->first_column = cmdyycolumn; \
-    context->last_column = cmdyycolumn+yyleng-1; \
-    cmdyycolumn += yyleng; \
-}
-
-/* macro to first eat whitespace, then expect a string */
-#define WS_STRING do { \
-    yy_push_state(WANT_STRING); \
-    yy_push_state(EAT_WHITESPACE); \
-} while (0)
-
-%}
-
-EOL (\r?\n)
-
-/* handle everything up to \n as a string */
-%s WANT_STRING
-/* eat a whitespace, then go to the next state on the stack */
-%s EAT_WHITESPACE
-/* handle a quoted string or everything up to the next whitespace */
-%s WANT_QSTRING
-
-%x EXEC
-
-%x BUFFER_LINE
-
-%%
-
-    {
-        /* This is called when a new line is lexed. We only want the
-         * first line to match to go into state BUFFER_LINE */
-        if (context->line_number == 0) {
-            context->line_number = 1;
-            BEGIN(INITIAL);
-            yy_push_state(BUFFER_LINE);
-        }
-    }
-
-<BUFFER_LINE>^[^\r\n]*/{EOL}? {
-    /* save whole line */
-    context->line_copy = sstrdup(yytext);
-
-    yyless(0);
-    yy_pop_state();
-    yy_set_bol(true);
-    cmdyycolumn = 1;
-}
-
-    /* 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);
-                                  /* strip quotes */
-                                  char *copy = sstrdup(yytext+1);
-                                  copy[strlen(copy)-1] = '\0';
-                                  cmdyylval.string = copy;
-                                  return STR;
-                                }
-<WANT_QSTRING>\"[^\"]+\"        {
-                                  BEGIN(INITIAL);
-                                  /* strip quotes */
-                                  char *copy = sstrdup(yytext+1);
-                                  copy[strlen(copy)-1] = '\0';
-                                  cmdyylval.string = copy;
-                                  return STR;
-                                }
-
-<WANT_STRING>[^;\n]+            { BEGIN(INITIAL); cmdyylval.string = sstrdup(yytext); return STR; }
-
-<EAT_WHITESPACE>[;\n]           { BEGIN(INITIAL); return ';'; }
-<EAT_WHITESPACE>[ \t]*          { yy_pop_state(); }
-
-[ \t]*                          { /* ignore whitespace */ ; }
-<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; }
-kill                            { return TOK_KILL; }
-window                          { return TOK_WINDOW; }
-client                          { return TOK_CLIENT; }
-fullscreen                      { return TOK_FULLSCREEN; }
-global                          { return TOK_GLOBAL; }
-layout                          { return TOK_LAYOUT; }
-default                         { return TOK_DEFAULT; }
-stacked                         { return TOK_STACKED; }
-stacking                        { return TOK_STACKED; }
-tabbed                          { return TOK_TABBED; }
-border                          { return TOK_BORDER; }
-normal                          { return TOK_NORMAL; }
-none                            { return TOK_NONE; }
-1pixel                          { return TOK_1PIXEL; }
-mode                            { BEGIN(WANT_QSTRING); return TOK_MODE; }
-tiling                          { return TOK_TILING; }
-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; }
-split                           { return TOK_SPLIT; }
-horizontal                      { return TOK_HORIZONTAL; }
-vertical                        { return TOK_VERTICAL; }
-up                              { return TOK_UP; }
-down                            { return TOK_DOWN; }
-left                            { return TOK_LEFT; }
-right                           { return TOK_RIGHT; }
-parent                          { return TOK_PARENT; }
-child                           { return TOK_CHILD; }
-resize                          { return TOK_RESIZE; }
-shrink                          { return TOK_SHRINK; }
-grow                            { return TOK_GROW; }
-px                              { return TOK_PX; }
-or                              { return TOK_OR; }
-ppt                             { return TOK_PPT; }
-nop                             { WS_STRING; return TOK_NOP; }
-append_layout                   { WS_STRING; return TOK_APPEND_LAYOUT; }
-mark                            { WS_STRING; return TOK_MARK; }
-
-enable                          { return TOK_ENABLE; }
-true                            { return TOK_ENABLE; }
-yes                             { return TOK_ENABLE; }
-disable                         { return TOK_DISABLE; }
-false                           { return TOK_DISABLE; }
-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; }
-title                           { BEGIN(WANT_QSTRING); return TOK_TITLE; }
-
-[0-9]+                          { cmdyylval.number = atoi(yytext); return NUMBER; }
-
-.                               { return (int)yytext[0]; }
-
-<<EOF>> {
-    while (yy_start_stack_ptr > 0)
-        yy_pop_state();
-    yyterminate();
-}
-
-%%
diff --git a/src/cmdparse.y b/src/cmdparse.y
deleted file mode 100644 (file)
index 7f86c8d..0000000
+++ /dev/null
@@ -1,1109 +0,0 @@
-%{
-/*
- * 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)
- *
- */
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <float.h>
-
-#include "all.h"
-
-/** When the command did not include match criteria (!), we use the currently
- * focused command. Do not confuse this case with a command which included
- * criteria but which did not match any windows. This macro has to be called in
- * every command.
- */
-#define HANDLE_EMPTY_MATCH do { \
-    if (match_is_empty(&current_match)) { \
-        owindow *ow = smalloc(sizeof(owindow)); \
-        ow->con = focused; \
-        TAILQ_INIT(&owindows); \
-        TAILQ_INSERT_TAIL(&owindows, ow, owindows); \
-    } \
-} while (0)
-
-typedef struct yy_buffer_state *YY_BUFFER_STATE;
-extern int cmdyylex(struct context *context);
-extern int cmdyyparse(void);
-extern int cmdyylex_destroy(void);
-extern FILE *cmdyyin;
-YY_BUFFER_STATE cmdyy_scan_string(const char *);
-
-static struct context *context;
-static Match current_match;
-
-/*
- * Helper data structure for an operation window (window on which the operation
- * will be performed). Used to build the TAILQ owindows.
- *
- */
-typedef struct owindow {
-    Con *con;
-    TAILQ_ENTRY(owindow) owindows;
-} owindow;
-static TAILQ_HEAD(owindows_head, owindow) owindows;
-
-/* Holds the JSON which will be returned via IPC or NULL for the default return
- * message */
-static char *json_output;
-
-/* We don’t need yydebug for now, as we got decent error messages using
- * yyerror(). Should you ever want to extend the parser, it might be handy
- * to just comment it in again, so it stays here. */
-//int cmdyydebug = 1;
-
-void cmdyyerror(const char *error_message) {
-    ELOG("\n");
-    ELOG("CMD: %s\n", error_message);
-    ELOG("CMD: in command:\n");
-    ELOG("CMD:   %s\n", context->line_copy);
-    ELOG("CMD:   ");
-    for (int c = 1; c <= context->last_column; c++)
-        if (c >= context->first_column)
-                printf("^");
-        else printf(" ");
-    printf("\n");
-    ELOG("\n");
-    context->compact_error = sstrdup(error_message);
-}
-
-int cmdyywrap() {
-    return 1;
-}
-
-char *parse_cmd(const char *new) {
-    json_output = NULL;
-    LOG("COMMAND: *%s*\n", new);
-    cmdyy_scan_string(new);
-
-    match_init(&current_match);
-    context = scalloc(sizeof(struct context));
-    context->filename = "cmd";
-    if (cmdyyparse() != 0) {
-        fprintf(stderr, "Could not parse command\n");
-        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);
-        return json_output;
-    }
-    printf("done, json output = %s\n", json_output);
-
-    cmdyylex_destroy();
-    FREE(context->line_copy);
-    FREE(context->compact_error);
-    free(context);
-    return json_output;
-}
-
-/*
- * Returns true if a is definitely greater than b (using the given epsilon)
- *
- */
-bool definitelyGreaterThan(float a, float b, float epsilon) {
-    return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
-}
-
-%}
-
-%error-verbose
-%lex-param { struct context *context }
-
-%union {
-    char *string;
-    char chr;
-    int number;
-}
-
-%token              TOK_EXEC            "exec"
-%token              TOK_EXIT            "exit"
-%token              TOK_RELOAD          "reload"
-%token              TOK_RESTART         "restart"
-%token              TOK_KILL            "kill"
-%token              TOK_WINDOW          "window"
-%token              TOK_CLIENT          "client"
-%token              TOK_FULLSCREEN      "fullscreen"
-%token              TOK_GLOBAL          "global"
-%token              TOK_LAYOUT          "layout"
-%token              TOK_DEFAULT         "default"
-%token              TOK_STACKED         "stacked"
-%token              TOK_TABBED          "tabbed"
-%token              TOK_BORDER          "border"
-%token              TOK_NORMAL          "normal"
-%token              TOK_NONE            "none"
-%token              TOK_1PIXEL          "1pixel"
-%token              TOK_MODE            "mode"
-%token              TOK_TILING          "tiling"
-%token              TOK_FLOATING        "floating"
-%token              TOK_MODE_TOGGLE     "mode_toggle"
-%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"
-%token              TOK_OPEN            "open"
-%token              TOK_NEXT            "next"
-%token              TOK_PREV            "prev"
-%token              TOK_SPLIT           "split"
-%token              TOK_HORIZONTAL      "horizontal"
-%token              TOK_VERTICAL        "vertical"
-%token              TOK_UP              "up"
-%token              TOK_DOWN            "down"
-%token              TOK_LEFT            "left"
-%token              TOK_RIGHT           "right"
-%token              TOK_PARENT          "parent"
-%token              TOK_CHILD           "child"
-%token              TOK_APPEND_LAYOUT   "append_layout"
-%token              TOK_MARK            "mark"
-%token              TOK_RESIZE          "resize"
-%token              TOK_GROW            "grow"
-%token              TOK_SHRINK          "shrink"
-%token              TOK_PX              "px"
-%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"
-
-%token  <string>    STR                 "<string>"
-%token  <number>    NUMBER              "<number>"
-
-%type   <number>    direction
-%type   <number>    split_direction
-%type   <number>    fullscreen_mode
-%type   <number>    level
-%type   <number>    window_mode
-%type   <number>    boolean
-%type   <number>    border_style
-%type   <number>    layout_mode
-%type   <number>    resize_px
-%type   <number>    resize_way
-%type   <number>    resize_tiling
-%type   <number>    optional_kill_mode
-%type   <number>    optional_no_startup_id
-
-%%
-
-commands:
-    commands ';' command
-    | command
-    {
-        owindow *current;
-
-        printf("single command completely parsed, dropping state...\n");
-        while (!TAILQ_EMPTY(&owindows)) {
-            current = TAILQ_FIRST(&owindows);
-            TAILQ_REMOVE(&owindows, current, owindows);
-            free(current);
-        }
-        match_init(&current_match);
-    }
-    ;
-
-command:
-    match operations
-    ;
-
-match:
-    | matchstart criteria matchend
-    {
-        printf("match parsed\n");
-    }
-    ;
-
-matchstart:
-    '['
-    {
-        printf("start\n");
-        match_init(&current_match);
-        TAILQ_INIT(&owindows);
-        /* copy all_cons */
-        Con *con;
-        TAILQ_FOREACH(con, &all_cons, all_cons) {
-            owindow *ow = smalloc(sizeof(owindow));
-            ow->con = con;
-            TAILQ_INSERT_TAIL(&owindows, ow, owindows);
-        }
-    }
-    ;
-
-matchend:
-    ']'
-    {
-        owindow *next, *current;
-
-        printf("match specification finished, matching...\n");
-        /* copy the old list head to iterate through it and start with a fresh
-         * list which will contain only matching windows */
-        struct owindows_head old = owindows;
-        TAILQ_INIT(&owindows);
-        for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) {
-            /* make a copy of the next pointer and advance the pointer to the
-             * next element as we are going to invalidate the element’s
-             * next/prev pointers by calling TAILQ_INSERT_TAIL later */
-            current = next;
-            next = TAILQ_NEXT(next, owindows);
-
-            printf("checking if con %p / %s matches\n", current->con, current->con->name);
-            if (current_match.con_id != NULL) {
-                if (current_match.con_id == current->con) {
-                    printf("matches container!\n");
-                    TAILQ_INSERT_TAIL(&owindows, current, owindows);
-
-                }
-            } else if (current_match.mark != NULL && current->con->mark != NULL &&
-                       regex_matches(current_match.mark, current->con->mark)) {
-                printf("match by mark\n");
-                TAILQ_INSERT_TAIL(&owindows, current, owindows);
-            } else {
-                if (current->con->window == NULL)
-                    continue;
-                if (match_matches_window(&current_match, current->con->window)) {
-                    printf("matches window!\n");
-                    TAILQ_INSERT_TAIL(&owindows, current, owindows);
-                } else {
-                    printf("doesnt match\n");
-                    free(current);
-                }
-            }
-        }
-
-        TAILQ_FOREACH(current, &owindows, owindows) {
-            printf("matching: %p / %s\n", current->con, current->con->name);
-        }
-
-    }
-    ;
-
-criteria:
-    criteria criterion
-    | criterion
-    ;
-
-criterion:
-    TOK_CLASS '=' STR
-    {
-        printf("criteria: class = %s\n", $3);
-        current_match.class = regex_new($3);
-        free($3);
-    }
-    | TOK_INSTANCE '=' STR
-    {
-        printf("criteria: instance = %s\n", $3);
-        current_match.instance = regex_new($3);
-        free($3);
-    }
-    | TOK_WINDOW_ROLE '=' STR
-    {
-        printf("criteria: window_role = %s\n", $3);
-        current_match.role = regex_new($3);
-        free($3);
-    }
-    | TOK_CON_ID '=' STR
-    {
-        printf("criteria: id = %s\n", $3);
-        char *end;
-        long parsed = strtol($3, &end, 10);
-        if (parsed == LONG_MIN ||
-            parsed == LONG_MAX ||
-            parsed < 0 ||
-            (end && *end != '\0')) {
-            ELOG("Could not parse con id \"%s\"\n", $3);
-        } else {
-            current_match.con_id = (Con*)parsed;
-            printf("id as int = %p\n", current_match.con_id);
-        }
-    }
-    | TOK_ID '=' STR
-    {
-        printf("criteria: window id = %s\n", $3);
-        char *end;
-        long parsed = strtol($3, &end, 10);
-        if (parsed == LONG_MIN ||
-            parsed == LONG_MAX ||
-            parsed < 0 ||
-            (end && *end != '\0')) {
-            ELOG("Could not parse window id \"%s\"\n", $3);
-        } else {
-            current_match.id = parsed;
-            printf("window id as int = %d\n", current_match.id);
-        }
-    }
-    | TOK_MARK '=' STR
-    {
-        printf("criteria: mark = %s\n", $3);
-        current_match.mark = regex_new($3);
-        free($3);
-    }
-    | TOK_TITLE '=' STR
-    {
-        printf("criteria: title = %s\n", $3);
-        current_match.title = regex_new($3);
-        free($3);
-    }
-    ;
-
-operations:
-    operation
-    | operations ',' operation
-    ;
-
-operation:
-    exec
-    | exit
-    | restart
-    | reload
-    | border
-    | layout
-    | append_layout
-    | move
-    | workspace
-    | focus
-    | kill
-    | open
-    | fullscreen
-    | split
-    | floating
-    | mark
-    | resize
-    | nop
-    | mode
-    ;
-
-exec:
-    TOK_EXEC optional_no_startup_id STR
-    {
-        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
-    {
-        printf("exit, bye bye\n");
-        exit(0);
-    }
-    ;
-
-reload:
-    TOK_RELOAD
-    {
-        printf("reloading\n");
-        kill_configerror_nagbar(false);
-        load_configuration(conn, NULL, true);
-        x_set_i3_atoms();
-        /* Send an IPC event just in case the ws names have changed */
-        ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
-    }
-    ;
-
-restart:
-    TOK_RESTART
-    {
-        printf("restarting i3\n");
-        i3_restart(false);
-    }
-    ;
-
-focus:
-    TOK_FOCUS
-    {
-        if (focused &&
-            focused->type != CT_WORKSPACE &&
-            focused->fullscreen_mode != CF_NONE) {
-            LOG("Cannot change focus while in fullscreen mode.\n");
-            break;
-        }
-
-        owindow *current;
-
-        if (match_is_empty(&current_match)) {
-            ELOG("You have to specify which window/container should be focused.\n");
-            ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
-
-            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);
-            /* If no workspace could be found, this was a dock window.
-             * Just skip it, you cannot focus dock windows. */
-            if (!ws)
-                continue;
-
-            /* 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++;
-        }
-
-        if (count > 1)
-            LOG("WARNING: Your criteria for the focus command matches %d containers, "
-                "while only exactly one container can be focused at a time.\n", count);
-
-        tree_render();
-    }
-    | TOK_FOCUS direction
-    {
-        if (focused &&
-            focused->type != CT_WORKSPACE &&
-            focused->fullscreen_mode != CF_NONE) {
-            LOG("Cannot change focus while in fullscreen mode.\n");
-            break;
-        }
-
-        int direction = $2;
-        switch (direction) {
-            case TOK_LEFT:
-                LOG("Focusing left\n");
-                tree_next('p', HORIZ);
-                break;
-            case TOK_RIGHT:
-                LOG("Focusing right\n");
-                tree_next('n', HORIZ);
-                break;
-            case TOK_UP:
-                LOG("Focusing up\n");
-                tree_next('p', VERT);
-                break;
-            case TOK_DOWN:
-                LOG("Focusing down\n");
-                tree_next('n', VERT);
-                break;
-            default:
-                ELOG("Invalid focus direction (%d)\n", direction);
-                break;
-        }
-
-        tree_render();
-    }
-    | TOK_FOCUS window_mode
-    {
-        if (focused &&
-            focused->type != CT_WORKSPACE &&
-            focused->fullscreen_mode != CF_NONE) {
-            LOG("Cannot change focus while in fullscreen mode.\n");
-            break;
-        }
-
-        printf("should focus: ");
-
-        if ($2 == TOK_TILING)
-            printf("tiling\n");
-        else if ($2 == TOK_FLOATING)
-            printf("floating\n");
-        else printf("mode toggle\n");
-
-        Con *ws = con_get_workspace(focused);
-        Con *current;
-        if (ws != NULL) {
-            int to_focus = $2;
-            if ($2 == TOK_MODE_TOGGLE) {
-                current = TAILQ_FIRST(&(ws->focus_head));
-                if (current != NULL && current->type == CT_FLOATING_CON)
-                    to_focus = TOK_TILING;
-                else to_focus = TOK_FLOATING;
-            }
-            TAILQ_FOREACH(current, &(ws->focus_head), focused) {
-                if ((to_focus == TOK_FLOATING && current->type != CT_FLOATING_CON) ||
-                    (to_focus == TOK_TILING && current->type == CT_FLOATING_CON))
-                    continue;
-
-                con_focus(con_descend_focused(current));
-                break;
-            }
-        }
-
-        tree_render();
-    }
-    | TOK_FOCUS level
-    {
-        if (focused &&
-            focused->type != CT_WORKSPACE &&
-            focused->fullscreen_mode != CF_NONE) {
-            LOG("Cannot change focus while in fullscreen mode.\n");
-            break;
-        }
-
-        if ($2 == TOK_PARENT)
-            level_up();
-        else level_down();
-
-        tree_render();
-    }
-    ;
-
-window_mode:
-    TOK_TILING        { $$ = TOK_TILING; }
-    | TOK_FLOATING    { $$ = TOK_FLOATING; }
-    | TOK_MODE_TOGGLE { $$ = TOK_MODE_TOGGLE; }
-    ;
-
-level:
-    TOK_PARENT  { $$ = TOK_PARENT; }
-    | TOK_CHILD { $$ = TOK_CHILD;  }
-    ;
-
-kill:
-    TOK_KILL optional_kill_mode
-    {
-        owindow *current;
-
-        printf("killing!\n");
-        /* check if the match is empty, not if the result is empty */
-        if (match_is_empty(&current_match))
-            tree_close_con($2);
-        else {
-            TAILQ_FOREACH(current, &owindows, owindows) {
-                printf("matching: %p / %s\n", current->con, current->con->name);
-                tree_close(current->con, $2, false, false);
-            }
-        }
-
-        tree_render();
-    }
-    ;
-
-optional_kill_mode:
-    /* empty */             { $$ = KILL_WINDOW; }
-    | TOK_WINDOW { $$ = KILL_WINDOW; }
-    | TOK_CLIENT { $$ = KILL_CLIENT; }
-    ;
-
-workspace:
-    TOK_WORKSPACE TOK_NEXT
-    {
-        workspace_show(workspace_next());
-        tree_render();
-    }
-    | TOK_WORKSPACE TOK_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);
-
-        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();
-    }
-    ;
-
-open:
-    TOK_OPEN
-    {
-        printf("opening new container\n");
-        Con *con = tree_open_con(NULL, NULL);
-        con_focus(con);
-        sasprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con);
-
-        tree_render();
-    }
-    ;
-
-fullscreen:
-    TOK_FULLSCREEN fullscreen_mode
-    {
-        printf("toggling fullscreen, mode = %s\n", ($2 == CF_OUTPUT ? "normal" : "global"));
-        owindow *current;
-
-        HANDLE_EMPTY_MATCH;
-
-        TAILQ_FOREACH(current, &owindows, owindows) {
-            printf("matching: %p / %s\n", current->con, current->con->name);
-            con_toggle_fullscreen(current->con, $2);
-        }
-
-        tree_render();
-    }
-    ;
-
-fullscreen_mode:
-    /* empty */  { $$ = CF_OUTPUT; }
-    | TOK_GLOBAL { $$ = CF_GLOBAL; }
-    ;
-
-split:
-    TOK_SPLIT split_direction
-    {
-        /* TODO: use matches */
-        printf("splitting in direction %c\n", $2);
-        tree_split(focused, ($2 == 'v' ? VERT : HORIZ));
-
-        tree_render();
-    }
-    ;
-
-split_direction:
-    TOK_HORIZONTAL  { $$ = 'h'; }
-    | 'h'           { $$ = 'h'; }
-    | TOK_VERTICAL  { $$ = 'v'; }
-    | 'v'           { $$ = 'v'; }
-    ;
-
-floating:
-    TOK_FLOATING boolean
-    {
-        HANDLE_EMPTY_MATCH;
-
-        owindow *current;
-        TAILQ_FOREACH(current, &owindows, owindows) {
-            printf("matching: %p / %s\n", current->con, current->con->name);
-            if ($2 == TOK_TOGGLE) {
-                printf("should toggle mode\n");
-                toggle_floating_mode(current->con, false);
-            } else {
-                printf("should switch mode to %s\n", ($2 == TOK_FLOATING ? "floating" : "tiling"));
-                if ($2 == TOK_ENABLE) {
-                    floating_enable(current->con, false);
-                } else {
-                    floating_disable(current->con, false);
-                }
-            }
-        }
-
-        tree_render();
-    }
-    ;
-
-boolean:
-    TOK_ENABLE    { $$ = TOK_ENABLE; }
-    | TOK_DISABLE { $$ = TOK_DISABLE; }
-    | TOK_TOGGLE  { $$ = TOK_TOGGLE; }
-    ;
-
-border:
-    TOK_BORDER border_style
-    {
-        printf("border style should be changed to %d\n", $2);
-        owindow *current;
-
-        HANDLE_EMPTY_MATCH;
-
-        TAILQ_FOREACH(current, &owindows, owindows) {
-            printf("matching: %p / %s\n", current->con, current->con->name);
-            int border_style = current->con->border_style;
-            if ($2 == TOK_TOGGLE) {
-                border_style++;
-                border_style %= 3;
-            } else border_style = $2;
-            con_set_border_style(current->con, border_style);
-        }
-
-        tree_render();
-    }
-    ;
-
-border_style:
-    TOK_NORMAL      { $$ = BS_NORMAL; }
-    | TOK_NONE      { $$ = BS_NONE; }
-    | TOK_1PIXEL    { $$ = BS_1PIXEL; }
-    | TOK_TOGGLE    { $$ = TOK_TOGGLE; }
-    ;
-
-move:
-    TOK_MOVE direction resize_px
-    {
-        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);
-            Rect newrect = focused->parent->rect;
-            if (direction == TOK_LEFT) {
-                newrect.x -= px;
-            } else if (direction == TOK_RIGHT) {
-                newrect.x += px;
-            } else if (direction == TOK_UP) {
-                newrect.y -= px;
-            } else if (direction == TOK_DOWN) {
-                newrect.y += px;
-            }
-            floating_reposition(focused->parent, newrect);
-        } else {
-            tree_move(direction);
-            tree_render();
-        }
-    }
-    | TOK_MOVE TOK_WORKSPACE STR
-    {
-        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);
-        free($3);
-
-        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_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:
-    TOK_APPEND_LAYOUT STR
-    {
-        printf("restoring \"%s\"\n", $2);
-        tree_append_json($2);
-        free($2);
-        tree_render();
-    }
-    ;
-
-layout:
-    TOK_LAYOUT layout_mode
-    {
-        printf("changing layout to %d\n", $2);
-        owindow *current;
-
-        /* check if the match is empty, not if the result is empty */
-        if (match_is_empty(&current_match))
-            con_set_layout(focused->parent, $2);
-        else {
-            TAILQ_FOREACH(current, &owindows, owindows) {
-                printf("matching: %p / %s\n", current->con, current->con->name);
-                con_set_layout(current->con, $2);
-            }
-        }
-
-        tree_render();
-    }
-    ;
-
-layout_mode:
-    TOK_DEFAULT   { $$ = L_DEFAULT; }
-    | TOK_STACKED { $$ = L_STACKED; }
-    | TOK_TABBED  { $$ = L_TABBED; }
-    ;
-
-mark:
-    TOK_MARK STR
-    {
-        printf("Clearing all windows which have that mark first\n");
-
-        Con *con;
-        TAILQ_FOREACH(con, &all_cons, all_cons) {
-            if (con->mark && strcmp(con->mark, $2) == 0)
-                FREE(con->mark);
-        }
-
-        printf("marking window with str %s\n", $2);
-        owindow *current;
-
-        HANDLE_EMPTY_MATCH;
-
-        TAILQ_FOREACH(current, &owindows, owindows) {
-            printf("matching: %p / %s\n", current->con, current->con->name);
-            current->con->mark = $2;
-        }
-
-        tree_render();
-    }
-    ;
-
-nop:
-    TOK_NOP STR
-    {
-        printf("-------------------------------------------------\n");
-        printf("  NOP: %s\n", $2);
-        printf("-------------------------------------------------\n");
-        free($2);
-    }
-    ;
-
-resize:
-    TOK_RESIZE resize_way direction resize_px resize_tiling
-    {
-        /* resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt] */
-        printf("resizing in way %d, direction %d, px %d or ppt %d\n", $2, $3, $4, $5);
-        int direction = $3;
-        int px = $4;
-        int ppt = $5;
-        if ($2 == TOK_SHRINK) {
-            px *= -1;
-            ppt *= -1;
-        }
-
-        Con *floating_con;
-        if ((floating_con = con_inside_floating(focused))) {
-            printf("floating resize\n");
-            if (direction == TOK_UP) {
-                floating_con->rect.y -= px;
-                floating_con->rect.height += px;
-            } else if (direction == TOK_DOWN) {
-                floating_con->rect.height += px;
-            } else if (direction == TOK_LEFT) {
-                floating_con->rect.x -= px;
-                floating_con->rect.width += px;
-            } else {
-                floating_con->rect.width += px;
-            }
-        } else {
-            LOG("tiling resize\n");
-            /* get the appropriate current container (skip stacked/tabbed cons) */
-            Con *current = focused;
-            while (current->parent->layout == L_STACKED ||
-                   current->parent->layout == L_TABBED)
-                current = current->parent;
-
-            /* Then further go up until we find one with the matching orientation. */
-            orientation_t search_orientation =
-                (direction == TOK_LEFT || direction == TOK_RIGHT ? HORIZ : VERT);
-
-            while (current->type != CT_WORKSPACE &&
-                   current->type != CT_FLOATING_CON &&
-                   current->parent->orientation != search_orientation)
-                current = current->parent;
-
-            /* get the default percentage */
-            int children = con_num_children(current->parent);
-            Con *other;
-            LOG("ins. %d children\n", children);
-            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 {
-                other = TAILQ_NEXT(current, nodes);
-            }
-            if (other == TAILQ_END(workspaces)) {
-                LOG("No other container in this direction found, cannot resize.\n");
-                break;
-            }
-            LOG("other->percent = %f\n", other->percent);
-            LOG("current->percent before = %f\n", current->percent);
-            if (current->percent == 0.0)
-                current->percent = percentage;
-            if (other->percent == 0.0)
-                other->percent = percentage;
-            double new_current_percent = current->percent + ((double)ppt / 100.0);
-            double new_other_percent = other->percent - ((double)ppt / 100.0);
-            LOG("new_current_percent = %f\n", new_current_percent);
-            LOG("new_other_percent = %f\n", new_other_percent);
-            /* Ensure that the new percentages are positive and greater than
-             * 0.05 to have a reasonable minimum size. */
-            if (definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON) &&
-                definitelyGreaterThan(new_other_percent, 0.05, DBL_EPSILON)) {
-                current->percent += ((double)ppt / 100.0);
-                other->percent -= ((double)ppt / 100.0);
-                LOG("current->percent after = %f\n", current->percent);
-                LOG("other->percent after = %f\n", other->percent);
-            } else {
-                LOG("Not resizing, already at minimum size\n");
-            }
-        }
-
-        tree_render();
-    }
-    ;
-
-resize_px:
-    /* empty */
-    {
-        $$ = 10;
-    }
-    | NUMBER TOK_PX
-    {
-        $$ = $1;
-    }
-    ;
-
-resize_tiling:
-    /* empty */
-    {
-        $$ = 10;
-    }
-    | TOK_OR NUMBER TOK_PPT
-    {
-        $$ = $2;
-    }
-    ;
-
-resize_way:
-    TOK_GROW        { $$ = TOK_GROW; }
-    | TOK_SHRINK    { $$ = TOK_SHRINK; }
-    ;
-
-direction:
-    TOK_UP          { $$ = TOK_UP; }
-    | TOK_DOWN      { $$ = TOK_DOWN; }
-    | TOK_LEFT      { $$ = TOK_LEFT; }
-    | TOK_RIGHT     { $$ = TOK_RIGHT; }
-    ;
-
-mode:
-    TOK_MODE STR
-    {
-        switch_mode($2);
-    }
-    ;
diff --git a/src/commands.c b/src/commands.c
new file mode 100644 (file)
index 0000000..8fc80a1
--- /dev/null
@@ -0,0 +1,1298 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * commands.c: all command functions (see commands_parser.c)
+ *
+ */
+#include <float.h>
+#include <stdarg.h>
+
+#include "all.h"
+
+/** When the command did not include match criteria (!), we use the currently
+ * focused command. Do not confuse this case with a command which included
+ * criteria but which did not match any windows. This macro has to be called in
+ * every command.
+ */
+#define HANDLE_EMPTY_MATCH do { \
+    if (match_is_empty(current_match)) { \
+        owindow *ow = smalloc(sizeof(owindow)); \
+        ow->con = focused; \
+        TAILQ_INIT(&owindows); \
+        TAILQ_INSERT_TAIL(&owindows, ow, owindows); \
+    } \
+} while (0)
+
+static owindows_head owindows;
+
+/*
+ * Returns true if a is definitely greater than b (using the given epsilon)
+ *
+ */
+static bool definitelyGreaterThan(float a, float b, float epsilon) {
+    return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
+}
+
+/*
+ * Returns an 'output' corresponding to one of left/right/down/up or a specific
+ * output name.
+ *
+ */
+static Output *get_output_from_string(Output *current_output, const char *output_str) {
+    Output *output;
+
+    if (strcasecmp(output_str, "left") == 0) {
+        output = get_output_next(D_LEFT, current_output);
+        if (!output)
+            output = get_output_most(D_RIGHT, current_output);
+    } else if (strcasecmp(output_str, "right") == 0) {
+        output = get_output_next(D_RIGHT, current_output);
+        if (!output)
+            output = get_output_most(D_LEFT, current_output);
+    } else if (strcasecmp(output_str, "up") == 0) {
+        output = get_output_next(D_UP, current_output);
+        if (!output)
+            output = get_output_most(D_DOWN, current_output);
+    } else if (strcasecmp(output_str, "down") == 0) {
+        output = get_output_next(D_DOWN, current_output);
+        if (!output)
+            output = get_output_most(D_UP, current_output);
+    } else output = get_output_by_name(output_str);
+
+    return output;
+}
+
+// This code is commented out because we might recycle it for popping up error
+// messages on parser errors.
+#if 0
+static pid_t migration_pid = -1;
+
+/*
+ * Handler which will be called when we get a SIGCHLD for the nagbar, meaning
+ * it exited (or could not be started, depending on the exit code).
+ *
+ */
+static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
+    ev_child_stop(EV_A_ watcher);
+    if (!WIFEXITED(watcher->rstatus)) {
+        fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
+        return;
+    }
+
+    int exitcode = WEXITSTATUS(watcher->rstatus);
+    printf("i3-nagbar process exited with status %d\n", exitcode);
+    if (exitcode == 2) {
+        fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
+    }
+
+    migration_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 (migration_pid != -1) {
+        LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", migration_pid);
+        kill(migration_pid, SIGKILL);
+    }
+}
+#endif
+
+void cmd_MIGRATION_start_nagbar() {
+    if (migration_pid != -1) {
+        fprintf(stderr, "i3-nagbar already running.\n");
+        return;
+    }
+    fprintf(stderr, "Starting i3-nagbar, command parsing differs from expected output.\n");
+    ELOG("Please report this on IRC or in the bugtracker. Make sure to include the full debug level logfile:\n");
+    ELOG("i3-dump-log | gzip -9c > /tmp/i3.log.gz\n");
+    ELOG("FYI: Your i3 version is " I3_VERSION "\n");
+    migration_pid = fork();
+    if (migration_pid == -1) {
+        warn("Could not fork()");
+        return;
+    }
+
+    /* child */
+    if (migration_pid == 0) {
+        char *pageraction;
+        sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename);
+        char *argv[] = {
+            NULL, /* will be replaced by the executable path */
+            "-t",
+            "error",
+            "-m",
+            "You found a parsing error. Please, please, please, report it!",
+            "-b",
+            "show errors",
+            pageraction,
+            NULL
+        };
+        exec_i3_utility("i3-nagbar", argv);
+    }
+
+    /* parent */
+    /* install a child watcher */
+    ev_child *child = smalloc(sizeof(ev_child));
+    ev_child_init(child, &nagbar_exited, migration_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
+}
+
+#endif
+
+/*******************************************************************************
+ * Criteria functions.
+ ******************************************************************************/
+
+/*
+ * Initializes the specified 'Match' data structure and the initial state of
+ * commands.c for matching target windows of a command.
+ *
+ */
+void cmd_criteria_init(I3_CMD) {
+    Con *con;
+    owindow *ow;
+
+    DLOG("Initializing criteria, current_match = %p\n", current_match);
+    match_init(current_match);
+    while (!TAILQ_EMPTY(&owindows)) {
+        ow = TAILQ_FIRST(&owindows);
+        TAILQ_REMOVE(&owindows, ow, owindows);
+        free(ow);
+    }
+    TAILQ_INIT(&owindows);
+    /* copy all_cons */
+    TAILQ_FOREACH(con, &all_cons, all_cons) {
+        ow = smalloc(sizeof(owindow));
+        ow->con = con;
+        TAILQ_INSERT_TAIL(&owindows, ow, owindows);
+    }
+}
+
+/*
+ * A match specification just finished (the closing square bracket was found),
+ * so we filter the list of owindows.
+ *
+ */
+void cmd_criteria_match_windows(I3_CMD) {
+    owindow *next, *current;
+
+    DLOG("match specification finished, matching...\n");
+    /* copy the old list head to iterate through it and start with a fresh
+     * list which will contain only matching windows */
+    struct owindows_head old = owindows;
+    TAILQ_INIT(&owindows);
+    for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) {
+        /* make a copy of the next pointer and advance the pointer to the
+         * next element as we are going to invalidate the element’s
+         * next/prev pointers by calling TAILQ_INSERT_TAIL later */
+        current = next;
+        next = TAILQ_NEXT(next, owindows);
+
+        DLOG("checking if con %p / %s matches\n", current->con, current->con->name);
+        if (current_match->con_id != NULL) {
+            if (current_match->con_id == current->con) {
+                DLOG("matches container!\n");
+                TAILQ_INSERT_TAIL(&owindows, current, owindows);
+            }
+        } else if (current_match->mark != NULL && current->con->mark != NULL &&
+                   regex_matches(current_match->mark, current->con->mark)) {
+            DLOG("match by mark\n");
+            TAILQ_INSERT_TAIL(&owindows, current, owindows);
+        } else {
+            if (current->con->window == NULL)
+                continue;
+            if (match_matches_window(current_match, current->con->window)) {
+                DLOG("matches window!\n");
+                TAILQ_INSERT_TAIL(&owindows, current, owindows);
+            } else {
+                DLOG("doesnt match\n");
+                free(current);
+            }
+        }
+    }
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+    }
+}
+
+/*
+ * Interprets a ctype=cvalue pair and adds it to the current match
+ * specification.
+ *
+ */
+void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) {
+    DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue);
+
+    if (strcmp(ctype, "class") == 0) {
+        current_match->class = regex_new(cvalue);
+        return;
+    }
+
+    if (strcmp(ctype, "instance") == 0) {
+        current_match->instance = regex_new(cvalue);
+        return;
+    }
+
+    if (strcmp(ctype, "window_role") == 0) {
+        current_match->role = regex_new(cvalue);
+        return;
+    }
+
+    if (strcmp(ctype, "con_id") == 0) {
+        char *end;
+        long parsed = strtol(cvalue, &end, 10);
+        if (parsed == LONG_MIN ||
+            parsed == LONG_MAX ||
+            parsed < 0 ||
+            (end && *end != '\0')) {
+            ELOG("Could not parse con id \"%s\"\n", cvalue);
+        } else {
+            current_match->con_id = (Con*)parsed;
+            printf("id as int = %p\n", current_match->con_id);
+        }
+        return;
+    }
+
+    if (strcmp(ctype, "id") == 0) {
+        char *end;
+        long parsed = strtol(cvalue, &end, 10);
+        if (parsed == LONG_MIN ||
+            parsed == LONG_MAX ||
+            parsed < 0 ||
+            (end && *end != '\0')) {
+            ELOG("Could not parse window id \"%s\"\n", cvalue);
+        } else {
+            current_match->id = parsed;
+            printf("window id as int = %d\n", current_match->id);
+        }
+        return;
+    }
+
+    if (strcmp(ctype, "con_mark") == 0) {
+        current_match->mark = regex_new(cvalue);
+        return;
+    }
+
+    if (strcmp(ctype, "title") == 0) {
+        current_match->title = regex_new(cvalue);
+        return;
+    }
+
+    if (strcmp(ctype, "urgent") == 0) {
+        if (strcasecmp(cvalue, "latest") == 0 ||
+            strcasecmp(cvalue, "newest") == 0 ||
+            strcasecmp(cvalue, "recent") == 0 ||
+            strcasecmp(cvalue, "last") == 0) {
+            current_match->urgent = U_LATEST;
+        } else if (strcasecmp(cvalue, "oldest") == 0 ||
+                   strcasecmp(cvalue, "first") == 0) {
+            current_match->urgent = U_OLDEST;
+        }
+        return;
+    }
+
+    ELOG("Unknown criterion: %s\n", ctype);
+}
+
+/*
+ * Implementation of 'move [window|container] [to] workspace
+ * next|prev|next_on_output|prev_on_output'.
+ *
+ */
+void cmd_move_con_to_workspace(I3_CMD, char *which) {
+    owindow *current;
+
+    DLOG("which=%s\n", which);
+
+    HANDLE_EMPTY_MATCH;
+
+    /* get the workspace */
+    Con *ws;
+    if (strcmp(which, "next") == 0)
+        ws = workspace_next();
+    else if (strcmp(which, "prev") == 0)
+        ws = workspace_prev();
+    else if (strcmp(which, "next_on_output") == 0)
+        ws = workspace_next_on_output();
+    else if (strcmp(which, "prev_on_output") == 0)
+        ws = workspace_prev_on_output();
+    else {
+        ELOG("BUG: called with which=%s\n", which);
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+        con_move_to_workspace(current->con, ws, true, false);
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'move [window|container] [to] workspace <name>'.
+ *
+ */
+void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
+    if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) {
+        LOG("You cannot switch to the i3 internal workspaces.\n");
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    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) {
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    LOG("should move window to workspace %s\n", name);
+    /* get the workspace */
+    Con *ws = workspace_get(name, NULL);
+
+    HANDLE_EMPTY_MATCH;
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+        con_move_to_workspace(current->con, ws, true, false);
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'resize grow|shrink <direction> [<px> px] [or <ppt> ppt]'.
+ *
+ */
+void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resize_ppt) {
+    /* resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt] */
+    DLOG("resizing in way %s, direction %s, px %s or ppt %s\n", way, direction, resize_px, resize_ppt);
+    // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking
+    int px = atoi(resize_px);
+    int ppt = atoi(resize_ppt);
+    if (strcmp(way, "shrink") == 0) {
+        px *= -1;
+        ppt *= -1;
+    }
+
+    Con *floating_con;
+    if ((floating_con = con_inside_floating(focused))) {
+        printf("floating resize\n");
+        if (strcmp(direction, "up") == 0) {
+            floating_con->rect.y -= px;
+            floating_con->rect.height += px;
+        } else if (strcmp(direction, "down") == 0) {
+            floating_con->rect.height += px;
+        } else if (strcmp(direction, "left") == 0) {
+            floating_con->rect.x -= px;
+            floating_con->rect.width += px;
+        } else {
+            floating_con->rect.width += px;
+        }
+    } else {
+        LOG("tiling resize\n");
+        /* get the appropriate current container (skip stacked/tabbed cons) */
+        Con *current = focused;
+        while (current->parent->layout == L_STACKED ||
+               current->parent->layout == L_TABBED)
+            current = current->parent;
+
+        /* Then further go up until we find one with the matching orientation. */
+        orientation_t search_orientation =
+            (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT);
+
+        while (current->type != CT_WORKSPACE &&
+               current->type != CT_FLOATING_CON &&
+               current->parent->orientation != search_orientation)
+            current = current->parent;
+
+        /* get the default percentage */
+        int children = con_num_children(current->parent);
+        Con *other;
+        LOG("ins. %d children\n", children);
+        double percentage = 1.0 / children;
+        LOG("default percentage = %f\n", percentage);
+
+        orientation_t orientation = current->parent->orientation;
+
+        if ((orientation == HORIZ &&
+             (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
+            (orientation == VERT &&
+             (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) {
+            LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
+                (orientation == HORIZ ? "horizontal" : "vertical"));
+            cmd_output->json_output = sstrdup("{\"sucess\": false}");
+            return;
+        }
+
+        if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) {
+            other = TAILQ_PREV(current, nodes_head, nodes);
+        } else {
+            other = TAILQ_NEXT(current, nodes);
+        }
+        if (other == TAILQ_END(workspaces)) {
+            LOG("No other container in this direction found, cannot resize.\n");
+            cmd_output->json_output = sstrdup("{\"sucess\": false}");
+            return;
+        }
+        LOG("other->percent = %f\n", other->percent);
+        LOG("current->percent before = %f\n", current->percent);
+        if (current->percent == 0.0)
+            current->percent = percentage;
+        if (other->percent == 0.0)
+            other->percent = percentage;
+        double new_current_percent = current->percent + ((double)ppt / 100.0);
+        double new_other_percent = other->percent - ((double)ppt / 100.0);
+        LOG("new_current_percent = %f\n", new_current_percent);
+        LOG("new_other_percent = %f\n", new_other_percent);
+        /* Ensure that the new percentages are positive and greater than
+         * 0.05 to have a reasonable minimum size. */
+        if (definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON) &&
+            definitelyGreaterThan(new_other_percent, 0.05, DBL_EPSILON)) {
+            current->percent += ((double)ppt / 100.0);
+            other->percent -= ((double)ppt / 100.0);
+            LOG("current->percent after = %f\n", current->percent);
+            LOG("other->percent after = %f\n", other->percent);
+        } else {
+            LOG("Not resizing, already at minimum size\n");
+        }
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'border normal|none|1pixel|toggle'.
+ *
+ */
+void cmd_border(I3_CMD, char *border_style_str) {
+    DLOG("border style should be changed to %s\n", border_style_str);
+    owindow *current;
+
+    HANDLE_EMPTY_MATCH;
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+        int border_style = current->con->border_style;
+        if (strcmp(border_style_str, "toggle") == 0) {
+            border_style++;
+            border_style %= 3;
+        } else {
+            if (strcmp(border_style_str, "normal") == 0)
+                border_style = BS_NORMAL;
+            else if (strcmp(border_style_str, "none") == 0)
+                border_style = BS_NONE;
+            else if (strcmp(border_style_str, "1pixel") == 0)
+                border_style = BS_1PIXEL;
+            else {
+                ELOG("BUG: called with border_style=%s\n", border_style_str);
+                cmd_output->json_output = sstrdup("{\"sucess\": false}");
+                return;
+            }
+        }
+        con_set_border_style(current->con, border_style);
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'nop <comment>'.
+ *
+ */
+void cmd_nop(I3_CMD, char *comment) {
+    LOG("-------------------------------------------------\n");
+    LOG("  NOP: %s\n", comment);
+    LOG("-------------------------------------------------\n");
+}
+
+/*
+ * Implementation of 'append_layout <path>'.
+ *
+ */
+void cmd_append_layout(I3_CMD, char *path) {
+    LOG("Appending layout \"%s\"\n", path);
+    tree_append_json(path);
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'workspace next|prev|next_on_output|prev_on_output'.
+ *
+ */
+void cmd_workspace(I3_CMD, char *which) {
+    Con *ws;
+
+    DLOG("which=%s\n", which);
+
+    if (strcmp(which, "next") == 0)
+        ws = workspace_next();
+    else if (strcmp(which, "prev") == 0)
+        ws = workspace_prev();
+    else if (strcmp(which, "next_on_output") == 0)
+        ws = workspace_next_on_output();
+    else if (strcmp(which, "prev_on_output") == 0)
+        ws = workspace_prev_on_output();
+    else {
+        ELOG("BUG: called with which=%s\n", which);
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    workspace_show(ws);
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'workspace back_and_forth'.
+ *
+ */
+void cmd_workspace_back_and_forth(I3_CMD) {
+    workspace_back_and_forth();
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'workspace <name>'
+ *
+ */
+void cmd_workspace_name(I3_CMD, char *name) {
+    if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) {
+        LOG("You cannot switch to the i3 internal workspaces.\n");
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    DLOG("should switch to workspace %s\n", name);
+
+    Con *ws = con_get_workspace(focused);
+
+    /* Check if the command wants to switch to the current workspace */
+    if (strcmp(ws->name, name) == 0) {
+        DLOG("This workspace is already focused.\n");
+        if (config.workspace_auto_back_and_forth) {
+            workspace_back_and_forth();
+            tree_render();
+        }
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    workspace_show_by_name(name);
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'mark <mark>'
+ *
+ */
+void cmd_mark(I3_CMD, char *mark) {
+    DLOG("Clearing all windows which have that mark first\n");
+
+    Con *con;
+    TAILQ_FOREACH(con, &all_cons, all_cons) {
+        if (con->mark && strcmp(con->mark, mark) == 0)
+            FREE(con->mark);
+    }
+
+    DLOG("marking window with str %s\n", mark);
+    owindow *current;
+
+    HANDLE_EMPTY_MATCH;
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+        current->con->mark = sstrdup(mark);
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'mode <string>'.
+ *
+ */
+void cmd_mode(I3_CMD, char *mode) {
+    DLOG("mode=%s\n", mode);
+    switch_mode(mode);
+
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'move [window|container] [to] output <str>'.
+ *
+ */
+void cmd_move_con_to_output(I3_CMD, char *name) {
+    owindow *current;
+
+    DLOG("should move window to output %s\n", name);
+
+    HANDLE_EMPTY_MATCH;
+
+    /* get the output */
+    Output *current_output = NULL;
+    Output *output;
+
+    // TODO: fix the handling of criteria
+    TAILQ_FOREACH(current, &owindows, owindows)
+        current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
+
+    assert(current_output != NULL);
+
+    // TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser
+    if (strcasecmp(name, "up") == 0)
+        output = get_output_next(D_UP, current_output);
+    else if (strcasecmp(name, "down") == 0)
+        output = get_output_next(D_DOWN, current_output);
+    else if (strcasecmp(name, "left") == 0)
+        output = get_output_next(D_LEFT, current_output);
+    else if (strcasecmp(name, "right") == 0)
+        output = get_output_next(D_RIGHT, current_output);
+    else
+        output = get_output_by_name(name);
+
+    if (!output) {
+        LOG("No such output found.\n");
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    /* get visible workspace on output */
+    Con *ws = NULL;
+    GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
+    if (!ws) {
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+        con_move_to_workspace(current->con, ws, true, false);
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'floating enable|disable|toggle'
+ *
+ */
+void cmd_floating(I3_CMD, char *floating_mode) {
+    owindow *current;
+
+    DLOG("floating_mode=%s\n", floating_mode);
+
+    HANDLE_EMPTY_MATCH;
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+        if (strcmp(floating_mode, "toggle") == 0) {
+            DLOG("should toggle mode\n");
+            toggle_floating_mode(current->con, false);
+        } else {
+            DLOG("should switch mode to %s\n", floating_mode);
+            if (strcmp(floating_mode, "enable") == 0) {
+                floating_enable(current->con, false);
+            } else {
+                floating_disable(current->con, false);
+            }
+        }
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'move workspace to [output] <str>'.
+ *
+ */
+void cmd_move_workspace_to_output(I3_CMD, char *name) {
+    DLOG("should move workspace to output %s\n", name);
+
+    HANDLE_EMPTY_MATCH;
+
+    owindow *current;
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        Output *current_output = get_output_containing(current->con->rect.x,
+                                                       current->con->rect.y);
+        Output *output = get_output_from_string(current_output, name);
+        if (!output) {
+            LOG("No such output\n");
+            cmd_output->json_output = sstrdup("{\"sucess\": false}");
+            return;
+        }
+
+        Con *content = output_get_content(output->con);
+        LOG("got output %p with content %p\n", output, content);
+
+        Con *ws = con_get_workspace(current->con);
+        LOG("should move workspace %p / %s\n", ws, ws->name);
+
+        if (con_num_children(ws->parent) == 1) {
+            LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name);
+
+            /* check if we can find a workspace assigned to this output */
+            bool used_assignment = false;
+            struct Workspace_Assignment *assignment;
+            TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
+                if (strcmp(assignment->output, current_output->name) != 0)
+                    continue;
+
+                /* check if this workspace is already attached to the tree */
+                Con *workspace = NULL, *out;
+                TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
+                    GREP_FIRST(workspace, output_get_content(out),
+                               !strcasecmp(child->name, assignment->name));
+                if (workspace != NULL)
+                    continue;
+
+                /* so create the workspace referenced to by this assignment */
+                LOG("Creating workspace from assignment %s.\n", assignment->name);
+                workspace_get(assignment->name, NULL);
+                used_assignment = true;
+                break;
+            }
+
+            /* if we couldn't create the workspace using an assignment, create
+             * it on the output */
+            if (!used_assignment)
+                create_workspace_on_output(current_output, ws->parent);
+
+            /* notify the IPC listeners */
+            ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
+        }
+
+        /* detach from the old output and attach to the new output */
+        bool workspace_was_visible = workspace_is_visible(ws);
+        Con *old_content = ws->parent;
+        con_detach(ws);
+        if (workspace_was_visible) {
+            /* The workspace which we just detached was visible, so focus
+             * the next one in the focus-stack. */
+            Con *focus_ws = TAILQ_FIRST(&(old_content->focus_head));
+            LOG("workspace was visible, focusing %p / %s now\n", focus_ws, focus_ws->name);
+            workspace_show(focus_ws);
+        }
+        con_attach(ws, content, false);
+
+        /* fix the coordinates of the floating containers */
+        Con *floating_con;
+        TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows)
+            floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect));
+
+        ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"move\"}");
+        if (workspace_was_visible) {
+            /* Focus the moved workspace on the destination output. */
+            workspace_show(ws);
+        }
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'split v|h|vertical|horizontal'.
+ *
+ */
+void cmd_split(I3_CMD, char *direction) {
+    /* TODO: use matches */
+    LOG("splitting in direction %c\n", direction[0]);
+    tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ));
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementaiton of 'kill [window|client]'.
+ *
+ */
+void cmd_kill(I3_CMD, char *kill_mode_str) {
+    if (kill_mode_str == NULL)
+        kill_mode_str = "window";
+    owindow *current;
+
+    DLOG("kill_mode=%s\n", kill_mode_str);
+
+    int kill_mode;
+    if (strcmp(kill_mode_str, "window") == 0)
+        kill_mode = KILL_WINDOW;
+    else if (strcmp(kill_mode_str, "client") == 0)
+        kill_mode = KILL_CLIENT;
+    else {
+        ELOG("BUG: called with kill_mode=%s\n", kill_mode_str);
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    /* check if the match is empty, not if the result is empty */
+    if (match_is_empty(current_match))
+        tree_close_con(kill_mode);
+    else {
+        TAILQ_FOREACH(current, &owindows, owindows) {
+            DLOG("matching: %p / %s\n", current->con, current->con->name);
+            tree_close(current->con, kill_mode, false, false);
+        }
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'exec [--no-startup-id] <command>'.
+ *
+ */
+void cmd_exec(I3_CMD, char *nosn, char *command) {
+    bool no_startup_id = (nosn != NULL);
+
+    DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
+    start_application(command, no_startup_id);
+
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'focus left|right|up|down'.
+ *
+ */
+void cmd_focus_direction(I3_CMD, char *direction) {
+    if (focused &&
+        focused->type != CT_WORKSPACE &&
+        focused->fullscreen_mode != CF_NONE) {
+        LOG("Cannot change focus while in fullscreen mode.\n");
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    DLOG("direction = *%s*\n", direction);
+
+    if (strcmp(direction, "left") == 0)
+        tree_next('p', HORIZ);
+    else if (strcmp(direction, "right") == 0)
+        tree_next('n', HORIZ);
+    else if (strcmp(direction, "up") == 0)
+        tree_next('p', VERT);
+    else if (strcmp(direction, "down") == 0)
+        tree_next('n', VERT);
+    else {
+        ELOG("Invalid focus direction (%s)\n", direction);
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'focus tiling|floating|mode_toggle'.
+ *
+ */
+void cmd_focus_window_mode(I3_CMD, char *window_mode) {
+    if (focused &&
+        focused->type != CT_WORKSPACE &&
+        focused->fullscreen_mode != CF_NONE) {
+        LOG("Cannot change focus while in fullscreen mode.\n");
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    DLOG("window_mode = %s\n", window_mode);
+
+    Con *ws = con_get_workspace(focused);
+    Con *current;
+    if (ws != NULL) {
+        if (strcmp(window_mode, "mode_toggle") == 0) {
+            current = TAILQ_FIRST(&(ws->focus_head));
+            if (current != NULL && current->type == CT_FLOATING_CON)
+                window_mode = "tiling";
+            else window_mode = "floating";
+        }
+        TAILQ_FOREACH(current, &(ws->focus_head), focused) {
+            if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) ||
+                (strcmp(window_mode, "tiling") == 0 && current->type == CT_FLOATING_CON))
+                continue;
+
+            con_focus(con_descend_focused(current));
+            break;
+        }
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'focus parent|child'.
+ *
+ */
+void cmd_focus_level(I3_CMD, char *level) {
+    if (focused &&
+        focused->type != CT_WORKSPACE &&
+        focused->fullscreen_mode != CF_NONE) {
+        LOG("Cannot change focus while in fullscreen mode.\n");
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    DLOG("level = %s\n", level);
+
+    if (strcmp(level, "parent") == 0)
+        level_up();
+    else level_down();
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'focus'.
+ *
+ */
+void cmd_focus(I3_CMD) {
+    DLOG("current_match = %p\n", current_match);
+    if (focused &&
+        focused->type != CT_WORKSPACE &&
+        focused->fullscreen_mode != CF_NONE) {
+        LOG("Cannot change focus while in fullscreen mode.\n");
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    owindow *current;
+
+    if (match_is_empty(current_match)) {
+        ELOG("You have to specify which window/container should be focused.\n");
+        ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
+
+        sasprintf(&(cmd_output->json_output),
+                  "{\"success\":false, \"error\":\"You have to "
+                  "specify which window/container should be focused\"}");
+        return;
+    }
+
+    int count = 0;
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        Con *ws = con_get_workspace(current->con);
+        /* If no workspace could be found, this was a dock window.
+         * Just skip it, you cannot focus dock windows. */
+        if (!ws)
+            continue;
+
+        /* 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++;
+    }
+
+    if (count > 1)
+        LOG("WARNING: Your criteria for the focus command matches %d containers, "
+            "while only exactly one container can be focused at a time.\n", count);
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'fullscreen [global]'.
+ *
+ */
+void cmd_fullscreen(I3_CMD, char *fullscreen_mode) {
+    if (fullscreen_mode == NULL)
+        fullscreen_mode = "output";
+    DLOG("toggling fullscreen, mode = %s\n", fullscreen_mode);
+    owindow *current;
+
+    HANDLE_EMPTY_MATCH;
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        printf("matching: %p / %s\n", current->con, current->con->name);
+        con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT));
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'move <direction> [<pixels> [px]]'.
+ *
+ */
+void cmd_move_direction(I3_CMD, char *direction, char *move_px) {
+    // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking
+    int px = atoi(move_px);
+
+    /* TODO: make 'move' work with criteria. */
+    DLOG("moving in direction %s, px %s\n", direction, move_px);
+    if (con_is_floating(focused)) {
+        DLOG("floating move with %d pixels\n", px);
+        Rect newrect = focused->parent->rect;
+        if (strcmp(direction, "left") == 0) {
+            newrect.x -= px;
+        } else if (strcmp(direction, "right") == 0) {
+            newrect.x += px;
+        } else if (strcmp(direction, "up") == 0) {
+            newrect.y -= px;
+        } else if (strcmp(direction, "down") == 0) {
+            newrect.y += px;
+        }
+        floating_reposition(focused->parent, newrect);
+    } else {
+        tree_move((strcmp(direction, "right") == 0 ? D_RIGHT :
+                   (strcmp(direction, "left") == 0 ? D_LEFT :
+                    (strcmp(direction, "up") == 0 ? D_UP :
+                     D_DOWN))));
+        cmd_output->needs_tree_render = true;
+    }
+
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'layout default|stacked|stacking|tabbed'.
+ *
+ */
+void cmd_layout(I3_CMD, char *layout_str) {
+    if (strcmp(layout_str, "stacking") == 0)
+        layout_str = "stacked";
+    DLOG("changing layout to %s\n", layout_str);
+    owindow *current;
+    int layout = (strcmp(layout_str, "default") == 0 ? L_DEFAULT :
+                  (strcmp(layout_str, "stacked") == 0 ? L_STACKED :
+                   L_TABBED));
+
+    /* check if the match is empty, not if the result is empty */
+    if (match_is_empty(current_match))
+        con_set_layout(focused->parent, layout);
+    else {
+        TAILQ_FOREACH(current, &owindows, owindows) {
+            DLOG("matching: %p / %s\n", current->con, current->con->name);
+            con_set_layout(current->con, layout);
+        }
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementaiton of 'exit'.
+ *
+ */
+void cmd_exit(I3_CMD) {
+    LOG("Exiting due to user command.\n");
+    exit(0);
+
+    /* unreached */
+}
+
+/*
+ * Implementaiton of 'reload'.
+ *
+ */
+void cmd_reload(I3_CMD) {
+    LOG("reloading\n");
+    kill_configerror_nagbar(false);
+    load_configuration(conn, NULL, true);
+    x_set_i3_atoms();
+    /* Send an IPC event just in case the ws names have changed */
+    ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
+
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementaiton of 'restart'.
+ *
+ */
+void cmd_restart(I3_CMD) {
+    LOG("restarting i3\n");
+    i3_restart(false);
+
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementaiton of 'open'.
+ *
+ */
+void cmd_open(I3_CMD) {
+    LOG("opening new container\n");
+    Con *con = tree_open_con(NULL, NULL);
+    con_focus(con);
+    sasprintf(&(cmd_output->json_output),
+              "{\"success\":true, \"id\":%ld}", (long int)con);
+
+    cmd_output->needs_tree_render = true;
+}
+
+/*
+ * Implementation of 'focus output <output>'.
+ *
+ */
+void cmd_focus_output(I3_CMD, char *name) {
+    owindow *current;
+
+    DLOG("name = %s\n", name);
+
+    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);
+
+    output = get_output_from_string(current_output, name);
+
+    if (!output) {
+        LOG("No such output found.\n");
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    /* get visible workspace on output */
+    Con *ws = NULL;
+    GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
+    if (!ws) {
+        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        return;
+    }
+
+    workspace_show(ws);
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'move scratchpad'.
+ *
+ */
+void cmd_move_scratchpad(I3_CMD) {
+    DLOG("should move window to scratchpad\n");
+    owindow *current;
+
+    HANDLE_EMPTY_MATCH;
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+        scratchpad_move(current->con);
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
+
+/*
+ * Implementation of 'scratchpad show'.
+ *
+ */
+void cmd_scratchpad_show(I3_CMD) {
+    DLOG("should show scratchpad window\n");
+    owindow *current;
+
+    if (match_is_empty(current_match)) {
+        scratchpad_show(NULL);
+    } else {
+        TAILQ_FOREACH(current, &owindows, owindows) {
+            DLOG("matching: %p / %s\n", current->con, current->con->name);
+            scratchpad_show(current->con);
+        }
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    cmd_output->json_output = sstrdup("{\"success\": true}");
+}
diff --git a/src/commands_parser.c b/src/commands_parser.c
new file mode 100644 (file)
index 0000000..f045922
--- /dev/null
@@ -0,0 +1,437 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * commands_parser.c: hand-written parser to parse commands (commands are what
+ * you bind on keys and what you can send to i3 using the IPC interface, like
+ * 'move left' or 'workspace 4').
+ *
+ * We use a hand-written parser instead of lex/yacc because our commands are
+ * easy for humans, not for computers. Thus, it’s quite hard to specify a
+ * context-free grammar for the commands. A PEG grammar would be easier, but
+ * there’s downsides to every PEG parser generator I have come accross so far.
+ *
+ * This parser is basically a state machine which looks for literals or strings
+ * and can push either on a stack. After identifying a literal or string, it
+ * will either transition to the current state, to a different state, or call a
+ * function (like cmd_move()).
+ *
+ * Special care has been taken that error messages are useful and the code is
+ * well testable (when compiled with -DTEST_PARSER it will output to stdout
+ * instead of actually calling any function).
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "all.h"
+
+/*******************************************************************************
+ * The data structures used for parsing. Essentially the current state and a
+ * list of tokens for that state.
+ *
+ * The GENERATED_* files are generated by generate-commands-parser.pl with the
+ * input parser-specs/commands.spec.
+ ******************************************************************************/
+
+#include "GENERATED_enums.h"
+
+typedef struct token {
+    char *name;
+    char *identifier;
+    /* This might be __CALL */
+    cmdp_state next_state;
+    union {
+        uint16_t call_identifier;
+    } extra;
+} cmdp_token;
+
+typedef struct tokenptr {
+    cmdp_token *array;
+    int n;
+} cmdp_token_ptr;
+
+#include "GENERATED_tokens.h"
+
+/*******************************************************************************
+ * The (small) stack where identified literals are stored during the parsing
+ * of a single command (like $workspace).
+ ******************************************************************************/
+
+struct stack_entry {
+    /* Just a pointer, not dynamically allocated. */
+    const char *identifier;
+    char *str;
+};
+
+/* 10 entries should be enough for everybody. */
+static struct stack_entry stack[10];
+
+/*
+ * Pushes a string (identified by 'identifier') on the stack. We simply use a
+ * single array, since the number of entries we have to store is very small.
+ *
+ */
+static void push_string(const char *identifier, char *str) {
+    for (int c = 0; c < 10; c++) {
+        if (stack[c].identifier != NULL)
+            continue;
+        /* Found a free slot, let’s store it here. */
+        stack[c].identifier = identifier;
+        stack[c].str = str;
+        return;
+    }
+
+    /* When we arrive here, the stack is full. This should not happen and
+     * means there’s either a bug in this parser or the specification
+     * contains a command with more than 10 identified tokens. */
+    printf("argh! stack full\n");
+    exit(1);
+}
+
+// XXX: ideally, this would be const char. need to check if that works with all
+// called functions.
+static char *get_string(const char *identifier) {
+    DLOG("Getting string %s from stack...\n", identifier);
+    for (int c = 0; c < 10; c++) {
+        if (stack[c].identifier == NULL)
+            break;
+        if (strcmp(identifier, stack[c].identifier) == 0)
+            return stack[c].str;
+    }
+    return NULL;
+}
+
+static void clear_stack() {
+    DLOG("clearing stack.\n");
+    for (int c = 0; c < 10; c++) {
+        if (stack[c].str != NULL)
+            free(stack[c].str);
+        stack[c].identifier = NULL;
+        stack[c].str = NULL;
+    }
+}
+
+// TODO: remove this if it turns out we don’t need it for testing.
+#if 0
+/*******************************************************************************
+ * A dynamically growing linked list which holds the criteria for the current
+ * command.
+ ******************************************************************************/
+
+typedef struct criterion {
+    char *type;
+    char *value;
+
+    TAILQ_ENTRY(criterion) criteria;
+} criterion;
+
+static TAILQ_HEAD(criteria_head, criterion) criteria =
+  TAILQ_HEAD_INITIALIZER(criteria);
+
+/*
+ * Stores the given type/value in the list of criteria.
+ * Accepts a pointer as first argument, since it is 'call'ed by the parser.
+ *
+ */
+static void push_criterion(void *unused_criteria, const char *type,
+                           const char *value) {
+    struct criterion *criterion = malloc(sizeof(struct criterion));
+    criterion->type = strdup(type);
+    criterion->value = strdup(value);
+    TAILQ_INSERT_TAIL(&criteria, criterion, criteria);
+}
+
+/*
+ * Clears the criteria linked list.
+ * Accepts a pointer as first argument, since it is 'call'ed by the parser.
+ *
+ */
+static void clear_criteria(void *unused_criteria) {
+    struct criterion *criterion;
+    while (!TAILQ_EMPTY(&criteria)) {
+        criterion = TAILQ_FIRST(&criteria);
+        free(criterion->type);
+        free(criterion->value);
+        TAILQ_REMOVE(&criteria, criterion, criteria);
+        free(criterion);
+    }
+}
+#endif
+
+/*******************************************************************************
+ * The parser itself.
+ ******************************************************************************/
+
+static cmdp_state state;
+#ifndef TEST_PARSER
+static Match current_match;
+#endif
+static struct CommandResult subcommand_output;
+static struct CommandResult command_output;
+
+#include "GENERATED_call.h"
+
+
+static void next_state(const cmdp_token *token) {
+    if (token->next_state == __CALL) {
+        DLOG("should call stuff, yay. call_id = %d\n",
+                token->extra.call_identifier);
+        subcommand_output.json_output = NULL;
+        subcommand_output.needs_tree_render = false;
+        GENERATED_call(token->extra.call_identifier, &subcommand_output);
+        if (subcommand_output.json_output) {
+            DLOG("Subcommand JSON output: %s\n", subcommand_output.json_output);
+            char *buffer;
+            /* In the beginning, the contents of json_output are "[\0". */
+            if (command_output.json_output[1] == '\0')
+                sasprintf(&buffer, "%s%s", command_output.json_output, subcommand_output.json_output);
+            else sasprintf(&buffer, "%s, %s", command_output.json_output, subcommand_output.json_output);
+            free(command_output.json_output);
+            command_output.json_output = buffer;
+            DLOG("merged command JSON output: %s\n", command_output.json_output);
+        }
+        /* If any subcommand requires a tree_render(), we need to make the
+         * whole parser result request a tree_render(). */
+        if (subcommand_output.needs_tree_render)
+            command_output.needs_tree_render = true;
+        clear_stack();
+        return;
+    }
+
+    state = token->next_state;
+    if (state == INITIAL) {
+        clear_stack();
+    }
+}
+
+/* TODO: Return parsing errors via JSON. */
+struct CommandResult *parse_command(const char *input) {
+    DLOG("new parser handling: %s\n", input);
+    state = INITIAL;
+    command_output.json_output = sstrdup("[");
+    command_output.needs_tree_render = false;
+
+    const char *walk = input;
+    const size_t len = strlen(input);
+    int c;
+    const cmdp_token *token;
+    bool token_handled;
+
+    // TODO: make this testable
+#ifndef TEST_PARSER
+    cmd_criteria_init(&current_match, &subcommand_output);
+#endif
+
+    /* The "<=" operator is intentional: We also handle the terminating 0-byte
+     * explicitly by looking for an 'end' token. */
+    while ((walk - input) <= len) {
+        /* skip whitespace and newlines before every token */
+        while ((*walk == ' ' || *walk == '\t' ||
+                *walk == '\r' || *walk == '\n') && *walk != '\0')
+            walk++;
+
+        DLOG("remaining input = %s\n", walk);
+
+        cmdp_token_ptr *ptr = &(tokens[state]);
+        token_handled = false;
+        for (c = 0; c < ptr->n; c++) {
+            token = &(ptr->array[c]);
+            DLOG("trying token %d = %s\n", c, token->name);
+
+            /* A literal. */
+            if (token->name[0] == '\'') {
+                DLOG("literal\n");
+                if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
+                    DLOG("found literal, moving to next state\n");
+                    if (token->identifier != NULL)
+                        push_string(token->identifier, sstrdup(token->name + 1));
+                    walk += strlen(token->name) - 1;
+                    next_state(token);
+                    token_handled = true;
+                    break;
+                }
+                continue;
+            }
+
+            if (strcmp(token->name, "string") == 0 ||
+                strcmp(token->name, "word") == 0) {
+                DLOG("parsing this as a string\n");
+                const char *beginning = walk;
+                /* Handle quoted strings (or words). */
+                if (*walk == '"') {
+                    beginning++;
+                    walk++;
+                    while (*walk != '\0' && (*walk != '"' || *(walk-1) == '\\'))
+                        walk++;
+                } else {
+                    if (token->name[0] == 's') {
+                        /* For a string (starting with 's'), the delimiters are
+                         * comma (,) and semicolon (;) which introduce a new
+                         * operation or command, respectively. Also, newlines
+                         * end a command. */
+                        while (*walk != ';' && *walk != ',' &&
+                               *walk != '\0' && *walk != '\r' &&
+                               *walk != '\n')
+                            walk++;
+                    } else {
+                        /* For a word, the delimiters are white space (' ' or
+                         * '\t'), closing square bracket (]), comma (,) and
+                         * semicolon (;). */
+                        while (*walk != ' ' && *walk != '\t' &&
+                               *walk != ']' && *walk != ',' &&
+                               *walk !=  ';' && *walk != '\r' &&
+                               *walk != '\n' && *walk != '\0')
+                            walk++;
+                    }
+                }
+                if (walk != beginning) {
+                    char *str = scalloc(walk-beginning + 1);
+                    /* We copy manually to handle escaping of characters. */
+                    int inpos, outpos;
+                    for (inpos = 0, outpos = 0;
+                         inpos < (walk-beginning);
+                         inpos++, outpos++) {
+                        /* We only handle escaped double quotes to not break
+                         * backwards compatibility with people using \w in
+                         * regular expressions etc. */
+                        if (beginning[inpos] == '\\' && beginning[inpos+1] == '"')
+                            inpos++;
+                        str[outpos] = beginning[inpos];
+                    }
+                    if (token->identifier)
+                        push_string(token->identifier, str);
+                    DLOG("str is \"%s\"\n", str);
+                    /* If we are at the end of a quoted string, skip the ending
+                     * double quote. */
+                    if (*walk == '"')
+                        walk++;
+                    next_state(token);
+                    token_handled = true;
+                    break;
+                }
+            }
+
+            if (strcmp(token->name, "end") == 0) {
+                DLOG("checking for the end token.\n");
+                if (*walk == '\0' || *walk == ',' || *walk == ';') {
+                    DLOG("yes, indeed. end\n");
+                    next_state(token);
+                    token_handled = true;
+                    /* To make sure we start with an appropriate matching
+                     * datastructure for commands which do *not* specify any
+                     * criteria, we re-initialize the criteria system after
+                     * every command. */
+                    // TODO: make this testable
+#ifndef TEST_PARSER
+                    if (*walk == '\0' || *walk == ';')
+                        cmd_criteria_init(&current_match, &subcommand_output);
+#endif
+                    walk++;
+                    break;
+               }
+           }
+        }
+
+        if (!token_handled) {
+            /* Figure out how much memory we will need to fill in the names of
+             * all tokens afterwards. */
+            int tokenlen = 0;
+            for (c = 0; c < ptr->n; c++)
+                tokenlen += strlen(ptr->array[c].name) + strlen("'', ");
+
+            /* Build up a decent error message. We include the problem, the
+             * full input, and underline the position where the parser
+             * currently is. */
+            char *errormessage;
+            char *possible_tokens = smalloc(tokenlen + 1);
+            char *tokenwalk = possible_tokens;
+            for (c = 0; c < ptr->n; c++) {
+                token = &(ptr->array[c]);
+                if (token->name[0] == '\'') {
+                    /* A literal is copied to the error message enclosed with
+                     * single quotes. */
+                    *tokenwalk++ = '\'';
+                    strcpy(tokenwalk, token->name + 1);
+                    tokenwalk += strlen(token->name + 1);
+                    *tokenwalk++ = '\'';
+                } else {
+                    /* Any other token is copied to the error message enclosed
+                     * with angle brackets. */
+                    *tokenwalk++ = '<';
+                    strcpy(tokenwalk, token->name);
+                    tokenwalk += strlen(token->name);
+                    *tokenwalk++ = '>';
+                }
+                if (c < (ptr->n - 1)) {
+                    *tokenwalk++ = ',';
+                    *tokenwalk++ = ' ';
+                }
+            }
+            *tokenwalk = '\0';
+            sasprintf(&errormessage, "Expected one of these tokens: %s",
+                      possible_tokens);
+            free(possible_tokens);
+
+            /* Contains the same amount of characters as 'input' has, but with
+             * the unparseable part highlighted using ^ characters. */
+            char *position = smalloc(len + 1);
+            for (const char *copywalk = input; *copywalk != '\0'; copywalk++)
+                position[(copywalk - input)] = (copywalk >= walk ? '^' : ' ');
+            position[len] = '\0';
+
+            printf("%s\n", errormessage);
+            printf("Your command: %s\n", input);
+            printf("              %s\n", position);
+
+            free(position);
+            free(errormessage);
+            break;
+        }
+    }
+
+    char *buffer;
+    sasprintf(&buffer, "%s]", command_output.json_output);
+    free(command_output.json_output);
+    command_output.json_output = buffer;
+    DLOG("command_output.json_output = %s\n", command_output.json_output);
+    DLOG("command_output.needs_tree_render = %d\n", command_output.needs_tree_render);
+    return &command_output;
+}
+
+/*******************************************************************************
+ * Code for building the stand-alone binary test.commands_parser which is used
+ * by t/187-commands-parser.t.
+ ******************************************************************************/
+
+#ifdef TEST_PARSER
+
+/*
+ * Logs the given message to stdout while prefixing the current time to it,
+ * but only if the corresponding debug loglevel was activated.
+ * This is to be called by DLOG() which includes filename/linenumber
+ *
+ */
+void debuglog(uint64_t lev, char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    fprintf(stderr, "# ");
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+}
+
+int main(int argc, char *argv[]) {
+    if (argc < 2) {
+        fprintf(stderr, "Syntax: %s <command>\n", argv[0]);
+        return 1;
+    }
+    parse_command(argv[1]);
+}
+#endif
index f1b974427868f00d104e049a2077c4d710b6369b..537b4436c0f2f1e0b5af6c6ff0b08245cb830717 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -644,8 +644,10 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
 
     /* 7: focus the con on the target workspace (the X focus is only updated by
      * calling tree_render(), so for the "real" focus this is a no-op).
-     * We don’t focus when there is a fullscreen con on that workspace. */
-    if (con_get_fullscreen_con(workspace, CF_OUTPUT) == NULL)
+     * We don’t focus the con for i3 pseudo workspaces like __i3_scratch and
+     * we don’t focus when there is a fullscreen con on that workspace. */
+    if ((workspace->name[0] != '_' || workspace->name[1] != '_') &&
+        con_get_fullscreen_con(workspace, CF_OUTPUT) == NULL)
         con_focus(con_descend_focused(con));
 
     /* 8: when moving to a visible workspace on a different output, we keep the
index 7fc15c0dc0d5fdf04828df77a4665f8f21083b59..de59420a562503ff259b498aa67aca56ec61c160 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * config.c: Configuration file (calling the parser (src/cfgparse.y) with the
  *           correct path, switching key bindings mode).
@@ -237,7 +237,7 @@ static char *get_config_path(const char *override_configpath) {
 
     die("Unable to find the configuration file (looked at "
             "~/.i3/config, $XDG_CONFIG_HOME/i3/config, "
-            SYSCONFDIR "i3/config and $XDG_CONFIG_DIRS/i3/config)");
+            SYSCONFDIR "/i3/config and $XDG_CONFIG_DIRS/i3/config)");
 }
 
 /*
@@ -248,7 +248,7 @@ static char *get_config_path(const char *override_configpath) {
  */
 static void parse_configuration(const char *override_configpath) {
     char *path = get_config_path(override_configpath);
-    DLOG("Parsing configfile %s\n", path);
+    LOG("Parsing configfile %s\n", path);
     FREE(current_configpath);
     current_configpath = path;
     parse_file(path);
@@ -307,17 +307,22 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
             FREE(barconfig->tray_output);
             FREE(barconfig->socket_path);
             FREE(barconfig->status_command);
+            FREE(barconfig->i3bar_command);
             FREE(barconfig->font);
             FREE(barconfig->colors.background);
             FREE(barconfig->colors.statusline);
-            FREE(barconfig->colors.focused_workspace_text);
+            FREE(barconfig->colors.focused_workspace_border);
             FREE(barconfig->colors.focused_workspace_bg);
-            FREE(barconfig->colors.active_workspace_text);
+            FREE(barconfig->colors.focused_workspace_text);
+            FREE(barconfig->colors.active_workspace_border);
             FREE(barconfig->colors.active_workspace_bg);
-            FREE(barconfig->colors.inactive_workspace_text);
+            FREE(barconfig->colors.active_workspace_text);
+            FREE(barconfig->colors.inactive_workspace_border);
             FREE(barconfig->colors.inactive_workspace_bg);
-            FREE(barconfig->colors.urgent_workspace_text);
+            FREE(barconfig->colors.inactive_workspace_text);
+            FREE(barconfig->colors.urgent_workspace_border);
             FREE(barconfig->colors.urgent_workspace_bg);
+            FREE(barconfig->colors.urgent_workspace_text);
             TAILQ_REMOVE(&barconfigs, barconfig, configs);
             FREE(barconfig);
         }
@@ -328,6 +333,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
         TAILQ_FOREACH(ws, workspaces, workspaces)
             workspace_set_name(ws, NULL);
 #endif
+
+        /* Invalidate pixmap caches in case font or colors changed */
+        Con *con;
+        TAILQ_FOREACH(con, &all_cons, all_cons)
+            FREE(con->deco_render_params);
+
+        /* Get rid of the current font */
+        free_font();
     }
 
     SLIST_INIT(&modes);
@@ -348,21 +361,24 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
     memset(&config, 0, sizeof(config));
 
     /* Initialize default colors */
-#define INIT_COLOR(x, cborder, cbackground, ctext) \
+#define INIT_COLOR(x, cborder, cbackground, ctext, cindicator) \
     do { \
         x.border = get_colorpixel(cborder); \
         x.background = get_colorpixel(cbackground); \
         x.text = get_colorpixel(ctext); \
+        x.indicator = get_colorpixel(cindicator); \
     } 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");
+    INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff", "#2e9ef4");
+    INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff", "#484e50");
+    INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888", "#292d2e");
+    INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff", "#900000");
+
+    /* the last argument (indicator color) is ignored for bar colors */
+    INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff", "#000000");
+    INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888", "#000000");
+    INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff", "#000000");
 
     config.default_border = BS_NORMAL;
     config.default_floating_border = BS_NORMAL;
@@ -379,6 +395,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
     if (config.font.id == 0) {
         ELOG("You did not specify required configuration option \"font\"\n");
         config.font = load_font("fixed", true);
+        set_font(&config.font);
+    }
+
+    /* Redraw the currently visible decorations on reload, so that
+     * the possibly new drawing parameters changed. */
+    if (reload) {
+        x_deco_recurse(croot);
+        xcb_flush(conn);
     }
 
 #if 0
index 0637521fbce020630c22ec98bd8f2efffee3785a..3f38761f1e34dfb72d516438fb2a8a330dde70c6 100644 (file)
 
 extern xcb_connection_t *conn;
 
+/*
+ * Calculates sum of heights and sum of widths of all currently active outputs
+ *
+ */
+Rect total_outputs_dimensions() {
+    Output *output;
+    /* Use Rect to encapsulate dimensions, ignoring x/y */
+    Rect outputs_dimensions = {0, 0, 0, 0};
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        outputs_dimensions.height += output->rect.height;
+        outputs_dimensions.width += output->rect.width;
+    }
+    return outputs_dimensions;
+}
+
 void floating_enable(Con *con, bool automatic) {
     bool set_focus = (con == focused);
 
@@ -120,9 +135,45 @@ void floating_enable(Con *con, bool automatic) {
             nc->rect.height = max(nc->rect.height, child->geometry.height);
         }
     }
-    /* Raise the width/height to at least 75x50 (minimum size for windows) */
-    nc->rect.width = max(nc->rect.width, 75);
-    nc->rect.height = max(nc->rect.height, 50);
+
+    /* Define reasonable minimal and maximal sizes for floating windows */
+    const int floating_sane_min_height = 50;
+    const int floating_sane_min_width = 75;
+
+    Rect floating_sane_max_dimensions;
+    floating_sane_max_dimensions = total_outputs_dimensions();
+
+    /* Unless user requests otherwise (-1), ensure width/height do not exceed
+     * configured maxima or, if unconfigured, limit to combined width of all
+     * outputs */
+    if (config.floating_maximum_height != -1) {
+        if (config.floating_maximum_height == 0)
+            nc->rect.height = min(nc->rect.height, floating_sane_max_dimensions.height);
+        else
+            nc->rect.height = min(nc->rect.height, config.floating_maximum_height);
+    }
+    if (config.floating_maximum_width != -1) {
+        if (config.floating_maximum_width == 0)
+            nc->rect.width = min(nc->rect.width, floating_sane_max_dimensions.width);
+        else
+            nc->rect.width = min(nc->rect.width, config.floating_maximum_width);
+    }
+
+    /* Unless user requests otherwise (-1), raise the width/height to
+     * reasonable minimum dimensions */
+    if (config.floating_minimum_height != -1) {
+        if (config.floating_minimum_height == 0)
+            nc->rect.height = max(nc->rect.height, floating_sane_min_height);
+        else
+            nc->rect.height = max(nc->rect.height, config.floating_minimum_height);
+    }
+    if (config.floating_minimum_width != -1) {
+        if (config.floating_minimum_width == 0)
+            nc->rect.width = max(nc->rect.width, floating_sane_min_width);
+        else
+            nc->rect.width = max(nc->rect.width, config.floating_minimum_width);
+    }
+
     /* add pixels for the decoration */
     /* TODO: don’t add them when the user automatically puts new windows into
      * 1pixel/borderless mode */
index e2fa205ca02535098d70c30be82ac970175f30d2..1fb2bdd466014a464026704f91296a251b3bf8a8 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * handlers.c: Small handlers for various events (keypresses, focus changes,
  *             …).
@@ -11,6 +11,7 @@
 #include "all.h"
 
 #include <time.h>
+#include <sys/time.h>
 #include <xcb/randr.h>
 #include <X11/XKBlib.h>
 #define SN_API_NOT_YET_FROZEN 1
@@ -82,7 +83,7 @@ bool event_is_ignored(const int sequence, const int response_type) {
  * the bound action to parse_command().
  *
  */
-static int handle_key_press(xcb_key_press_event_t *event) {
+static void handle_key_press(xcb_key_press_event_t *event) {
 
     last_timestamp = event->time;
 
@@ -114,13 +115,16 @@ static int handle_key_press(xcb_key_press_event_t *event) {
         if ((bind = get_binding(state_filtered, event->detail)) == NULL) {
             ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
                  state_filtered, event->detail);
-            return 1;
+            return;
         }
     }
 
-    char *json_result = parse_cmd(bind->command);
-    FREE(json_result);
-    return 1;
+    struct CommandResult *command_output = parse_command(bind->command);
+
+    if (command_output->needs_tree_render)
+        tree_render();
+
+    free(command_output->json_output);
 }
 
 /*
@@ -163,7 +167,7 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
  * When the user moves the mouse pointer onto a window, this callback gets called.
  *
  */
-static int handle_enter_notify(xcb_enter_notify_event_t *event) {
+static void handle_enter_notify(xcb_enter_notify_event_t *event) {
     Con *con;
 
     last_timestamp = event->time;
@@ -173,13 +177,13 @@ static int handle_enter_notify(xcb_enter_notify_event_t *event) {
     DLOG("coordinates %d, %d\n", event->event_x, event->event_y);
     if (event->mode != XCB_NOTIFY_MODE_NORMAL) {
         DLOG("This was not a normal notify, ignoring\n");
-        return 1;
+        return;
     }
     /* Some events are not interesting, because they were not generated
      * actively by the user, but by reconfiguration of windows */
     if (event_is_ignored(event->sequence, XCB_ENTER_NOTIFY)) {
         DLOG("Event ignored\n");
-        return 1;
+        return;
     }
 
     bool enter_child = false;
@@ -193,12 +197,12 @@ static int handle_enter_notify(xcb_enter_notify_event_t *event) {
     if (con == NULL) {
         DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y);
         check_crossing_screen_boundary(event->root_x, event->root_y);
-        return 1;
+        return;
     }
 
     if (con->parent->type == CT_DOCKAREA) {
         DLOG("Ignoring, this is a dock client\n");
-        return 1;
+        return;
     }
 
     /* see if the user entered the window on a certain window decoration */
@@ -224,7 +228,7 @@ static int handle_enter_notify(xcb_enter_notify_event_t *event) {
 #endif
 
     if (config.disable_focus_follows_mouse)
-        return 1;
+        return;
 
     /* Get the currently focused workspace to check if the focus change also
      * involves changing workspaces. If so, we need to call workspace_show() to
@@ -236,7 +240,7 @@ static int handle_enter_notify(xcb_enter_notify_event_t *event) {
     con_focus(con_descend_focused(con));
     tree_render();
 
-    return 1;
+    return;
 }
 
 /*
@@ -245,26 +249,26 @@ static int handle_enter_notify(xcb_enter_notify_event_t *event) {
  * and crossing virtual screen boundaries), this callback gets called.
  *
  */
-static int handle_motion_notify(xcb_motion_notify_event_t *event) {
+static void 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)
-        return 1;
+        return;
 
     Con *con;
     if ((con = con_by_frame_id(event->event)) == NULL) {
         check_crossing_screen_boundary(event->root_x, event->root_y);
-        return 1;
+        return;
     }
 
     if (config.disable_focus_follows_mouse)
-        return 1;
+        return;
 
     if (con->layout != L_DEFAULT)
-        return 1;
+        return;
 
     /* see over which rect the user is */
     Con *current;
@@ -274,14 +278,14 @@ static int handle_motion_notify(xcb_motion_notify_event_t *event) {
 
         /* We found the rect, let’s see if this window is focused */
         if (TAILQ_FIRST(&(con->focus_head)) == current)
-            return 1;
+            return;
 
         con_focus(current);
         x_push_changes(croot);
-        return 1;
+        return;
     }
 
-    return 1;
+    return;
 }
 
 /*
@@ -289,10 +293,10 @@ static int handle_motion_notify(xcb_motion_notify_event_t *event) {
  * we need to update our key bindings then (re-translate symbols).
  *
  */
-static int handle_mapping_notify(xcb_mapping_notify_event_t *event) {
+static void handle_mapping_notify(xcb_mapping_notify_event_t *event) {
     if (event->request != XCB_MAPPING_KEYBOARD &&
         event->request != XCB_MAPPING_MODIFIER)
-        return 0;
+        return;
 
     DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
     xcb_refresh_keyboard_mapping(keysyms, event);
@@ -303,14 +307,14 @@ static int handle_mapping_notify(xcb_mapping_notify_event_t *event) {
     translate_keysyms();
     grab_all_keys(conn, false);
 
-    return 0;
+    return;
 }
 
 /*
  * A new window appeared on the screen (=was mapped), so let’s manage it.
  *
  */
-static int handle_map_request(xcb_map_request_event_t *event) {
+static void handle_map_request(xcb_map_request_event_t *event) {
     xcb_get_window_attributes_cookie_t cookie;
 
     cookie = xcb_get_window_attributes_unchecked(conn, event->window);
@@ -320,16 +324,18 @@ static int handle_map_request(xcb_map_request_event_t *event) {
 
     manage_window(event->window, cookie, false);
     x_push_changes(croot);
-    return 1;
+    return;
 }
 
 /*
- * Configure requests are received when the application wants to resize windows on their own.
+ * Configure requests are received when the application wants to resize windows
+ * on their own.
  *
- * We generate a synthethic configure notify event to signalize the client its "new" position.
+ * We generate a synthethic configure notify event to signalize the client its
+ * "new" position.
  *
  */
-static int handle_configure_request(xcb_configure_request_event_t *event) {
+static void handle_configure_request(xcb_configure_request_event_t *event) {
     Con *con;
 
     DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n",
@@ -361,7 +367,7 @@ static int handle_configure_request(xcb_configure_request_event_t *event) {
         xcb_configure_window(conn, event->window, mask, values);
         xcb_flush(conn);
 
-        return 1;
+        return;
     }
 
     DLOG("Configure request!\n");
@@ -402,7 +408,7 @@ static int handle_configure_request(xcb_configure_request_event_t *event) {
 
         DLOG("Container is a floating leaf node, will do that.\n");
         floating_reposition(floatingcon, newrect);
-        return 1;
+        return;
     }
 
     /* Dock windows can be reconfigured in their height */
@@ -418,7 +424,7 @@ static int handle_configure_request(xcb_configure_request_event_t *event) {
 
     fake_absolute_configure_notify(con);
 
-    return 1;
+    return;
 }
 #if 0
 
@@ -442,14 +448,14 @@ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_n
  * changes the screen configuration in any way (mode, position, …)
  *
  */
-static int handle_screen_change(xcb_generic_event_t *e) {
+static void handle_screen_change(xcb_generic_event_t *e) {
     DLOG("RandR screen change\n");
 
     randr_query_outputs();
 
     ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
 
-    return 1;
+    return;
 }
 
 /*
@@ -606,26 +612,26 @@ static int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t
  * Expose event means we should redraw our windows (= title bar)
  *
  */
-static int handle_expose_event(xcb_expose_event_t *event) {
+static void handle_expose_event(xcb_expose_event_t *event) {
     Con *parent;
 
-    /* event->count is the number of minimum remaining expose events for this
-     * window, so we skip all events but the last one */
-    if (event->count != 0)
-        return 1;
-
     DLOG("window = %08x\n", event->window);
 
     if ((parent = con_by_frame_id(event->window)) == NULL) {
         LOG("expose event for unknown window, ignoring\n");
-        return 1;
+        return;
     }
 
-    /* re-render the parent (recursively, if it’s a split con) */
-    x_deco_recurse(parent);
+    /* Since we render to our pixmap on every change anyways, expose events
+     * only tell us that the X server lost (parts of) the window contents. We
+     * can handle that by copying the appropriate part from our pixmap to the
+     * window. */
+    xcb_copy_area(conn, parent->pixmap, parent->frame, parent->pm_gc,
+                  event->x, event->y, event->x, event->y,
+                  event->width, event->height);
     xcb_flush(conn);
 
-    return 1;
+    return;
 }
 
 /*
@@ -839,12 +845,22 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
 
     if (!con->urgent && focused == con) {
         DLOG("Ignoring urgency flag for current client\n");
+        con->window->urgent.tv_sec = 0;
+        con->window->urgent.tv_usec = 0;
         goto end;
     }
 
     /* Update the flag on the client directly */
     con->urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
     //CLIENT_LOG(con);
+    if (con->window) {
+        if (con->urgent) {
+            gettimeofday(&con->window->urgent, NULL);
+        } else {
+            con->window->urgent.tv_sec = 0;
+            con->window->urgent.tv_usec = 0;
+        }
+    }
     LOG("Urgency flag changed to %d\n", con->urgent);
 
     Con *ws;
@@ -887,14 +903,6 @@ static bool handle_transient_for(void *data, xcb_connection_t *conn, uint8_t sta
 
     window_update_transient_for(con->window, prop);
 
-    // TODO: put window in floating mode if con->window->transient_for != XCB_NONE:
-#if 0
-    if (client->floating == FLOATING_AUTO_OFF) {
-        DLOG("This is a popup window, putting into floating\n");
-        toggle_floating_mode(conn, client, true);
-    }
-#endif
-
     return true;
 }
 
@@ -927,33 +935,33 @@ static bool handle_clientleader_change(void *data, xcb_connection_t *conn, uint8
  * decorations accordingly.
  *
  */
-static int handle_focus_in(xcb_focus_in_event_t *event) {
+static void handle_focus_in(xcb_focus_in_event_t *event) {
     DLOG("focus change in, for window 0x%08x\n", event->event);
     Con *con;
     if ((con = con_by_window_id(event->event)) == NULL || con->window == NULL)
-        return 1;
+        return;
     DLOG("That is con %p / %s\n", con, con->name);
 
     if (event->mode == XCB_NOTIFY_MODE_GRAB ||
         event->mode == XCB_NOTIFY_MODE_UNGRAB) {
         DLOG("FocusIn event for grab/ungrab, ignoring\n");
-        return 1;
+        return;
     }
 
     if (event->detail == XCB_NOTIFY_DETAIL_POINTER) {
         DLOG("notify detail is pointer, ignoring this event\n");
-        return 1;
+        return;
     }
 
     if (focused_id == event->event) {
         DLOG("focus matches the currently focused window, not doing anything\n");
-        return 1;
+        return;
     }
 
     /* Skip dock clients, they cannot get the i3 focus. */
     if (con->parent->type == CT_DOCKAREA) {
         DLOG("This is a dock client, not focusing.\n");
-        return 1;
+        return;
     }
 
     DLOG("focus is different, updating decorations\n");
@@ -969,7 +977,7 @@ static int handle_focus_in(xcb_focus_in_event_t *event) {
     /* We update focused_id because we don’t need to set focus again */
     focused_id = event->event;
     x_push_changes(croot);
-    return 1;
+    return;
 }
 
 /* Returns false if the event could not be processed (e.g. the window could not
index fe1464e6c41d2da6d6dbdd4a6f71d6ec343ea62c..3733034dec5713ae6fabaef1988beadf83010088 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).
  *
@@ -119,16 +119,18 @@ IPC_HANDLER(command) {
     char *command = scalloc(message_size + 1);
     strncpy(command, (const char*)message, message_size);
     LOG("IPC: received: *%s*\n", command);
-    char *reply = parse_cmd((const char*)command);
-    char *save_reply = reply;
+    struct CommandResult *command_output = parse_command((const char*)command);
     free(command);
 
+    if (command_output->needs_tree_render)
+        tree_render();
+
     /* If no reply was provided, we just use the default success message */
-    if (reply == NULL)
-        reply = "{\"success\":true}";
-    ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_COMMAND, (const uint8_t*)reply);
+    ipc_send_message(fd, strlen(command_output->json_output),
+                     I3_IPC_REPLY_TYPE_COMMAND,
+                     (const uint8_t*)command_output->json_output);
 
-    FREE(save_reply);
+    free(command_output->json_output);
 }
 
 static void dump_rect(yajl_gen gen, const char *name, Rect r) {
@@ -166,6 +168,19 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
             break;
     }
 
+    ystr("scratchpad_state");
+    switch (con->scratchpad_state) {
+        case SCRATCHPAD_NONE:
+            ystr("none");
+            break;
+        case SCRATCHPAD_FRESH:
+            ystr("fresh");
+            break;
+        case SCRATCHPAD_CHANGED:
+            ystr("changed");
+            break;
+    }
+
     ystr("percent");
     if (con->percent == 0.0)
         y(null);
@@ -261,6 +276,22 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     ystr("fullscreen_mode");
     y(integer, con->fullscreen_mode);
 
+    ystr("floating");
+    switch (con->floating) {
+        case FLOATING_AUTO_OFF:
+            ystr("auto_off");
+            break;
+        case FLOATING_AUTO_ON:
+            ystr("auto_on");
+            break;
+        case FLOATING_USER_OFF:
+            ystr("user_off");
+            break;
+        case FLOATING_USER_ON:
+            ystr("user_on");
+            break;
+    }
+
     ystr("swallows");
     y(array_open);
     Match *match;
@@ -282,6 +313,8 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
             y(map_open);
             ystr("id");
             y(integer, con->window->id);
+            ystr("restart_mode");
+            y(bool, true);
             y(map_close);
         }
     }
@@ -330,6 +363,8 @@ IPC_HANDLER(get_workspaces) {
 
     Con *output;
     TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+        if (output->name[0] == '_' && output->name[1] == '_')
+            continue;
         Con *ws;
         TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
             assert(ws->type == CT_WORKSPACE);
@@ -558,6 +593,36 @@ IPC_HANDLER(get_bar_config) {
             ystr("hide");
         else ystr("dock");
 
+        ystr("modifier");
+        switch (config->modifier) {
+            case M_CONTROL:
+                ystr("ctrl");
+                break;
+            case M_SHIFT:
+                ystr("shift");
+                break;
+            case M_MOD1:
+                ystr("Mod1");
+                break;
+            case M_MOD2:
+                ystr("Mod2");
+                break;
+            case M_MOD3:
+                ystr("Mod3");
+                break;
+            /*
+            case M_MOD4:
+                ystr("Mod4");
+                break;
+            */
+            case M_MOD5:
+                ystr("Mod5");
+                break;
+            default:
+                ystr("Mod4");
+                break;
+        }
+
         ystr("position");
         if (config->position == P_BOTTOM)
             ystr("bottom");
@@ -585,14 +650,18 @@ IPC_HANDLER(get_bar_config) {
         y(map_open);
         YSTR_IF_SET(background);
         YSTR_IF_SET(statusline);
-        YSTR_IF_SET(focused_workspace_text);
+        YSTR_IF_SET(focused_workspace_border);
         YSTR_IF_SET(focused_workspace_bg);
-        YSTR_IF_SET(active_workspace_text);
+        YSTR_IF_SET(focused_workspace_text);
+        YSTR_IF_SET(active_workspace_border);
         YSTR_IF_SET(active_workspace_bg);
-        YSTR_IF_SET(inactive_workspace_text);
+        YSTR_IF_SET(active_workspace_text);
+        YSTR_IF_SET(inactive_workspace_border);
         YSTR_IF_SET(inactive_workspace_bg);
-        YSTR_IF_SET(urgent_workspace_text);
+        YSTR_IF_SET(inactive_workspace_text);
+        YSTR_IF_SET(urgent_workspace_border);
         YSTR_IF_SET(urgent_workspace_bg);
+        YSTR_IF_SET(urgent_workspace_text);
         y(map_close);
 
 #undef YSTR_IF_SET
@@ -704,7 +773,7 @@ handler_t handlers[7] = {
     handle_get_outputs,
     handle_tree,
     handle_get_marks,
-    handle_get_bar_config
+    handle_get_bar_config,
 };
 
 /*
index ef787fd1a8356df2812c25665b2b270bb7eafb75..a8063dcada1d7cc6b875cb65d4690a57d13a0dbb 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * load_layout.c: Restore (parts of) the layout, for example after an inplace
  *                restart.
@@ -24,8 +24,19 @@ static bool parsing_swallows;
 static bool parsing_rect;
 static bool parsing_window_rect;
 static bool parsing_geometry;
+static bool parsing_focus;
 struct Match *current_swallow;
 
+/* This list is used for reordering the focus stack after parsing the 'focus'
+ * array. */
+struct focus_mapping {
+    int old_id;
+    TAILQ_ENTRY(focus_mapping) focus_mappings;
+};
+
+static TAILQ_HEAD(focus_mappings_head, focus_mapping) focus_mappings =
+  TAILQ_HEAD_INITIALIZER(focus_mappings);
+
 static int json_start_map(void *ctx) {
     LOG("start of map, last_key = %s\n", last_key);
     if (parsing_swallows) {
@@ -70,6 +81,29 @@ static int json_end_map(void *ctx) {
 static int json_end_array(void *ctx) {
     LOG("end of array\n");
     parsing_swallows = false;
+    if (parsing_focus) {
+        /* Clear the list of focus mappings */
+        struct focus_mapping *mapping;
+        TAILQ_FOREACH_REVERSE(mapping, &focus_mappings, focus_mappings_head, focus_mappings) {
+            LOG("focus (reverse) %d\n", mapping->old_id);
+            Con *con;
+            TAILQ_FOREACH(con, &(json_node->focus_head), focused) {
+                if (con->old_id != mapping->old_id)
+                    continue;
+                LOG("got it! %p\n", con);
+                /* Move this entry to the top of the focus list. */
+                TAILQ_REMOVE(&(json_node->focus_head), con, focused);
+                TAILQ_INSERT_HEAD(&(json_node->focus_head), con, focused);
+                break;
+            }
+        }
+        while (!TAILQ_EMPTY(&focus_mappings)) {
+            mapping = TAILQ_FIRST(&focus_mappings);
+            TAILQ_REMOVE(&focus_mappings, mapping, focus_mappings);
+            free(mapping);
+        }
+        parsing_focus = false;
+    }
     return 1;
 }
 
@@ -82,15 +116,21 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) {
     FREE(last_key);
     last_key = scalloc((len+1) * sizeof(char));
     memcpy(last_key, val, len);
-    if (strcasecmp(last_key, "swallows") == 0) {
+    if (strcasecmp(last_key, "swallows") == 0)
         parsing_swallows = true;
-    }
+
     if (strcasecmp(last_key, "rect") == 0)
         parsing_rect = true;
+
     if (strcasecmp(last_key, "window_rect") == 0)
         parsing_window_rect = true;
+
     if (strcasecmp(last_key, "geometry") == 0)
         parsing_geometry = true;
+
+    if (strcasecmp(last_key, "focus") == 0)
+        parsing_focus = true;
+
     return 1;
 }
 
@@ -156,6 +196,28 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
             char *buf = NULL;
             sasprintf(&buf, "%.*s", (int)len, val);
             json_node->mark = buf;
+        } else if (strcasecmp(last_key, "floating") == 0) {
+            char *buf = NULL;
+            sasprintf(&buf, "%.*s", (int)len, val);
+            if (strcasecmp(buf, "auto_off") == 0)
+                json_node->floating = FLOATING_AUTO_OFF;
+            else if (strcasecmp(buf, "auto_on") == 0)
+                json_node->floating = FLOATING_AUTO_ON;
+            else if (strcasecmp(buf, "user_off") == 0)
+                json_node->floating = FLOATING_USER_OFF;
+            else if (strcasecmp(buf, "user_on") == 0)
+                json_node->floating = FLOATING_USER_ON;
+            free(buf);
+        } else if (strcasecmp(last_key, "scratchpad_state") == 0) {
+            char *buf = NULL;
+            sasprintf(&buf, "%.*s", (int)len, val);
+            if (strcasecmp(buf, "none") == 0)
+                json_node->scratchpad_state = SCRATCHPAD_NONE;
+            else if (strcasecmp(buf, "fresh") == 0)
+                json_node->scratchpad_state = SCRATCHPAD_FRESH;
+            else if (strcasecmp(buf, "changed") == 0)
+                json_node->scratchpad_state = SCRATCHPAD_CHANGED;
+            free(buf);
         }
     }
     return 1;
@@ -168,15 +230,24 @@ static int json_int(void *ctx, long long val) {
 static int json_int(void *ctx, long val) {
     LOG("int %ld for key %s\n", val, last_key);
 #endif
-    if (strcasecmp(last_key, "type") == 0) {
+    if (strcasecmp(last_key, "type") == 0)
         json_node->type = val;
-    }
-    if (strcasecmp(last_key, "fullscreen_mode") == 0) {
+
+    if (strcasecmp(last_key, "fullscreen_mode") == 0)
         json_node->fullscreen_mode = val;
-    }
+
     if (strcasecmp(last_key, "num") == 0)
         json_node->num = val;
 
+    if (!parsing_swallows && strcasecmp(last_key, "id") == 0)
+        json_node->old_id = val;
+
+    if (parsing_focus) {
+        struct focus_mapping *focus_mapping = scalloc(sizeof(struct focus_mapping));
+        focus_mapping->old_id = val;
+        TAILQ_INSERT_TAIL(&focus_mappings, focus_mapping, focus_mappings);
+    }
+
     if (parsing_rect || parsing_window_rect || parsing_geometry) {
         Rect *r;
         if (parsing_rect)
@@ -217,6 +288,11 @@ static int json_bool(void *ctx, int val) {
         to_focus = json_node;
     }
 
+    if (parsing_swallows) {
+        if (strcasecmp(last_key, "restart_mode") == 0)
+            current_swallow->restart_mode = val;
+    }
+
     return 1;
 }
 
index a615978065eaf8570f96e970c706cf95ffcfa518..7b7eca5045e7b22537d71806ccb2249e9672747a 100644 (file)
--- a/src/log.c
+++ b/src/log.c
 #include <sys/time.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <errno.h>
 
 #include "util.h"
 #include "log.h"
+#include "i3.h"
+#include "libi3.h"
+#include "shmlog.h"
 
 /* loglevels.h is autogenerated at make time */
 #include "loglevels.h"
@@ -27,21 +33,94 @@ static bool verbose = false;
 static FILE *errorfile;
 char *errorfilename;
 
+/* SHM logging variables */
+
+/* The name for the SHM (/i3-log-%pid). Will end up on /dev/shm on most
+ * systems. Global so that we can clean up at exit. */
+char *shmlogname = "";
+/* Size limit for the SHM log, by default 25 MiB. Can be overwritten using the
+ * flag --shmlog-size. */
+int shmlog_size = 0;
+/* If enabled, logbuffer will point to a memory mapping of the i3 SHM log. */
+static char *logbuffer;
+/* A pointer (within logbuffer) where data will be written to next. */
+static char *logwalk;
+/* A pointer to the byte where we last wrapped. Necessary to not print the
+ * left-overs at the end of the ringbuffer. */
+static char *loglastwrap;
+/* Size (in bytes) of the i3 SHM log. */
+static int logbuffer_size;
+/* File descriptor for shm_open. */
+static int logbuffer_shm;
+
+/*
+ * Writes the offsets for the next write and for the last wrap to the
+ * shmlog_header.
+ * Necessary to print the i3 SHM log in the correct order.
+ *
+ */
+static void store_log_markers() {
+    i3_shmlog_header *header = (i3_shmlog_header*)logbuffer;
+
+    header->offset_next_write = (logwalk - logbuffer);
+    header->offset_last_wrap = (loglastwrap - logbuffer);
+    header->size = logbuffer_size;
+}
+
 /*
  * Initializes logging by creating an error logfile in /tmp (or
  * XDG_RUNTIME_DIR, see get_process_filename()).
  *
+ * Will be called twice if --shmlog-size is specified.
+ *
  */
 void init_logging() {
-    errorfilename = get_process_filename("errorlog");
-    if (errorfilename == NULL) {
-        ELOG("Could not initialize errorlog\n");
-        return;
+    if (!errorfilename) {
+        if (!(errorfilename = get_process_filename("errorlog")))
+            ELOG("Could not initialize errorlog\n");
+        else {
+            errorfile = fopen(errorfilename, "w");
+            if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) {
+                ELOG("Could not set close-on-exec flag\n");
+            }
+        }
     }
 
-    errorfile = fopen(errorfilename, "w");
-    if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) {
-        ELOG("Could not set close-on-exec flag\n");
+    /* If this is a debug build (not a release version), we will enable SHM
+     * logging by default, unless the user turned it off explicitly. */
+    if (logbuffer == NULL && shmlog_size > 0) {
+        /* Reserve 1% of the RAM for the logfile, but at max 25 MiB.
+         * For 512 MiB of RAM this will lead to a 5 MiB log buffer.
+         * At the moment (2011-12-10), no testcase leads to an i3 log
+         * of more than ~ 600 KiB. */
+        long long physical_mem_bytes = (long long)sysconf(_SC_PHYS_PAGES) *
+                                                  sysconf(_SC_PAGESIZE);
+        logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size);
+        sasprintf(&shmlogname, "/i3-log-%d", getpid());
+        logbuffer_shm = shm_open(shmlogname, O_RDWR | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE);
+        if (logbuffer_shm == -1) {
+            ELOG("Could not shm_open SHM segment for the i3 log: %s\n", strerror(errno));
+            return;
+        }
+
+        if (ftruncate(logbuffer_shm, logbuffer_size) == -1) {
+            close(logbuffer_shm);
+            shm_unlink("/i3-log-");
+            ELOG("Could not ftruncate SHM segment for the i3 log: %s\n", strerror(errno));
+            return;
+        }
+
+        logbuffer = mmap(NULL, logbuffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
+        if (logbuffer == MAP_FAILED) {
+            close(logbuffer_shm);
+            shm_unlink("/i3-log-");
+            ELOG("Could not mmap SHM segment for the i3 log: %s\n", strerror(errno));
+            logbuffer = NULL;
+            return;
+        }
+        logwalk = logbuffer + sizeof(i3_shmlog_header);
+        loglastwrap = logbuffer + logbuffer_size;
+        store_log_markers();
     }
 }
 
@@ -79,28 +158,75 @@ void add_loglevel(const char *level) {
 }
 
 /*
- * Logs the given message to stdout while prefixing the current time to it.
+ * Logs the given message to stdout (if print is true) while prefixing the
+ * current time to it. Additionally, the message will be saved in the i3 SHM
+ * log if enabled.
  * This is to be called by *LOG() which includes filename/linenumber/function.
  *
  */
-void vlog(char *fmt, va_list args) {
-    static char timebuf[64];
+static void vlog(const bool print, const char *fmt, va_list args) {
+    /* Precisely one page to not consume too much memory but to hold enough
+     * data to be useful. */
+    static char message[4096];
     static struct tm result;
+    static time_t t;
+    static struct tm *tmp;
+    static size_t len;
 
     /* Get current time */
-    time_t t = time(NULL);
+    t = time(NULL);
     /* Convert time to local time (determined by the locale) */
-    struct tm *tmp = localtime_r(&t, &result);
+    tmp = localtime_r(&t, &result);
     /* Generate time prefix */
-    strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp);
+    len = strftime(message, sizeof(message), "%x %X - ", tmp);
+
+    /*
+     * logbuffer  print
+     * ----------------
+     *  true      true   format message, save, print
+     *  true      false  format message, save
+     *  false     true   print message only
+     *  false     false  INVALID, never called
+     */
+    if (!logbuffer) {
 #ifdef DEBUG_TIMING
-    struct timeval tv;
-    gettimeofday(&tv, NULL);
-    printf("%s%d.%d - ", timebuf, tv.tv_sec, tv.tv_usec);
+        struct timeval tv;
+        gettimeofday(&tv, NULL);
+        printf("%s%d.%d - ", message, tv.tv_sec, tv.tv_usec);
 #else
-    printf("%s", timebuf);
+        printf("%s", message);
 #endif
-    vprintf(fmt, args);
+        vprintf(fmt, args);
+    } else {
+        len += vsnprintf(message + len, sizeof(message) - len, fmt, args);
+        if (len < 0 ) {
+            fprintf(stderr, "BUG: something is overflowing here. Dropping the log entry\n");
+            return;
+        }
+
+        if (len >= sizeof(message)) {
+            fprintf(stderr, "BUG: single log message > 4k\n");
+        }
+
+        /* If there is no space for the current message (plus trailing
+         * nullbyte) in the ringbuffer, we need to wrap and write to the
+         * beginning again. */
+        if ((len+1) >= (logbuffer_size - (logwalk - logbuffer))) {
+            loglastwrap = logwalk;
+            logwalk = logbuffer + sizeof(i3_shmlog_header);
+        }
+
+        /* Copy the buffer, terminate it, move the write pointer to the byte after
+         * our current message. */
+        strncpy(logwalk, message, len);
+        logwalk[len] = '\0';
+        logwalk += len + 1;
+
+        store_log_markers();
+
+        if (print)
+            fwrite(message, len, 1, stdout);
+    }
 }
 
 /*
@@ -111,11 +237,11 @@ void vlog(char *fmt, va_list args) {
 void verboselog(char *fmt, ...) {
     va_list args;
 
-    if (!verbose)
+    if (!logbuffer && !verbose)
         return;
 
     va_start(args, fmt);
-    vlog(fmt, args);
+    vlog(verbose, fmt, args);
     va_end(args);
 }
 
@@ -127,7 +253,7 @@ void errorlog(char *fmt, ...) {
     va_list args;
 
     va_start(args, fmt);
-    vlog(fmt, args);
+    vlog(true, fmt, args);
     va_end(args);
 
     /* also log to the error logfile, if opened */
@@ -146,10 +272,10 @@ void errorlog(char *fmt, ...) {
 void debuglog(uint64_t lev, char *fmt, ...) {
     va_list args;
 
-    if ((loglevel & lev) == 0)
+    if (!logbuffer && !(loglevel & lev))
         return;
 
     va_start(args, fmt);
-    vlog(fmt, args);
+    vlog((loglevel & lev), fmt, args);
     va_end(args);
 }
index 5a3468da4095e47cc8eb121ef5fa63b634bae37e..76ea838d71ce9143f4cbfe552fefdff0901d8714 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * main.c: Initialization, main loop
  *
@@ -14,6 +14,8 @@
 #include <sys/un.h>
 #include <sys/time.h>
 #include <sys/resource.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
 #include "all.h"
 
 #include "sd-daemon.h"
  * RLIM_INFINITY for i3 debugging versions. */
 struct rlimit original_rlimit_core;
 
+/* Whether this version of i3 is a debug build or a release build. */
+bool debug_build = false;
+
+/** The number of file descriptors passed via socket activation. */
+int listen_fds;
+
 static int xkb_event_base;
 
 int xkb_current_group;
@@ -45,7 +53,13 @@ xcb_timestamp_t last_timestamp = XCB_CURRENT_TIME;
 
 xcb_screen_t *root_screen;
 xcb_window_t root;
+
+/* Color depth, visual id and colormap to use when creating windows and
+ * pixmaps. Will use 32 bit depth and an appropriate visual, if available,
+ * otherwise the root window’s default (usually 24 bit TrueColor). */
 uint8_t root_depth;
+xcb_visualid_t visual_id;
+xcb_colormap_t colormap;
 
 struct ev_loop *main_loop;
 
@@ -205,6 +219,28 @@ static void i3_exit() {
 #if EV_VERSION_MAJOR >= 4
     ev_loop_destroy(main_loop);
 #endif
+
+    if (*shmlogname != '\0') {
+        fprintf(stderr, "Closing SHM log \"%s\"\n", shmlogname);
+        fflush(stderr);
+        shm_unlink(shmlogname);
+    }
+}
+
+/*
+ * (One-shot) Handler for all signals with default action "Term", see signal(7)
+ *
+ * Unlinks the SHM log and re-raises the signal.
+ *
+ */
+static void handle_signal(int sig, siginfo_t *info, void *data) {
+    fprintf(stderr, "Received signal %d, terminating\n", sig);
+    if (*shmlogname != '\0') {
+        fprintf(stderr, "Closing SHM log \"%s\"\n", shmlogname);
+        shm_unlink(shmlogname);
+    }
+    fflush(stderr);
+    raise(sig);
 }
 
 int main(int argc, char *argv[]) {
@@ -214,6 +250,7 @@ int main(int argc, char *argv[]) {
     bool delete_layout_path = false;
     bool force_xinerama = false;
     bool disable_signalhandler = false;
+    bool enable_32bit_visual = false;
     static struct option long_options[] = {
         {"no-autostart", no_argument, 0, 'a'},
         {"config", required_argument, 0, 'c'},
@@ -224,11 +261,16 @@ int main(int argc, char *argv[]) {
         {"force-xinerama", no_argument, 0, 0},
         {"force_xinerama", no_argument, 0, 0},
         {"disable-signalhandler", no_argument, 0, 0},
+        {"shmlog-size", required_argument, 0, 0},
+        {"shmlog_size", required_argument, 0, 0},
         {"get-socketpath", no_argument, 0, 0},
         {"get_socketpath", no_argument, 0, 0},
+        {"enable-32bit-visual", no_argument, 0, 0},
+        {"enable_32bit_visual", no_argument, 0, 0},
         {0, 0, 0, 0}
     };
     int option_index = 0, opt;
+    xcb_void_cookie_t colormap_cookie;
 
     setlocale(LC_ALL, "");
 
@@ -242,8 +284,21 @@ int main(int argc, char *argv[]) {
 
     srand(time(NULL));
 
+    /* Init logging *before* initializing debug_build to guarantee early
+     * (file) logging. */
     init_logging();
 
+    /* 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. */
+    debug_build = ((strchr(I3_VERSION, '(') - I3_VERSION) > 10);
+
+    /* On non-release builds, disable SHM logging by default. */
+    shmlog_size = (debug_build ? 25 * 1024 * 1024 : 0);
+
     start_argv = argv;
 
     while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) {
@@ -293,18 +348,31 @@ int main(int argc, char *argv[]) {
                     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();
+                    char *socket_path = root_atom_contents("I3_SOCKET_PATH");
                     if (socket_path) {
                         printf("%s\n", socket_path);
                         return 0;
                     }
 
                     return 1;
+                } else if (strcmp(long_options[option_index].name, "shmlog-size") == 0 ||
+                           strcmp(long_options[option_index].name, "shmlog_size") == 0) {
+                    shmlog_size = atoi(optarg);
+                    /* Re-initialize logging immediately to get as many
+                     * logmessages as possible into the SHM log. */
+                    init_logging();
+                    LOG("Limiting SHM log size to %d bytes\n", shmlog_size);
+                    break;
                 } else if (strcmp(long_options[option_index].name, "restart") == 0) {
                     FREE(layout_path);
                     layout_path = sstrdup(optarg);
                     delete_layout_path = true;
                     break;
+                } else if (strcmp(long_options[option_index].name, "enable_32bit_visual") == 0 ||
+                           strcmp(long_options[option_index].name, "enable-32bit-visual") == 0) {
+                    LOG("Enabling 32 bit visual (if available)\n");
+                    enable_32bit_visual = true;
+                    break;
                 }
                 /* fall-through */
             default:
@@ -326,6 +394,15 @@ int main(int argc, char *argv[]) {
                 fprintf(stderr, "\t--get-socketpath\n"
                                 "\tRetrieve the i3 IPC socket path from X11, print it, then exit.\n");
                 fprintf(stderr, "\n");
+                fprintf(stderr, "\t--shmlog-size <limit>\n"
+                                "\tLimits the size of the i3 SHM log to <limit> bytes. Setting this\n"
+                                "\tto 0 disables SHM logging entirely.\n"
+                                "\tThe default is %d bytes.\n", shmlog_size);
+                fprintf(stderr, "\n");
+                fprintf(stderr, "\t--enable-32bit-visual\n"
+                                "\tMakes i3 use a 32 bit visual, if available. Necessary for\n"
+                                "\tpseudo-transparency with xcompmgr.\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"
@@ -361,7 +438,7 @@ int main(int argc, char *argv[]) {
             optind++;
         }
         LOG("Command is: %s (%d bytes)\n", payload, strlen(payload));
-        char *socket_path = socket_path_from_x11();
+        char *socket_path = root_atom_contents("I3_SOCKET_PATH");
         if (!socket_path) {
             ELOG("Could not get i3 IPC socket path\n");
             return 1;
@@ -395,13 +472,11 @@ int main(int argc, char *argv[]) {
         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) {
+    /* Enable logging to handle the case when the user did not specify --shmlog-size */
+    init_logging();
+
+    /* Try to enable core dumps by default when running a debug build */
+    if (debug_build) {
         struct rlimit limit = { RLIM_INFINITY, RLIM_INFINITY };
         setrlimit(RLIMIT_CORE, &limit);
 
@@ -438,7 +513,38 @@ int main(int argc, char *argv[]) {
 
     root_screen = xcb_aux_get_screen(conn, conn_screen);
     root = root_screen->root;
+
+    /* By default, we use the same depth and visual as the root window, which
+     * usually is TrueColor (24 bit depth) and the corresponding visual.
+     * However, we also check if a 32 bit depth and visual are available (for
+     * transparency) and use it if so. */
     root_depth = root_screen->root_depth;
+    visual_id = root_screen->root_visual;
+    colormap = root_screen->default_colormap;
+
+    if (enable_32bit_visual) {
+        xcb_depth_iterator_t depth_iter;
+        xcb_visualtype_iterator_t visual_iter;
+        for (depth_iter = xcb_screen_allowed_depths_iterator(root_screen);
+             depth_iter.rem;
+             xcb_depth_next(&depth_iter)) {
+            if (depth_iter.data->depth != 32)
+                continue;
+            visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
+            if (!visual_iter.rem)
+                continue;
+
+            visual_id = visual_iter.data->visual_id;
+            root_depth = depth_iter.data->depth;
+            colormap = xcb_generate_id(conn);
+            colormap_cookie = xcb_create_colormap_checked(conn, XCB_COLORMAP_ALLOC_NONE, colormap, root, visual_id);
+            DLOG("Found a visual with 32 bit depth.\n");
+            break;
+        }
+    }
+
+    DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_id);
+
     xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
     xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root);
 
@@ -468,6 +574,20 @@ int main(int argc, char *argv[]) {
     cookie = xcb_change_window_attributes_checked(conn, root, mask, values);
     check_error(conn, cookie, "Another window manager seems to be running");
 
+    /* By now we already checked for replies once, so let’s see if colormap
+     * creation worked (if requested). */
+    if (colormap != root_screen->default_colormap) {
+        xcb_generic_error_t *error = xcb_request_check(conn, colormap_cookie);
+        if (error != NULL) {
+            ELOG("Could not create ColorMap for 32 bit visual, falling back to X11 default.\n");
+            root_depth = root_screen->root_depth;
+            visual_id = root_screen->root_visual;
+            colormap = root_screen->default_colormap;
+            DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_id);
+            free(error);
+        }
+    }
+
     xcb_get_geometry_reply_t *greply = xcb_get_geometry_reply(conn, gcookie, NULL);
     if (greply == NULL) {
         ELOG("Could not get geometry of the root window, exiting\n");
@@ -606,15 +726,29 @@ 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)
+    /* Also handle the UNIX domain sockets passed via socket activation. The
+     * parameter 1 means "remove the environment variables", we don’t want to
+     * pass these to child processes. */
+    listen_fds = sd_listen_fds(0);
+    if (listen_fds < 0)
         ELOG("socket activation: Error in sd_listen_fds\n");
-    else if (fds == 0)
+    else if (listen_fds == 0)
         DLOG("socket activation: no sockets passed\n");
     else {
-        for (int fd = SD_LISTEN_FDS_START; fd < (SD_LISTEN_FDS_START + fds); fd++) {
+        int flags;
+        for (int fd = SD_LISTEN_FDS_START;
+             fd < (SD_LISTEN_FDS_START + listen_fds);
+             fd++) {
             DLOG("socket activation: also listening on fd %d\n", fd);
+
+            /* sd_listen_fds() enables FD_CLOEXEC by default.
+             * However, we need to keep the file descriptors open for in-place
+             * restarting, therefore we explicitly disable FD_CLOEXEC. */
+            if ((flags = fcntl(fd, F_GETFD)) < 0 ||
+                fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC) < 0) {
+                ELOG("Could not disable FD_CLOEXEC 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);
@@ -651,8 +785,31 @@ int main(int argc, char *argv[]) {
 
     manage_existing_windows(root);
 
+    struct sigaction action;
+
+    action.sa_sigaction = handle_signal;
+    action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
+    sigemptyset(&action.sa_mask);
+
     if (!disable_signalhandler)
         setup_signal_handler();
+    else {
+        /* Catch all signals with default action "Core", see signal(7) */
+        if (sigaction(SIGQUIT, &action, NULL) == -1 ||
+            sigaction(SIGILL, &action, NULL) == -1 ||
+            sigaction(SIGABRT, &action, NULL) == -1 ||
+            sigaction(SIGFPE, &action, NULL) == -1 ||
+            sigaction(SIGSEGV, &action, NULL) == -1)
+            ELOG("Could not setup signal handler");
+    }
+
+    /* Catch all signals with default action "Term", see signal(7) */
+    if (sigaction(SIGHUP, &action, NULL) == -1 ||
+        sigaction(SIGINT, &action, NULL) == -1 ||
+        sigaction(SIGALRM, &action, NULL) == -1 ||
+        sigaction(SIGUSR1, &action, NULL) == -1 ||
+        sigaction(SIGUSR2, &action, NULL) == -1)
+        ELOG("Could not setup signal handler");
 
     /* Ignore SIGPIPE to survive errors when an IPC client disconnects
      * while we are sending him a message */
@@ -678,8 +835,9 @@ int main(int argc, char *argv[]) {
     Barconfig *barconfig;
     TAILQ_FOREACH(barconfig, &barconfigs, configs) {
         char *command = NULL;
-        sasprintf(&command, "i3bar --bar_id=%s --socket=\"%s\"",
-                  barconfig->id, current_socketpath);
+        sasprintf(&command, "%s --bar_id=%s --socket=\"%s\"",
+                barconfig->i3bar_command ? barconfig->i3bar_command : "i3bar",
+                barconfig->id, current_socketpath);
         LOG("Starting bar process: %s\n", command);
         start_application(command, true);
         free(command);
index a87807b429914db043143485b0d8f7a203a44047..653de156c24d7b2703bc5d126a8d7c01c228187b 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * manage.c: Initially managing new windows (or existing ones on restart).
  *
@@ -216,7 +216,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     DLOG("Initial geometry: (%d, %d, %d, %d)\n", geom->x, geom->y, geom->width, geom->height);
 
     Con *nc = NULL;
-    Match *match;
+    Match *match = NULL;
     Assignment *assignment;
 
     /* TODO: two matches for one container */
@@ -286,7 +286,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
             Con *target_output = con_get_output(ws);
 
             if (workspace_is_visible(ws) && current_output == target_output) {
-                con_focus(nc);
+                if (!match || !match->restart_mode) {
+                    con_focus(nc);
+                } else DLOG("not focusing, matched with restart_mode == true\n");
             } else DLOG("workspace not visible, not focusing\n");
         } else DLOG("dock, not focusing\n");
     } else {
index c2773acc4cedeba9e37b2630b9ef132717be2848..e92a95d2d8458d8e84a5c3e03ab8ca8552139720 100644 (file)
  */
 #include "all.h"
 
+/* From sys/time.h, not sure if it’s available on all systems. */
+# define _i3_timercmp(a, b, CMP)                                                  \
+  (((a).tv_sec == (b).tv_sec) ?                                             \
+   ((a).tv_usec CMP (b).tv_usec) :                                          \
+   ((a).tv_sec CMP (b).tv_sec))
+
 /*
  * Initializes the Match data structure. This function is necessary because the
  * members representing boolean values (like dock) need to be initialized with
@@ -22,6 +28,7 @@
 void match_init(Match *match) {
     memset(match, 0, sizeof(Match));
     match->dock = -1;
+    match->urgent = U_DONTCHECK;
 }
 
 /*
@@ -39,6 +46,7 @@ bool match_is_empty(Match *match) {
             match->class == NULL &&
             match->instance == NULL &&
             match->role == NULL &&
+            match->urgent == U_DONTCHECK &&
             match->id == XCB_NONE &&
             match->con_id == NULL &&
             match->dock == -1 &&
@@ -120,6 +128,38 @@ bool match_matches_window(Match *match, i3Window *window) {
         }
     }
 
+    Con *con = NULL;
+    if (match->urgent == U_LATEST) {
+        /* if the window isn't urgent, no sense in searching */
+        if (window->urgent.tv_sec == 0) {
+            return false;
+        }
+        /* if we find a window that is newer than this one, bail */
+        TAILQ_FOREACH(con, &all_cons, all_cons) {
+            if ((con->window != NULL) &&
+                _i3_timercmp(con->window->urgent, window->urgent, >)) {
+                return false;
+            }
+        }
+        LOG("urgent matches latest\n");
+    }
+
+    if (match->urgent == U_OLDEST) {
+        /* if the window isn't urgent, no sense in searching */
+        if (window->urgent.tv_sec == 0) {
+            return false;
+        }
+        /* if we find a window that is older than this one (and not 0), bail */
+        TAILQ_FOREACH(con, &all_cons, all_cons) {
+            if ((con->window != NULL) &&
+                (con->window->urgent.tv_sec != 0) &&
+                _i3_timercmp(con->window->urgent, window->urgent, <)) {
+                return false;
+            }
+        }
+        LOG("urgent matches oldest\n");
+    }
+
     if (match->dock != -1) {
         if ((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) ||
          (window->dock == W_DOCK_BOTTOM && match->dock == M_DOCK_BOTTOM) ||
index 00bcf2d9c4d3bd8e44be67845cbc08a2b954536f..d3065c2490bf1faae92e6882231ece95b1a0fe16 100644 (file)
@@ -9,8 +9,6 @@
  */
 #include "all.h"
 
-#include "cmdparse.tab.h"
-
 typedef enum { BEFORE, AFTER } position_t;
 
 /*
@@ -88,8 +86,8 @@ static void attach_to_workspace(Con *con, Con *ws) {
 }
 
 /*
- * Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT,
- * TOK_UP, TOK_DOWN from cmdparse.l)
+ * Moves the current container in the given direction (D_LEFT, D_RIGHT,
+ * D_UP, D_DOWN).
  *
  */
 void tree_move(int direction) {
@@ -107,7 +105,7 @@ void tree_move(int direction) {
         return;
     }
 
-    orientation_t o = (direction == TOK_LEFT || direction == TOK_RIGHT ? HORIZ : VERT);
+    orientation_t o = (direction == D_LEFT || direction == D_RIGHT ? HORIZ : VERT);
 
     Con *same_orientation = con_parent_with_orientation(con, o);
     /* The do {} while is used to 'restart' at this point with a different
@@ -136,14 +134,14 @@ void tree_move(int direction) {
         if (same_orientation == con->parent) {
             DLOG("We are in the same container\n");
             Con *swap;
-            if ((swap = (direction == TOK_LEFT || direction == TOK_UP ?
+            if ((swap = (direction == D_LEFT || direction == D_UP ?
                           TAILQ_PREV(con, nodes_head, nodes) :
                           TAILQ_NEXT(con, nodes)))) {
                 if (!con_is_leaf(swap)) {
                     insert_con_into(con, con_descend_focused(swap), AFTER);
                     goto end;
                 }
-                if (direction == TOK_LEFT || direction == TOK_UP)
+                if (direction == D_LEFT || direction == D_UP)
                     TAILQ_SWAP(swap, con, &(swap->parent->nodes_head), nodes);
                 else TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes);
 
@@ -174,7 +172,7 @@ void tree_move(int direction) {
     DLOG("above = %p\n", above);
     Con *next;
     position_t position;
-    if (direction == TOK_UP || direction == TOK_LEFT) {
+    if (direction == D_UP || direction == D_LEFT) {
         position = BEFORE;
         next = TAILQ_PREV(above, nodes_head, nodes);
     } else {
@@ -195,5 +193,8 @@ end:
      * container(s) would still point to the old container(s)). */
     con_focus(con);
 
+    /* force re-painting the indicators */
+    FREE(con->deco_render_params);
+
     tree_flatten(croot);
 }
index e65fdbd6f8fea7827eac3242b6a926db33ff1713..85f0eab3a916153c5eec4075fac99f75b33f6544 100644 (file)
@@ -319,8 +319,6 @@ void output_init_con(Output *output) {
  *
  */
 void init_ws_for_output(Output *output, Con *content) {
-    char *name;
-
     /* go through all assignments and move the existing workspaces to this output */
     struct Workspace_Assignment *assignment;
     TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
@@ -404,96 +402,7 @@ void init_ws_for_output(Output *output, Con *content) {
 
     /* if there is still no workspace, we create the first free workspace */
     DLOG("Now adding a workspace\n");
-
-    /* add a workspace to this output */
-    Con *out, *current;
-    bool exists = true;
-    Con *ws = con_new(NULL, NULL);
-    ws->type = CT_WORKSPACE;
-
-    /* try the configured workspace bindings first to find a free name */
-    Binding *bind;
-    TAILQ_FOREACH(bind, bindings, bindings) {
-        DLOG("binding with command %s\n", bind->command);
-        if (strlen(bind->command) < strlen("workspace ") ||
-            strncasecmp(bind->command, "workspace", strlen("workspace")) != 0)
-            continue;
-        DLOG("relevant command = %s\n", bind->command);
-        char *target = bind->command + strlen("workspace ");
-        /* We check if this is the workspace next/prev/back_and_forth command.
-         * Beware: The workspace names "next", "prev" and "back_and_forth" are
-         * OK, so we check before stripping the double quotes */
-        if (strncasecmp(target, "next", strlen("next")) == 0 ||
-            strncasecmp(target, "prev", strlen("prev")) == 0 ||
-            strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0)
-            continue;
-        if (*target == '"')
-            target++;
-        FREE(ws->name);
-        ws->name = strdup(target);
-        if (ws->name[strlen(ws->name)-1] == '"')
-            ws->name[strlen(ws->name)-1] = '\0';
-        DLOG("trying name *%s*\n", ws->name);
-
-        current = NULL;
-        TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
-            GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
-
-        exists = (current != NULL);
-        if (!exists) {
-            /* Set ->num to the number of the workspace, if the name actually
-             * is a number or starts with a number */
-            char *endptr = NULL;
-            long parsed_num = strtol(ws->name, &endptr, 10);
-            if (parsed_num == LONG_MIN ||
-                parsed_num == LONG_MAX ||
-                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);
-
-            break;
-        }
-    }
-
-    if (exists) {
-        /* get the next unused workspace number */
-        DLOG("Getting next unused workspace by number\n");
-        int c = 0;
-        while (exists) {
-            c++;
-
-            FREE(ws->name);
-            sasprintf(&(ws->name), "%d", c);
-
-            current = NULL;
-            TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
-                GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
-            exists = (current != NULL);
-
-            DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists);
-        }
-        ws->num = c;
-    }
-    con_attach(ws, content, false);
-
-    sasprintf(&name, "[i3 con] workspace %s", ws->name);
-    x_set_name(ws, name);
-    free(name);
-
-    ws->fullscreen_mode = CF_OUTPUT;
-
-    /* If default_orientation is set to NO_ORIENTATION we determine
-     * orientation depending on output resolution. */
-    if (config.default_orientation == NO_ORIENTATION) {
-        ws->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
-        DLOG("Auto orientation. Workspace size set to (%d,%d), setting orientation to %d.\n",
-             output->rect.width, output->rect.height, ws->orientation);
-    } else {
-        ws->orientation = config.default_orientation;
-    }
-
+    Con *ws = create_workspace_on_output(output, content);
 
     /* TODO: Set focus in main.c */
     con_focus(ws);
@@ -760,6 +669,12 @@ void randr_query_outputs() {
                 Con *old_content = output_get_content(output->con);
                 while (!TAILQ_EMPTY(&(old_content->nodes_head))) {
                     current = TAILQ_FIRST(&(old_content->nodes_head));
+                    if (current != next && TAILQ_EMPTY(&(current->focus_head))) {
+                        /* the workspace is empty and not focused, get rid of it */
+                        DLOG("Getting rid of current = %p / %s (empty, unfocused)\n", current, current->name);
+                        tree_close(current, DONT_KILL_WINDOW, false, false);
+                        continue;
+                    }
                     DLOG("Detaching current = %p / %s\n", current, current->name);
                     con_detach(current);
                     DLOG("Re-attaching current = %p / %s\n", current, current->name);
index 2905356c9d29a6f7c846c73123a02f532166ca5c..860219df5f92fbfb76f5901308566b74280d3cfc 100644 (file)
@@ -143,8 +143,17 @@ void render_con(Con *con, bool render_fullscreen) {
         inset->width -= (2 * con->border_width);
         inset->height -= (2 * con->border_width);
 
-        /* Obey the aspect ratio, if any */
-        if (con->proportional_height != 0 &&
+        /* Obey the aspect ratio, if any, unless we are in fullscreen mode.
+         *
+         * The spec isn’t explicit on whether the aspect ratio hints should be
+         * respected during fullscreen mode. Other WMs such as Openbox don’t do
+         * that, and this post suggests that this is the correct way to do it:
+         * http://mail.gnome.org/archives/wm-spec-list/2003-May/msg00007.html
+         *
+         * Ignoring aspect ratio during fullscreen was necessary to fix MPlayer
+         * subtitle rendering, see http://bugs.i3wm.org/594 */
+        if (!render_fullscreen &&
+            con->proportional_height != 0 &&
             con->proportional_width != 0) {
             double new_height = inset->height + 1;
             int new_width = inset->width;
@@ -193,7 +202,9 @@ void render_con(Con *con, bool render_fullscreen) {
     }
 
     /* find the height for the decorations */
-    int deco_height = config.font.height + 5;
+    int deco_height = config.font.height + 4;
+    if (config.font.height & 0x01)
+        ++deco_height;
 
     /* precalculate the sizes to be able to correct rounding errors */
     int sizes[children];
@@ -219,6 +230,9 @@ void render_con(Con *con, bool render_fullscreen) {
     }
 
     if (con->layout == L_OUTPUT) {
+        /* Skip i3-internal outputs */
+        if (con->name[0] == '_' && con->name[1] == '_')
+            return;
         render_l_output(con);
     } else if (con->type == CT_ROOT) {
         Con *output;
@@ -232,6 +246,8 @@ void render_con(Con *con, bool render_fullscreen) {
          * windows/containers so that they overlap on another output. */
         DLOG("Rendering floating windows:\n");
         TAILQ_FOREACH(output, &(con->nodes_head), nodes) {
+            if (output->name[0] == '_' && output->name[1] == '_')
+                continue;
             /* Get the active workspace of that output */
             Con *content = output_get_content(output);
             Con *workspace = TAILQ_FIRST(&(content->focus_head));
diff --git a/src/scratchpad.c b/src/scratchpad.c
new file mode 100644 (file)
index 0000000..b00d7f6
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * scratchpad.c: Moving windows to the scratchpad and making them visible again.
+ *
+ */
+#include "all.h"
+
+/*
+ * Moves the specified window to the __i3_scratch workspace, making it floating
+ * and setting the appropriate scratchpad_state.
+ *
+ * Gets called upon the command 'move scratchpad'.
+ *
+ */
+void scratchpad_move(Con *con) {
+    if (con->type == CT_WORKSPACE) {
+        LOG("'move scratchpad' used on a workspace \"%s\". Calling it "
+            "recursively on all windows on this workspace.\n", con->name);
+        Con *current;
+        current = TAILQ_FIRST(&(con->focus_head));
+        while (current) {
+            Con *next = TAILQ_NEXT(current, focused);
+            scratchpad_move(current);
+            current = next;
+        }
+        return;
+    }
+    DLOG("should move con %p to __i3_scratch\n", con);
+
+    Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
+    if (con_get_workspace(con) == __i3_scratch) {
+        DLOG("This window is already on __i3_scratch.\n");
+        return;
+    }
+
+    /* 1: Ensure the window is floating. From now on, we deal with the
+     * CT_FLOATING_CON. We use automatic == false because the user made the
+     * choice that this window should be a scratchpad (and floating). */
+    floating_enable(con, false);
+    con = con->parent;
+
+    /* 2: Send the window to the __i3_scratch workspace, mainting its
+     * coordinates and not warping the pointer. */
+    Con *focus_next = con_next_focused(con);
+    con_move_to_workspace(con, __i3_scratch, true, true);
+
+    /* 3: If this is the first time this window is used as a scratchpad, we set
+     * the scratchpad_state to SCRATCHPAD_FRESH. The window will then be
+     * adjusted in size according to what the user specifies. */
+    if (con->scratchpad_state == SCRATCHPAD_NONE) {
+        DLOG("This window was never used as a scratchpad before.\n");
+        con->scratchpad_state = SCRATCHPAD_FRESH;
+    }
+
+    /* 4: Fix focus. Normally, when moving a window to a different output, the
+     * destination output gets focused. In this case, we don’t want that. */
+    con_focus(focus_next);
+}
+
+/*
+ * Either shows the top-most scratchpad window (con == NULL) or shows the
+ * specified con (if it is scratchpad window).
+ *
+ * When called with con == NULL and the currently focused window is a
+ * scratchpad window, this serves as a shortcut to hide it again (so the user
+ * can press the same key to quickly look something up).
+ *
+ */
+void scratchpad_show(Con *con) {
+    DLOG("should show scratchpad window %p\n", con);
+    Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
+    Con *floating;
+
+    /* If this was 'scratchpad show' without criteria, we check if the
+     * currently focused window is a scratchpad window and should be hidden
+     * again. */
+    if (!con &&
+        (floating = con_inside_floating(focused)) &&
+        floating->scratchpad_state != SCRATCHPAD_NONE) {
+        DLOG("Focused window is a scratchpad window, hiding it.\n");
+        scratchpad_move(focused);
+        return;
+    }
+
+    /* If this was 'scratchpad show' with criteria, we check if it matches a
+     * currently visible scratchpad window and hide it. */
+    if (con &&
+        (floating = con_inside_floating(con)) &&
+        floating->scratchpad_state != SCRATCHPAD_NONE &&
+        con_get_workspace(con) != __i3_scratch) {
+        DLOG("Window is a scratchpad window, hiding it.\n");
+        scratchpad_move(con);
+        return;
+    }
+
+    Con *ws = con_get_workspace(focused);
+    if (con == NULL) {
+        /* Use the container on __i3_scratch which is highest in the focus
+         * stack. When moving windows to __i3_scratch, they get inserted at the
+         * bottom of the stack. */
+        con = TAILQ_FIRST(&(__i3_scratch->floating_head));
+
+        if (!con) {
+            LOG("You don't have any scratchpad windows yet.\n");
+            LOG("Use 'move scratchpad' to move a window to the scratchpad.\n");
+            return;
+        }
+    }
+
+    /* 1: Move the window from __i3_scratch to the current workspace. */
+    con_move_to_workspace(con, ws, true, false);
+
+    /* 2: Adjust the size if this window was not adjusted yet. */
+    if (con->scratchpad_state == SCRATCHPAD_FRESH) {
+        DLOG("Adjusting size of this window.\n");
+        Con *output = con_get_output(con);
+        con->rect.width = output->rect.width * 0.5;
+        con->rect.height = output->rect.height * 0.75;
+        con->rect.x = output->rect.x +
+                      ((output->rect.width / 2.0) - (con->rect.width / 2.0));
+        con->rect.y = output->rect.y +
+                      ((output->rect.height / 2.0) - (con->rect.height / 2.0));
+        con->scratchpad_state = SCRATCHPAD_CHANGED;
+    }
+
+    con_focus(con_descend_focused(con));
+}
index a029422bf778645f1396773b49122ad585f132ad..4a0c13b5aa1a396f40fa5f29af9c7ff820652aa8 100644 (file)
@@ -47,15 +47,11 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei
     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") });
+    set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
 
     for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) {
-        int text_len = strlen(crash_text[i]);
-        char *full_text = convert_utf8_to_ucs2(crash_text[i], &text_len);
-        xcb_image_text_16(conn, text_len, pixmap, pixmap_gc, 8 /* X */,
-                          3 + (i + 1) * font_height /* Y = baseline of font */,
-                          (xcb_char2b_t*)full_text);
-        free(full_text);
+        draw_text(crash_text[i], strlen(crash_text[i]), false, pixmap, pixmap_gc,
+                8, 5 + i * font_height, width - 16);
     }
 
     /* Copy the contents of the pixmap to the real window */
@@ -150,9 +146,9 @@ void handle_signal(int sig, siginfo_t *info, void *data) {
     int height = 13 + (crash_text_num * config.font.height);
 
     /* calculate width for longest text */
-    int text_len = strlen(crash_text[crash_text_longest]);
-    char *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len);
-    int font_width = predict_text_width(longest_text, text_len);
+    size_t text_len = strlen(crash_text[crash_text_longest]);
+    xcb_char2b_t *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len);
+    int font_width = predict_text_width((char *)longest_text, text_len, true);
     int width = font_width + 20;
 
     /* Open a popup window on each virtual screen */
@@ -169,9 +165,6 @@ void handle_signal(int sig, siginfo_t *info, void *data) {
         xcb_create_pixmap(conn, root_depth, pixmap, win, width, height);
         xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
 
-        /* Create graphics context */
-        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);
 
@@ -206,8 +199,11 @@ void setup_signal_handler() {
     action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
     sigemptyset(&action.sa_mask);
 
-    if (sigaction(SIGSEGV, &action, NULL) == -1 ||
+    /* Catch all signals with default action "Core", see signal(7) */
+    if (sigaction(SIGQUIT, &action, NULL) == -1 ||
+        sigaction(SIGILL, &action, NULL) == -1 ||
         sigaction(SIGABRT, &action, NULL) == -1 ||
-        sigaction(SIGFPE, &action, NULL) == -1)
+        sigaction(SIGFPE, &action, NULL) == -1 ||
+        sigaction(SIGSEGV, &action, NULL) == -1)
         ELOG("Could not setup signal handler");
 }
index 86e66eaa380e8463d144a87dadf4698b8873ec4d..bcc2415af10a34c1d98b883244fa3f126a17d9f8 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 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
@@ -11,6 +11,7 @@
  *
  */
 #include "all.h"
+#include "sd-daemon.h"
 
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -113,6 +114,15 @@ void start_application(const char *command, bool no_startup_id) {
         /* Child process */
         setsid();
         setrlimit(RLIMIT_CORE, &original_rlimit_core);
+        /* Close all socket activation file descriptors explicitly, we disabled
+         * FD_CLOEXEC to keep them open when restarting i3. */
+        for (int fd = SD_LISTEN_FDS_START;
+             fd < (SD_LISTEN_FDS_START + listen_fds);
+             fd++) {
+            close(fd);
+        }
+        unsetenv("LISTEN_PID");
+        unsetenv("LISTEN_FDS");
         if (fork() == 0) {
             /* Setup the environment variable(s) */
             if (!no_startup_id)
index f5bac1939b0425b84f106d7a6d990323eb31b44f..5559908f0738d20afbbed8e9b5f048a7609a4e65 100644 (file)
@@ -15,7 +15,48 @@ struct Con *focused;
 struct all_cons_head all_cons = TAILQ_HEAD_INITIALIZER(all_cons);
 
 /*
- * Loads tree from ~/.i3/_restart.json (used for in-place restarts).
+ * Create the pseudo-output __i3. Output-independent workspaces such as
+ * __i3_scratch will live there.
+ *
+ */
+static Con *_create___i3() {
+    Con *__i3 = con_new(croot, NULL);
+    FREE(__i3->name);
+    __i3->name = sstrdup("__i3");
+    __i3->type = CT_OUTPUT;
+    __i3->layout = L_OUTPUT;
+    con_fix_percent(croot);
+    x_set_name(__i3, "[i3 con] pseudo-output __i3");
+    /* For retaining the correct position/size of a scratchpad window, the
+     * dimensions of the real outputs should be multiples of the __i3
+     * pseudo-output. */
+    __i3->rect.width = 1280;
+    __i3->rect.height = 1024;
+
+    /* Add a content container. */
+    DLOG("adding main content container\n");
+    Con *content = con_new(NULL, NULL);
+    content->type = CT_CON;
+    FREE(content->name);
+    content->name = sstrdup("content");
+
+    x_set_name(content, "[i3 con] content __i3");
+    con_attach(content, __i3, false);
+
+    /* Attach the __i3_scratch workspace. */
+    Con *ws = con_new(NULL, NULL);
+    ws->type = CT_WORKSPACE;
+    ws->num = -1;
+    ws->name = sstrdup("__i3_scratch");
+    con_attach(ws, content, false);
+    x_set_name(ws, "[i3 con] workspace __i3_scratch");
+    ws->fullscreen_mode = CF_OUTPUT;
+
+    return __i3;
+}
+
+/*
+ * Loads tree from 'path' (used for in-place restarts).
  *
  */
 bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) {
@@ -47,6 +88,17 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) {
     Con *ws = TAILQ_FIRST(&(out->nodes_head));
     printf("ws = %p\n", ws);
 
+    /* For in-place restarting into v4.2, we need to make sure the new
+     * pseudo-output __i3 is present. */
+    if (strcmp(out->name, "__i3") != 0) {
+        DLOG("Adding pseudo-output __i3 during inplace restart\n");
+        Con *__i3 = _create___i3();
+        /* Ensure that it is the first output, other places in the code make
+         * that assumption. */
+        TAILQ_REMOVE(&(croot->nodes_head), __i3, nodes);
+        TAILQ_INSERT_HEAD(&(croot->nodes_head), __i3, nodes);
+    }
+
     return true;
 }
 
@@ -66,6 +118,8 @@ void tree_init(xcb_get_geometry_reply_t *geometry) {
         geometry->width,
         geometry->height
     };
+
+    _create___i3();
 }
 
 /*
@@ -288,6 +342,11 @@ void tree_split(Con *con, orientation_t orientation) {
     }
 
     Con *parent = con->parent;
+
+    /* Force re-rendering to make the indicator border visible. */
+    FREE(con->deco_render_params);
+    FREE(parent->deco_render_params);
+
     /* if we are in a container whose parent contains only one
      * child (its split functionality is unused so far), we just change the
      * orientation (more intuitive than splitting again) */
index ba0b2c4b8be6dbe2ac22ae83b1824759885ff099..70984f2b7d5bd6422780981f459030428fb18788 100644 (file)
@@ -12,7 +12,6 @@
 
 #include <sys/wait.h>
 #include <stdarg.h>
-#include <iconv.h>
 #if defined(__OpenBSD__)
 #include <sys/cdefs.h>
 #endif
@@ -24,8 +23,6 @@
 #define SN_API_NOT_YET_FROZEN 1
 #include <libsn/sn-launcher.h>
 
-static iconv_t conversion_descriptor = 0;
-
 int min(int a, int b) {
     return (a < b ? a : b);
 }
@@ -120,51 +117,6 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes
     }
 }
 
-/*
- * Converts the given string to UCS-2 big endian for use with
- * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
- * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
- * returned. It has to be freed when done.
- *
- */
-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 = 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_descriptor == 0) {
-        conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
-        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");
-        FREE(buffer);
-        if (real_strlen != NULL)
-            *real_strlen = 0;
-        return NULL;
-    }
-
-    if (real_strlen != NULL)
-        *real_strlen = ((buffer_size - output_size) / 2) - 1;
-
-    return buffer;
-}
-
 /*
  * This function resolves ~ in pathnames.
  * It may resolve wildcards in the first part of the path, but if no match
index e630e776c001d061a255e7f1b6b8011949929d68..e9e61f16c01aa57c64ac0a28ea425bd81afb028a 100644 (file)
@@ -68,8 +68,8 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
         return;
     }
     /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */
-    int len;
-    char *ucs2_name = convert_utf8_to_ucs2(new_name, &len);
+    size_t len;
+    xcb_char2b_t *ucs2_name = convert_utf8_to_ucs2(new_name, &len);
     if (ucs2_name == NULL) {
         LOG("Could not convert _NET_WM_NAME to UCS-2, ignoring new hint\n");
         FREE(new_name);
@@ -79,7 +79,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
     FREE(win->name_x);
     FREE(win->name_json);
     win->name_json = new_name;
-    win->name_x = ucs2_name;
+    win->name_x = (char*)ucs2_name;
     win->name_len = len;
     win->name_x_changed = true;
     LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json);
index 1ccae967d4a76152a71489a7e9eda87b779fb1e4..4fc8ba1bda3c421905c7a3b1720cd1cc7e506879 100644 (file)
@@ -88,6 +88,127 @@ Con *workspace_get(const char *num, bool *created) {
     return workspace;
 }
 
+/*
+ * Returns a pointer to a new workspace in the given output. The workspace
+ * is created attached to the tree hierarchy through the given content
+ * container.
+ *
+ */
+Con *create_workspace_on_output(Output *output, Con *content) {
+    /* add a workspace to this output */
+    Con *out, *current;
+    char *name;
+    bool exists = true;
+    Con *ws = con_new(NULL, NULL);
+    ws->type = CT_WORKSPACE;
+
+    /* try the configured workspace bindings first to find a free name */
+    Binding *bind;
+    TAILQ_FOREACH(bind, bindings, bindings) {
+        DLOG("binding with command %s\n", bind->command);
+        if (strlen(bind->command) < strlen("workspace ") ||
+            strncasecmp(bind->command, "workspace", strlen("workspace")) != 0)
+            continue;
+        DLOG("relevant command = %s\n", bind->command);
+        char *target = bind->command + strlen("workspace ");
+        /* We check if this is the workspace
+         * next/prev/next_on_output/prev_on_output/back_and_forth command.
+         * Beware: The workspace names "next", "prev", "next_on_output",
+         * "prev_on_output" and "back_and_forth" are OK, so we check before
+         * stripping the double quotes */
+        if (strncasecmp(target, "next", strlen("next")) == 0 ||
+            strncasecmp(target, "prev", strlen("prev")) == 0 ||
+            strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
+            strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 ||
+            strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0)
+            continue;
+        if (*target == '"')
+            target++;
+        FREE(ws->name);
+        ws->name = strdup(target);
+        if (ws->name[strlen(ws->name)-1] == '"')
+            ws->name[strlen(ws->name)-1] = '\0';
+        DLOG("trying name *%s*\n", ws->name);
+
+        /* Ensure that this workspace is not assigned to a different output —
+         * otherwise we would create it, then move it over to its output, then
+         * find a new workspace, etc… */
+        bool assigned = false;
+        struct Workspace_Assignment *assignment;
+        TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
+            if (strcmp(assignment->name, ws->name) != 0 ||
+                strcmp(assignment->output, output->name) == 0)
+                continue;
+
+            assigned = true;
+            break;
+        }
+
+        if (assigned)
+            continue;
+
+        current = NULL;
+        TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
+            GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
+
+        exists = (current != NULL);
+        if (!exists) {
+            /* Set ->num to the number of the workspace, if the name actually
+             * is a number or starts with a number */
+            char *endptr = NULL;
+            long parsed_num = strtol(ws->name, &endptr, 10);
+            if (parsed_num == LONG_MIN ||
+                parsed_num == LONG_MAX ||
+                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);
+
+            break;
+        }
+    }
+
+    if (exists) {
+        /* get the next unused workspace number */
+        DLOG("Getting next unused workspace by number\n");
+        int c = 0;
+        while (exists) {
+            c++;
+
+            FREE(ws->name);
+            sasprintf(&(ws->name), "%d", c);
+
+            current = NULL;
+            TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
+                GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
+            exists = (current != NULL);
+
+            DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists);
+        }
+        ws->num = c;
+    }
+    con_attach(ws, content, false);
+
+    sasprintf(&name, "[i3 con] workspace %s", ws->name);
+    x_set_name(ws, name);
+    free(name);
+
+    ws->fullscreen_mode = CF_OUTPUT;
+
+    /* If default_orientation is set to NO_ORIENTATION we determine
+     * orientation depending on output resolution. */
+    if (config.default_orientation == NO_ORIENTATION) {
+        ws->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
+        DLOG("Auto orientation. Workspace size set to (%d,%d), setting orientation to %d.\n",
+             output->rect.width, output->rect.height, ws->orientation);
+    } else {
+        ws->orientation = config.default_orientation;
+    }
+
+    return ws;
+}
+
 /*
  * Returns true if the workspace is currently visible. Especially important for
  * multi-monitor environments, as they can have multiple currenlty active
@@ -185,6 +306,10 @@ static void workspace_reassign_sticky(Con *con) {
 static void _workspace_show(Con *workspace, bool changed_num_workspaces) {
     Con *current, *old = NULL;
 
+    /* safe-guard against showing i3-internal workspaces like __i3_scratch */
+    if (workspace->name[0] == '_' && workspace->name[1] == '_')
+        return;
+
     /* disable fullscreen for the other workspaces and get the workspace we are
      * currently on. */
     TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) {
@@ -278,7 +403,10 @@ Con* workspace_next() {
         next = TAILQ_NEXT(current, nodes);
     } else {
         /* If currently a numbered workspace, find next numbered workspace. */
-        TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
+        TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+            /* Skip outputs starting with __, they are internal. */
+            if (output->name[0] == '_' && output->name[1] == '_')
+                continue;
             NODES_FOREACH(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
                     continue;
@@ -290,12 +418,16 @@ Con* workspace_next() {
                 if (current->num < child->num && (!next || child->num < next->num))
                     next = child;
             }
+        }
     }
 
     /* Find next named workspace. */
     if (!next) {
         bool found_current = false;
-        TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
+        TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+            /* Skip outputs starting with __, they are internal. */
+            if (output->name[0] == '_' && output->name[1] == '_')
+                continue;
             NODES_FOREACH(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
                     continue;
@@ -306,17 +438,22 @@ Con* workspace_next() {
                     goto workspace_next_end;
                 }
             }
+        }
     }
 
     /* Find first workspace. */
     if (!next) {
-        TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
+        TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+            /* Skip outputs starting with __, they are internal. */
+            if (output->name[0] == '_' && output->name[1] == '_')
+                continue;
             NODES_FOREACH(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
                     continue;
                 if (!next || (child->num != -1 && child->num < next->num))
                     next = child;
             }
+        }
     }
 workspace_next_end:
     return next;
@@ -338,7 +475,10 @@ Con* workspace_prev() {
             prev = NULL;
     } else {
         /* If numbered workspace, find previous numbered workspace. */
-        TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes)
+        TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
+            /* Skip outputs starting with __, they are internal. */
+            if (output->name[0] == '_' && output->name[1] == '_')
+                continue;
             NODES_FOREACH_REVERSE(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE || child->num == -1)
                     continue;
@@ -348,12 +488,16 @@ Con* workspace_prev() {
                 if (current->num > child->num && (!prev || child->num > prev->num))
                     prev = child;
             }
+        }
     }
 
     /* Find previous named workspace. */
     if (!prev) {
         bool found_current = false;
-        TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes)
+        TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
+            /* Skip outputs starting with __, they are internal. */
+            if (output->name[0] == '_' && output->name[1] == '_')
+                continue;
             NODES_FOREACH_REVERSE(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
                     continue;
@@ -364,23 +508,140 @@ Con* workspace_prev() {
                     goto workspace_prev_end;
                 }
             }
+        }
     }
 
     /* Find last workspace. */
     if (!prev) {
-        TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes)
+        TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
+            /* Skip outputs starting with __, they are internal. */
+            if (output->name[0] == '_' && output->name[1] == '_')
+                continue;
             NODES_FOREACH_REVERSE(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
                     continue;
                 if (!prev || child->num > prev->num)
                     prev = child;
             }
+        }
     }
 
 workspace_prev_end:
     return prev;
 }
 
+
+/*
+ * Focuses the next workspace on the same output.
+ *
+ */
+Con* workspace_next_on_output() {
+    Con *current = con_get_workspace(focused);
+    Con *next = NULL;
+    Con *output  = con_get_output(focused);
+
+    if (current->num == -1) {
+        /* If currently a named workspace, find next named workspace. */
+        next = TAILQ_NEXT(current, nodes);
+    } else {
+        /* If currently a numbered workspace, find next numbered workspace. */
+        NODES_FOREACH(output_get_content(output)) {
+            if (child->type != CT_WORKSPACE)
+                continue;
+            if (child->num == -1)
+                break;
+            /* Need to check child against current and next because we are
+             * traversing multiple lists and thus are not guaranteed the
+             * relative order between the list of workspaces. */
+            if (current->num < child->num && (!next || child->num < next->num))
+                next = child;
+            }
+        }
+
+    /* Find next named workspace. */
+    if (!next) {
+        bool found_current = false;
+        NODES_FOREACH(output_get_content(output)) {
+            if (child->type != CT_WORKSPACE)
+                continue;
+            if (child == current) {
+                found_current = 1;
+            } else if (child->num == -1 && (current->num != -1 || found_current)) {
+                next = child;
+                goto workspace_next_on_output_end;
+            }
+        }
+    }
+
+    /* Find first workspace. */
+    if (!next) {
+        NODES_FOREACH(output_get_content(output)) {
+            if (child->type != CT_WORKSPACE)
+                continue;
+            if (!next || (child->num != -1 && child->num < next->num))
+                next = child;
+        }
+    }
+workspace_next_on_output_end:
+    return next;
+}
+
+/*
+ * Focuses the previous workspace on same output.
+ *
+ */
+Con* workspace_prev_on_output() {
+    Con *current = con_get_workspace(focused);
+    Con *prev = NULL;
+    Con *output  = con_get_output(focused);
+
+    if (current->num == -1) {
+        /* If named workspace, find previous named workspace. */
+        prev = TAILQ_PREV(current, nodes_head, nodes);
+        if (prev && prev->num != -1)
+            prev = NULL;
+    } else {
+        /* If numbered workspace, find previous numbered workspace. */
+        NODES_FOREACH_REVERSE(output_get_content(output)) {
+            if (child->type != CT_WORKSPACE || child->num == -1)
+                continue;
+             /* Need to check child against current and previous because we
+             * are traversing multiple lists and thus are not guaranteed
+             * the relative order between the list of workspaces. */
+            if (current->num > child->num && (!prev || child->num > prev->num))
+                prev = child;
+        }
+    }
+
+    /* Find previous named workspace. */
+    if (!prev) {
+        bool found_current = false;
+        NODES_FOREACH_REVERSE(output_get_content(output)) {
+            if (child->type != CT_WORKSPACE)
+                continue;
+            if (child == current) {
+                found_current = true;
+            } else if (child->num == -1 && (current->num != -1 || found_current)) {
+                prev = child;
+                goto workspace_prev_on_output_end;
+            }
+        }
+    }
+
+    /* Find last workspace. */
+    if (!prev) {
+        NODES_FOREACH_REVERSE(output_get_content(output)) {
+            if (child->type != CT_WORKSPACE)
+                continue;
+            if (!prev || child->num > prev->num)
+                prev = child;
+        }
+    }
+
+workspace_prev_on_output_end:
+    return prev;
+}
+
 /*
  * Focuses the previously focused workspace.
  *
diff --git a/src/x.c b/src/x.c
index 557a49d915d6f6e8e74f602e1a822a496576ebb7..fe64d3ecca6be7f3a4daf3190ca4b347df1a3c5f 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -90,15 +90,28 @@ void x_con_init(Con *con) {
      * get the initial geometry right */
 
     uint32_t mask = 0;
-    uint32_t values[2];
+    uint32_t values[5];
+
+    /* We explicitly set a background color and border color (even though we
+     * don’t even have a border) because the X11 server requires us to when
+     * using 32 bit color depths, see
+     * http://stackoverflow.com/questions/3645632 */
+    mask |= XCB_CW_BACK_PIXEL;
+    values[0] = root_screen->black_pixel;
+
+    mask |= XCB_CW_BORDER_PIXEL;
+    values[1] = root_screen->black_pixel;
 
     /* our own frames should not be managed */
     mask |= XCB_CW_OVERRIDE_REDIRECT;
-    values[0] = 1;
+    values[2] = 1;
 
     /* see include/xcb.h for the FRAME_EVENT_MASK */
     mask |= XCB_CW_EVENT_MASK;
-    values[1] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW;
+    values[3] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW;
+
+    mask |= XCB_CW_COLORMAP;
+    values[4] = colormap;
 
     Rect dims = { -15, -15, 10, 10 };
     con->frame = create_window(conn, dims, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values);
@@ -307,7 +320,6 @@ void x_draw_decoration(Con *con) {
     p->con_deco_rect = con->deco_rect;
     p->background = config.client.background;
     p->con_is_leaf = con_is_leaf(con);
-    p->font = config.font.id;
 
     if (con->deco_render_params != NULL &&
         (con->window == NULL || !con->window->name_x_changed) &&
@@ -383,9 +395,26 @@ void x_draw_decoration(Con *con) {
             xcb_rectangle_t topline = { br.x, 0, con->rect.width + br.width + br.x, br.y };
             xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline);
         }
+
+        /* Highlight the side of the border at which the next window will be
+         * opened if we are rendering a single window within a split container
+         * (which is undistinguishable from a single window outside a split
+         * container otherwise. */
+        if (TAILQ_NEXT(con, nodes) == NULL &&
+            TAILQ_PREV(con, nodes_head, nodes) == NULL &&
+            con->parent->type != CT_FLOATING_CON) {
+            xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->indicator });
+            if (con_orientation(con->parent) == HORIZ)
+                xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]){
+                        { r->width + br.width + br.x, 0, r->width, r->height + br.height } });
+            else
+                xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]){
+                        { br.x, r->height + br.height + br.y, r->width - (2 * br.x), r->height } });
+        }
+
     }
 
-    /* if this is a borderless/1pixel window, we don’t need to render the
+    /* if this is a borderless/1pixel window, we don’t need to render the
      * decoration. */
     if (p->border_style != BS_NORMAL)
         goto copy_pixmaps;
@@ -408,25 +437,17 @@ void x_draw_decoration(Con *con) {
     xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments);
 
     /* 6: draw the title */
-    uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
-    uint32_t values[] = { p->color->text, p->color->background, config.font.id };
-    xcb_change_gc(conn, parent->pm_gc, mask, values);
-    int text_offset_y = config.font.height + (con->deco_rect.height - config.font.height) / 2 - 1;
+    set_font_colors(parent->pm_gc, p->color->text, p->color->background);
+    int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
 
     struct Window *win = con->window;
     if (win == NULL || win->name_x == NULL) {
         /* this is a non-leaf container, we need to make up a good description */
         // TODO: use a good description instead of just "another container"
-        xcb_image_text_8(
-            conn,
-            strlen("another container"),
-            parent->pixmap,
-            parent->pm_gc,
-            con->deco_rect.x + 2,
-            con->deco_rect.y + text_offset_y,
-            "another container"
-        );
-
+        draw_text("another container", strlen("another container"), false,
+                parent->pixmap, parent->pm_gc,
+                con->deco_rect.x + 2, con->deco_rect.y + text_offset_y,
+                con->deco_rect.width - 2);
         goto copy_pixmaps;
     }
 
@@ -447,26 +468,33 @@ void x_draw_decoration(Con *con) {
     //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)
-        xcb_image_text_16(
-            conn,
-            win->name_len,
-            parent->pixmap,
-            parent->pm_gc,
-            con->deco_rect.x + 2 + indent_px,
-            con->deco_rect.y + text_offset_y,
-            (xcb_char2b_t*)win->name_x
-        );
-    else
-        xcb_image_text_8(
-            conn,
-            win->name_len,
-            parent->pixmap,
-            parent->pm_gc,
-            con->deco_rect.x + 2 + indent_px,
-            con->deco_rect.y + text_offset_y,
-            win->name_x
-        );
+    draw_text(win->name_x, win->name_len, win->uses_net_wm_name,
+            parent->pixmap, parent->pm_gc,
+            con->deco_rect.x + 2 + indent_px, con->deco_rect.y + text_offset_y,
+            con->deco_rect.width - 2 - indent_px);
+
+    /* Since we don’t clip the text at all, it might in some cases be painted
+     * on the border pixels on the right side of a window. Therefore, we draw
+     * the right border again after rendering the text (and the unconnected
+     * lines in border color). */
+
+    /* Draw a separator line after every tab (except the last one), so that
+     * tabs can be easily distinguished. */
+    if (parent->layout == L_TABBED && TAILQ_NEXT(con, nodes) != NULL) {
+        xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
+    } else {
+        xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background });
+    }
+    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, parent->pixmap, parent->pm_gc, 4,
+                  (xcb_point_t[]){
+                      { dr->x + dr->width - 1, dr->y },
+                      { dr->x + dr->width - 1, dr->y + dr->height },
+                      { dr->x + dr->width - 2, dr->y },
+                      { dr->x + dr->width - 2, dr->y + dr->height }
+                  });
+
+    xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
+    xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments);
 
 copy_pixmaps:
     xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height);
@@ -961,6 +989,8 @@ void x_set_i3_atoms() {
                         current_socketpath);
     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8,
                         strlen(current_configpath), current_configpath);
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
+                        strlen(shmlogname), shmlogname);
 }
 
 /*
index aa761cac74679873f32f0347d3675cce708de934..5aa74b32b396d51dabc5ab934540bcc8ec2cfcdc 100644 (file)
--- a/src/xcb.c
+++ b/src/xcb.c
@@ -20,8 +20,17 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
         enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values) {
     xcb_window_t result = xcb_generate_id(conn);
 
-    /* If the window class is XCB_WINDOW_CLASS_INPUT_ONLY, depth has to be 0 */
-    uint16_t depth = (window_class == XCB_WINDOW_CLASS_INPUT_ONLY ? 0 : XCB_COPY_FROM_PARENT);
+    /* By default, the color depth determined in src/main.c is used (32 bit if
+     * available, otherwise the X11 root window’s default depth). */
+    uint16_t depth = root_depth;
+    xcb_visualid_t visual = visual_id;
+
+    /* If the window class is XCB_WINDOW_CLASS_INPUT_ONLY, we copy depth and
+     * visual id from the parent window. */
+    if (window_class == XCB_WINDOW_CLASS_INPUT_ONLY) {
+        depth = XCB_COPY_FROM_PARENT;
+        visual = XCB_COPY_FROM_PARENT;
+    }
 
     xcb_create_window(conn,
             depth,
@@ -30,7 +39,7 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
             dims.x, dims.y, dims.width, dims.height, /* dimensions */
             0, /* border = 0, we draw our own */
             window_class,
-            XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+            visual,
             mask,
             values);
 
@@ -130,32 +139,6 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
     xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
 }
 
-/*
- * Query the width of the given text (16-bit characters, UCS) with given real
- * length (amount of glyphs) using the given font.
- *
- */
-int predict_text_width(char *text, int length) {
-    xcb_query_text_extents_cookie_t cookie;
-    xcb_query_text_extents_reply_t *reply;
-    xcb_generic_error_t *error;
-    int width;
-
-    cookie = xcb_query_text_extents(conn, config.font.id, length, (xcb_char2b_t*)text);
-    if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) {
-        ELOG("Could not get text extents (X error code %d)\n",
-             error->error_code);
-        /* We return the rather safe guess of 7 pixels, because a
-         * rendering error is better than a crash. Plus, the user will
-         * see the error in his log. */
-        return 7;
-    }
-
-    width = reply->overall_width;
-    free(reply);
-    return width;
-}
-
 /*
  * Configures the given window to have the size/position specified by given rect
  *
index 11385f763f631aa57e22d9868ce1f0e71230cf93..1c9873894abc3102488692d055b4fd136755af99 100755 (executable)
@@ -4,22 +4,28 @@ use strict; use warnings;
 use ExtUtils::MakeMaker;
 
 WriteMakefile(
-    NAME => 'i3 testsuite',
+    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,
+        'Inline'       => 0,
+        'Test::More'   => '0.94',
     },
-    # don't install any files from this directory
-    PM => {},
+    PM => {}, # do not install any files from this directory
     clean => {
-        FILES => 'testsuite-* latest'
+        FILES => 'testsuite-* latest i3-cfg-for-*',
     }
 );
-# and don't run the tests while installing
-sub MY::test { }
+
+package MY;
+sub test { } # do not run the tests while installing
+
+# do not rename the Makefile
+sub clean {
+    my $section = shift->SUPER::clean(@_);
+    $section =~ s/^\t\Q$_\E\n$//m for
+        '- $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) $(DEV_NULL)';
+    $section;
+}
index c43fbf07b49cc4553a879ef79eee768ee40aec68..15def35c85516332c5acc3f771969810c573da48 100755 (executable)
@@ -1,67 +1,59 @@
 #!/usr/bin/env perl
 # vim:ts=4:sw=4:expandtab
 # © 2010-2011 Michael Stapelberg and contributors
-
+package complete_run;
 use strict;
 use warnings;
 use v5.10;
 # the following are modules which ship with Perl (>= 5.10):
 use Pod::Usage;
 use Cwd qw(abs_path);
-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 POSIX ();
 use TAP::Harness;
 use TAP::Parser;
 use TAP::Parser::Aggregator;
+use Time::HiRes qw(time);
 # these are shipped with the testsuite
 use lib qw(lib);
-use SocketActivation;
 use StartXDummy;
 use StatusLine;
+use TestWorker;
 # the following modules are not shipped with Perl
 use AnyEvent;
+use AnyEvent::Util;
 use AnyEvent::Handle;
 use AnyEvent::I3 qw(:all);
-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.
-# 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);
-    local $/;
-    <$fh>;
-}
+use X11::XCB::Connection;
+use JSON::XS; # AnyEvent::I3 depends on it, too.
+
+# Close superfluous file descriptors which were passed by running in a VIM
+# subshell or situations like that.
+AnyEvent::Util::close_all_fds_except(0, 1, 2);
 
 # convinience wrapper to write to the log file
 my $log;
 sub Log { say $log "@_" }
 
-my $coverage_testing = 0;
-my $valgrind = 0;
+my %timings;
 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 %options = (
+    valgrind => 0,
+    strace => 0,
+    coverage => 0,
+    restart => 0,
+);
 
 my $result = GetOptions(
-    "coverage-testing" => \$coverage_testing,
-    "valgrind" => \$valgrind,
+    "coverage-testing" => \$options{coverage},
+    "valgrind" => \$options{valgrind},
+    "strace" => \$options{strace},
     "display=s" => \@displays,
     "parallel=i" => \$parallel,
     "help|?" => \$help,
@@ -72,49 +64,81 @@ pod2usage(-verbose => 2, -exitcode => 0) if $help;
 @displays = split(/,/, join(',', @displays));
 @displays = map { s/ //g; $_ } @displays;
 
+# 2: get a list of all testcases
+my @testfiles = @ARGV;
+
+# if no files were passed on command line, run all tests from t/
+@testfiles = <t/*.t> if @testfiles == 0;
+
+my $numtests = scalar @testfiles;
+
+# When the user specifies displays, we don’t run multi-monitor tests at all
+# (because we don’t know which displaynumber is the X-Server with multiple
+# monitors).
+my $multidpy = undef;
+
 # No displays specified, let’s start some Xdummy instances.
 if (@displays == 0) {
-    my ($displays, $pids) = start_xdummy($parallel);
-    @displays = @$displays;
-    @childpids = @$pids;
+    my $dpyref;
+    ($dpyref, $multidpy) = start_xdummy($parallel, $numtests);
+    @displays = @$dpyref;
 }
 
+# 1: create an output directory for this test-run
+my $outdir = "testsuite-";
+$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";
+
+
 # connect to all displays for two reasons:
 # 1: check if the display actually works
 # 2: keep the connection open so that i3 is not the only client. this prevents
 #    the X server from exiting (Xdummy will restart it, but not quick enough
 #    sometimes)
-my @conns;
-my @wdisplays;
+my @single_worker;
 for my $display (@displays) {
     my $screen;
-    my $x = X11::XCB->new($display, $screen);
+    my $x = X11::XCB::Connection->new(display => $display);
     if ($x->has_error) {
-        Log "WARNING: Not using X11 display $display, could not connect";
+        die "Could not connect to display $display\n";
     } else {
-        push @conns, $x;
-        push @wdisplays, $display;
+        # start a TestWorker for each display
+        push @single_worker, worker($display, $x, $outdir, \%options);
     }
 }
 
-die "No usable displays found" if @wdisplays == 0;
+my @multi_worker;
+if (defined($multidpy)) {
+    my $x = X11::XCB::Connection->new(display => $multidpy);
+    if ($x->has_error) {
+        die "Could not connect to multi-monitor display $multidpy\n";
+    } else {
+        push @multi_worker, worker($multidpy, $x, $outdir, \%options);
+    }
+}
 
-my $config = slurp('i3-test.config');
+# Read previous timing information, if available. We will be able to roughly
+# predict the test duration and schedule a good order for the tests.
+my $timingsjson = StartXDummy::slurp('.last_run_timings.json');
+%timings = %{decode_json($timingsjson)} if length($timingsjson) > 0;
 
-# 1: get a list of all testcases
-my @testfiles = @ARGV;
+# Re-order the files so that those which took the longest time in the previous
+# run will be started at the beginning to not delay the whole run longer than
+# necessary.
+@testfiles = map  { $_->[0] }
+             sort { $b->[1] <=> $a->[1] }
+             map  { [$_, $timings{$_} // 999] } @testfiles;
 
-# if no files were passed on command line, run all tests from t/
-@testfiles = <t/*.t> if @testfiles == 0;
+printf("\nRough time estimate for this run: %.2f seconds\n\n", $timings{GLOBAL})
+    if exists($timings{GLOBAL});
 
-# 2: create an output directory for this test-run
-my $outdir = "testsuite-";
-$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";
+# Forget the old timings, we don’t necessarily run the same set of tests as
+# before. Otherwise we would end up with left-overs.
+%timings = (GLOBAL => time());
 
 my $logfile = "$outdir/complete-run.log";
 open $log, '>', $logfile or die "Could not create '$logfile': $!";
@@ -125,14 +149,77 @@ my @done;
 my $num = @testfiles;
 my $harness = TAP::Harness->new({ });
 
+my @single_monitor_tests = grep { m,^t/([0-9]+)-, && $1 < 500 } @testfiles;
+my @multi_monitor_tests = grep { m,^t/([0-9]+)-, && $1 >= 500 } @testfiles;
+
 my $aggregator = TAP::Parser::Aggregator->new();
 $aggregator->start();
 
-status_init(displays => \@wdisplays, tests => $num);
+status_init(displays => [ @displays, $multidpy ], tests => $num);
+
+my $single_cv = AE::cv;
+my $multi_cv = AE::cv;
 
 # We start tests concurrently: For each display, one test gets started. Every
 # test starts another test after completing.
-take_job($_) for @wdisplays;
+for (@single_worker) {
+    $single_cv->begin;
+    take_job($_, $single_cv, \@single_monitor_tests);
+}
+for (@multi_worker) {
+    $multi_cv->begin;
+    take_job($_, $multi_cv, \@multi_monitor_tests);
+}
+
+$single_cv->recv;
+$multi_cv->recv;
+
+$aggregator->stop();
+
+# print empty lines to seperate failed tests from statuslines
+print "\n\n";
+
+for (@done) {
+    my ($test, $output) = @$_;
+    say "no output for $test" unless $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;
+
+# 5: Save the timings for better scheduling/prediction next run.
+$timings{GLOBAL} = time() - $timings{GLOBAL};
+open(my $fh, '>', '.last_run_timings.json');
+print $fh encode_json(\%timings);
+close($fh);
+
+# 6: Print the slowest test files.
+my @slowest = map  { $_->[0] }
+              sort { $b->[1] <=> $a->[1] }
+              map  { [$_, $timings{$_}] }
+              grep { !/^GLOBAL$/ } keys %timings;
+say '';
+say 'The slowest tests are:';
+printf("\t%s with %.2f seconds\n", $_, $timings{$_})
+    for @slowest[0..($#slowest > 4 ? 4 : $#slowest)];
+
+# When we are running precisely one test, print the output. Makes developing
+# with a single testcase easier.
+if ($numtests == 1) {
+    say '';
+    say 'Test output:';
+    say StartXDummy::slurp($logfile);
+}
+
+END { cleanup() }
+
+exit 0;
 
 #
 # Takes a test from the beginning of @testfiles and runs it.
@@ -145,174 +232,94 @@ take_job($_) for @wdisplays;
 # triggered to finish testing.
 #
 sub take_job {
-    my ($display) = @_;
+    my ($worker, $cv, $tests) = @_;
 
-    my $test = shift @testfiles;
-    return unless $test;
+    my $test = shift @$tests
+        or return $cv->end;
 
-    my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/);
-    my $basename = basename($test);
-    my $logpath = "$outdir/i3-log-for-$basename";
+    my $display = $worker->{display};
 
-    my ($fh, $tmpfile) = tempfile("i3-cfg-for-$basename.XXXXXX", UNLINK => 1);
-    say $fh $config;
-    say $fh "ipc-socket /tmp/nested-$display";
-    close($fh);
+    Log status($display, "$test: starting");
+    $timings{$test} = time();
+    worker_next($worker, $test);
 
-    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;
-        });
-    }
-
-    my $kill_i3 = sub {
-        my $kill_cv = AnyEvent->condvar;
-
-        # Don’t bother killing i3 when we haven’t started it
-        if ($dont_start) {
-            $kill_cv->send();
-            return $kill_cv;
-        }
+    # create a TAP::Parser with an in-memory fh
+    my $output;
+    my $parser = TAP::Parser->new({
+        source => do { open(my $fh, '<', \$output); $fh },
+    });
 
-        # When measuring code coverage, try to exit i3 cleanly (otherwise, .gcda
-        # files are not written) and fallback to killing it
-        if ($coverage_testing || $valgrind) {
-            my $exited = 0;
-            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();
-                    });
+    my $ipc = $worker->{ipc};
+
+    my $w;
+    $w = AnyEvent->io(
+        fh => $ipc,
+        poll => 'r',
+        cb => sub {
+            state $tests_completed = 0;
+            state $partial = '';
+
+            sysread($ipc, my $buf, 4096) or die "sysread: $!";
+
+            if ($partial) {
+                $buf = $partial . $buf;
+                $partial = '';
+            }
+
+            # make sure we feed TAP::Parser complete lines so it doesn't blow up
+            if (substr($buf, -1, 1) ne "\n") {
+                my $nl = rindex($buf, "\n");
+                if ($nl == -1) {
+                    $partial = $buf;
+                    return;
                 }
-            });
-        } 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();
-        }
+                # strip partial from buffer
+                $partial = substr($buf, $nl + 1, '');
+            }
 
-        return $kill_cv;
-    };
-
-    # 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");
-        }
+            # count lines before stripping eof-marker otherwise we might
+            # end up with for (1 .. 0) { } which would effectivly skip the loop
+            my $lines = $buf =~ tr/\n//;
+            my $t_eof = $buf =~ s/^$TestWorker::EOF$//m;
+
+            $output .= $buf;
 
-        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);
-                        }
-                    });
+            for (1 .. $lines) {
+                my $result = $parser->next;
+                if (defined($result) and $result->is_test) {
+                    $tests_completed++;
+                    status($display, "$test: [$tests_completed/??] ");
                 }
-            );
-            push @watchers, $w;
-        }
-    });
-}
+            }
 
-$cv->recv;
+            return unless $t_eof;
 
-$aggregator->stop();
+            Log status($display, "$test: finished");
+            $timings{$test} = time() - $timings{$test};
+            status_completed(scalar @done);
 
-# print empty lines to seperate failed tests from statuslines
-print "\n\n";
+            $aggregator->add($test, $parser);
+            push @done, [ $test, $output ];
 
-for (@done) {
-    my ($test, $output) = @$_;
-    Log "output for $test:";
-    Log $output;
-    # print error messages of failed tests
-    say for $output =~ /^not ok.+\n+((?:^#.+\n)+)/mg
+            undef $w;
+            take_job($worker, $cv, $tests);
+        }
+    );
 }
 
-# 4: print summary
-$harness->summary($aggregator);
-
-close $log;
+sub cleanup {
+    $_->() for our @CLEANUP;
+    exit;
+}
 
-kill(15, $_) for @childpids;
+# must be in a begin block because we C<exit 0> above
+BEGIN {
+    $SIG{$_} = sub {
+        require Carp; Carp::cluck("Caught SIG$_[0]\n");
+        cleanup();
+    } for qw(INT TERM QUIT KILL PIPE)
+}
 
 __END__
 
@@ -354,7 +361,12 @@ 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>.
+C<latest/valgrind-for-$test.log>.
+
+=item B<--strace>
+
+Runs i3 under strace to trace system calls. The output will be available in
+C<latest/strace-for-$test.log>.
 
 =item B<--coverage-testing>
 
index e0b08d746fc3bde846b502c159f8e06b94cdd095..8f52bddc4a3eb8a92a3ba37ae8363a29cfd9b779 100644 (file)
@@ -5,8 +5,9 @@ use strict;
 use warnings;
 use IO::Socket::UNIX; # core
 use Cwd qw(abs_path); # core
-use POSIX (); # core
+use POSIX qw(:fcntl_h); # core
 use AnyEvent::Handle; # not core
+use AnyEvent::Util; # not core
 use Exporter 'import';
 use v5.10;
 
@@ -38,11 +39,6 @@ sub activate_i3 {
     # 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},
@@ -56,6 +52,10 @@ sub activate_i3 {
         $ENV{LISTEN_PID} = $$;
         $ENV{LISTEN_FDS} = 1;
         delete $ENV{DESKTOP_STARTUP_ID};
+        unless ($args{dont_create_temp_dir}) {
+            $ENV{XDG_RUNTIME_DIR} = '/tmp/i3-testsuite/';
+            mkdir $ENV{XDG_RUNTIME_DIR};
+        }
         $ENV{DISPLAY} = $args{display};
         $ENV{PATH} = join(':',
             '../i3-nagbar',
@@ -65,31 +65,64 @@ sub activate_i3 {
             '..',
             $ENV{PATH}
         );
-        # Only pass file descriptors 0 (stdin), 1 (stdout), 2 (stderr) and
-        # 3 (socket) to the child.
-        $^F = 3;
+
+        # We are about to exec, but we did not modify $^F to include $socket
+        # when creating the socket (because the file descriptor could have a
+        # number != 3 which would lead to i3 leaking a file descriptor). This
+        # caused Perl to set the FD_CLOEXEC flag, which would close $socket on
+        # exec(), effectively *NOT* passing $socket to the new process.
+        # Therefore, we explicitly clear FD_CLOEXEC (the only flag right now)
+        # by setting the flags to 0.
+        POSIX::fcntl($socket, F_SETFD, 0) or die "Could not clear fd flags: $!";
 
         # 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);
+            POSIX::close(fileno($socket));
         }
 
+        # Make sure no file descriptors are open. Strangely, I got an open file
+        # descriptor pointing to AnyEvent/Impl/EV.pm when testing.
+        AnyEvent::Util::close_all_fds_except(0, 1, 2, 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";
+        # Also disable logging to SHM since we redirect the logs anyways.
+        # Force Xinerama because we use Xdmx for multi-monitor tests.
+        my $i3cmd = abs_path("../i3") . q| -V -d all --disable-signalhandler| .
+                                        q| --shmlog-size=0 --force-xinerama|;
+
+        # For convenience:
+        my $outdir = $args{outdir};
+        my $test = $args{testname};
+
+        if ($args{restart}) {
+            $i3cmd .= ' -L ' . abs_path('restart-state.golden');
+        }
 
         if ($args{valgrind}) {
             $i3cmd =
-                qq|valgrind -v --log-file="$args{outdir}/valgrind.log" | .
+                qq|valgrind -v --log-file="$outdir/valgrind-for-$test.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
+        my $logfile = "$outdir/i3-log-for-$test";
+        # Append to $logfile instead of overwriting because i3 might be
         # run multiple times in one testcase.
-        my $cmd = "exec $i3cmd -c $args{configfile} >>$args{logpath} 2>&1";
+        my $cmd = "exec $i3cmd -c $args{configfile} >>$logfile 2>&1";
+
+        if ($args{strace}) {
+            my $out = "$outdir/strace-for-$test.log";
+
+            # We overwrite LISTEN_PID with the correct process ID to make
+            # socket activation work (LISTEN_PID has to match getpid(),
+            # otherwise the LISTEN_FDS will be treated as a left-over).
+            $cmd = qq|strace -fF -s2048 -v -o "$out" -- | .
+                     'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"';
+        }
 
         # We need to use the shell due to using output redirections.
         exec '/bin/sh', '-c', $cmd;
index 9dfdedd29b7bcff425e9aec546b172d40d7c366f..3df782004b2fb4ff58fdf1dac41376aa41c442d8 100644 (file)
@@ -9,6 +9,8 @@ use v5.10;
 
 our @EXPORT = qw(start_xdummy);
 
+my $x_socketpath = '/tmp/.X11-unix/X';
+
 # reads in a whole file
 sub slurp {
     open(my $fh, '<', shift) or return '';
@@ -16,6 +18,42 @@ sub slurp {
     <$fh>;
 }
 
+# forks an Xdummy or Xdmx process
+sub fork_xserver {
+    my $displaynum = shift;
+    my $pid = fork();
+    die "Could not fork: $!" unless defined($pid);
+    if ($pid == 0) {
+        # Child, close stdout/stderr, then start Xdummy.
+        close STDOUT;
+        close STDERR;
+
+        exec @_;
+        exit 1;
+    }
+    push(@complete_run::CLEANUP, sub {
+        kill(15, $pid);
+        # Unlink the X11 socket, Xdmx seems to leave it there.
+        unlink($x_socketpath . $displaynum);
+    });
+
+    return $x_socketpath . $displaynum;
+}
+
+# Blocks until the socket paths specified in the given array reference actually
+# exist.
+sub wait_for_x {
+    my ($sockets_waiting) = @_;
+
+    # Wait until Xdmx actually runs. Pretty ugly solution, but as long as we
+    # can’t socket-activate X11…
+    while (1) {
+        @$sockets_waiting = grep { ! -S $_ } @$sockets_waiting;
+        last unless @$sockets_waiting;
+        sleep 0.1;
+    }
+}
+
 =head2 start_xdummy($parallel)
 
 Starts C<$parallel> (or number of cores * 2 if undef) Xdummy processes (see
@@ -23,8 +61,9 @@ 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 ($parallel, $numtests) = @_;
 
     my @displays = ();
     my @childpids = ();
@@ -36,47 +75,52 @@ sub start_xdummy {
     # If /proc/cpuinfo does not exist, we fall back to 2 cores.
     $num_cores ||= 2;
 
-    $parallel ||= $num_cores * 2;
+    # If unset, we use num_cores * 2, plus two extra xdummys to combine to a
+    # multi-monitor setup using Xdmx.
+    $parallel ||= ($num_cores * 2) + 2;
+
+    # If we are running a small number of tests, don’t over-parallelize.
+    $parallel = $numtests if $numtests < $parallel;
+
+    # Ensure we have at least 1 X-Server plus two X-Servers for multi-monitor
+    # tests.
+    $parallel = 3 if $parallel < 3;
 
     # 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/;
+    my ($displaynum) = map { /(\d+)$/ } reverse sort glob($x_socketpath . '*');
     $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);
+    my @sockets_waiting;
+    for (1 .. $parallel) {
+        # 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.
+        my $socket = fork_xserver($displaynum, './Xdummy', ":$displaynum",
+                '-config', '/dev/null');
         push(@displays, ":$displaynum");
+        push(@sockets_waiting, $socket);
         $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;
+    wait_for_x(\@sockets_waiting);
+
+    # Now combine the last two displays to a multi-monitor display using Xdmx
+    my $first = pop @displays;
+    my $second = pop @displays;
+
+    # make sure this display isn’t in use yet
+    $displaynum++ while -e ($x_socketpath . $displaynum);
+    say 'starting xdmx on display :' . $displaynum;
+
+    my $multidpy = ":$displaynum";
+    my $socket = fork_xserver($displaynum, 'Xdmx', '+xinerama', '-xinput',
+            'local', '-display', $first, '-display', $second, '-ac', $multidpy);
+    wait_for_x([ $socket ]);
 
-    return \@displays, \@childpids;
+    return \@displays, $multidpy;
 }
 
 1
diff --git a/testcases/lib/TestWorker.pm b/testcases/lib/TestWorker.pm
new file mode 100644 (file)
index 0000000..66f22bc
--- /dev/null
@@ -0,0 +1,138 @@
+# vim:ts=4:sw=4:sts=4:expandtab
+package TestWorker;
+use strict; use warnings;
+use v5.10;
+
+use Socket qw(AF_UNIX SOCK_DGRAM PF_UNSPEC);
+use IO::Handle; # for ->autoflush
+
+use POSIX ();
+
+use Exporter 'import';
+our @EXPORT = qw(worker worker_next);
+
+use File::Basename qw(basename);
+my @x;
+my $options;
+
+sub worker {
+    my ($display, $x, $outdir, $optref) = @_;
+
+    # make sure $x hangs around
+    push @x, $x;
+
+    # store the options hashref
+    $options = $optref;
+
+    socketpair(my $ipc_child, my $ipc, AF_UNIX, SOCK_DGRAM, PF_UNSPEC)
+        or die "socketpair: $!";
+
+    $ipc->autoflush(1);
+    $ipc_child->autoflush(1);
+
+    my $worker = {
+        display => $display,
+        ipc => $ipc,
+    };
+
+    my $pid = fork // die "could not fork: $!";
+
+    if ($pid == 0) {
+        close $ipc;
+        undef @complete_run::CLEANUP;
+        # reap dead test children
+        $SIG{CHLD} = sub { waitpid -1, POSIX::WNOHANG };
+
+        $worker->{ipc} = $ipc_child;
+
+        require i3test;
+        # TODO: recycle $x
+        # unfortunately this fails currently with:
+        # Could not get reply for: xcb_intern_atom_reply at X11/XCB/Atom.pm line 22.
+
+        # $i3test::x = bless $x, 'i3test::X11';
+        worker_wait($worker, $outdir);
+        exit 23;
+
+    }
+
+    close $ipc_child;
+    push @complete_run::CLEANUP, sub {
+        # signal via empty line to exit itself
+        syswrite($ipc, "\n") or kill('TERM', $pid);
+        waitpid $pid, 0;
+    };
+
+    return $worker;
+
+}
+
+our $EOF = "# end of file\n";
+sub worker_wait {
+    my ($self, $outdir) = @_;
+
+    my $ipc = $self->{ipc};
+    my $ipc_fd = fileno($ipc);
+
+    while (defined(my $file = $ipc->getline)) {
+        chomp $file;
+
+        exit unless $file;
+
+        die "tried to launch nonexistend testfile $file: $!\n"
+            unless -e $file;
+
+        # start a new and self contained process:
+        # whatever happens in the testfile should *NOT* effect us.
+
+        my $pid = fork // die "could not fork: $!";
+        if ($pid == 0) {
+            undef @complete_run::CLEANUP;
+            local $SIG{CHLD};
+
+            $0 = $file;
+
+            POSIX::dup2($ipc_fd, 0);
+            POSIX::dup2($ipc_fd, 1);
+            POSIX::dup2(1, 2);
+
+            # get Test::Builder singleton
+            my $test = Test::Builder->new;
+
+            # Test::Builder dups stdout/stderr while loading.
+            # we need to reset them here to point to $ipc
+            $test->output(\*STDOUT);
+            $test->failure_output(\*STDERR);
+            $test->todo_output(\*STDOUT);
+
+            @ENV{qw(DISPLAY TESTNAME OUTDIR VALGRIND STRACE COVERAGE RESTART)}
+                = ($self->{display},
+                   basename($file),
+                   $outdir,
+                   $options->{valgrind},
+                   $options->{strace},
+                   $options->{coverage},
+                   $options->{restart});
+
+            package main;
+            local $@;
+            do "./$file";
+            $test->ok(undef, "$@") if $@;
+
+            # XXX hack, we need to trigger the read watcher once more
+            # to signal eof to TAP::Parser
+            print $EOF;
+
+            exit 0;
+        }
+    }
+}
+
+sub worker_next {
+    my ($self, $file) = @_;
+
+    my $ipc = $self->{ipc};
+    syswrite $ipc, "$file\n" or die "syswrite: $!";
+}
+
+__PACKAGE__ __END__
index 7473b61757ae9c86037f2ecb3df70cf8307bc977..f7be1b665d8a9f3eb8697e1d27a7bbf155ce97a2 100644 (file)
@@ -8,14 +8,18 @@ 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 Scalar::Util qw(blessed);
 use SocketActivation;
 
 use v5.10;
 
+# preload
+use Test::More ();
+use Data::Dumper ();
+
 use Exporter ();
 our @EXPORT = qw(
     get_workspace_names
@@ -39,6 +43,7 @@ our @EXPORT = qw(
     wait_for_event
     wait_for_map
     wait_for_unmap
+    $x
 );
 
 my $tester = Test::Builder->new();
@@ -46,6 +51,8 @@ my $_cached_socket_path = undef;
 my $_sync_window = undef;
 my $tmp_socket_path = undef;
 
+our $x;
+
 BEGIN {
     my $window_count = 0;
     sub counter_window {
@@ -53,19 +60,70 @@ BEGIN {
     }
 }
 
+my $i3_pid;
+my $i3_autostart;
+
+END {
+
+    # testcases which start i3 manually should always call exit_gracefully
+    # on their own. Let’s see, whether they really did.
+    if (! $i3_autostart) {
+        return unless $i3_pid;
+
+        $tester->ok(undef, 'testcase called exit_gracefully()');
+    }
+
+    # don't trigger SIGCHLD handler
+    local $SIG{CHLD};
+
+    # From perldoc -v '$?':
+    # Inside an "END" subroutine $? contains the value
+    # that is going to be given to "exit()".
+    #
+    # Since waitpid sets $?, we need to localize it,
+    # otherwise TAP would be misinterpreted our return status
+    local $?;
+
+    # When measuring code coverage, try to exit i3 cleanly (otherwise, .gcda
+    # files are not written)
+    if ($ENV{COVERAGE} || $ENV{VALGRIND}) {
+        exit_gracefully($i3_pid, "/tmp/nested-$ENV{DISPLAY}");
+
+    } else {
+        kill(9, $i3_pid)
+            or $tester->BAIL_OUT("could not kill i3");
+
+        waitpid $i3_pid, 0;
+    }
+}
+
 sub import {
-    my $class = shift;
+    my ($class, %args) = @_;
     my $pkg = caller;
-    eval "package $pkg;
-use Test::Most" . (@_ > 0 ? " qw(@_)" : "") . ";
+
+    $i3_autostart = delete($args{i3_autostart}) // 1;
+
+    my $cv = launch_with_config('-default', dont_block => 1)
+        if $i3_autostart;
+
+    my $test_more_args = '';
+    $test_more_args = join(' ', 'qw(', %args, ')') if keys %args;
+    local $@;
+    eval << "__";
+package $pkg;
+use Test::More $test_more_args;
 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;
-";
+__
+    $tester->BAIL_OUT("$@") if $@;
+    feature->import(":5.10");
+    strict->import;
+    warnings->import;
+
+    $x ||= i3test::X11->new;
+    $cv->recv if $i3_autostart;
+
     @_ = ($class);
     goto \&Exporter::import;
 }
@@ -80,15 +138,16 @@ use warnings;
 # wait_for_event $x, 0.25, sub { $_[0]->{response_type} == MAP_NOTIFY };
 #
 sub wait_for_event {
-    my ($x, $timeout, $cb) = @_;
+    my ($timeout, $cb) = @_;
 
     my $cv = AE::cv;
 
-    my $prep = EV::prepare sub {
-        $x->flush;
-    };
+    $x->flush;
+
+    # unfortunately, there is no constant for this
+    my $ae_read = 0;
 
-    my $check = EV::check sub {
+    my $guard = AE::io $x->get_file_descriptor, $ae_read, sub {
         while (defined(my $event = $x->poll_for_event)) {
             if ($cb->($event)) {
                 $cv->send(1);
@@ -97,32 +156,35 @@ sub wait_for_event {
         }
     };
 
-    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;
+    undef $guard;
     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 };
+    my ($win) = @_;
+    my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win;
+    wait_for_event 2, sub {
+        $_[0]->{response_type} == MAP_NOTIFY and $_[0]->{window} == $id
+    };
 }
 
 # 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);
+    my ($win) = @_;
+    # my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win;
+    wait_for_event 2, sub {
+        $_[0]->{response_type} == UNMAP_NOTIFY # and $_[0]->{window} == $id
+    };
+    sync_with_i3();
 }
 
 #
@@ -131,6 +193,12 @@ sub wait_for_unmap {
 #
 # set dont_map to a true value to avoid mapping
 #
+# if you want to change aspects of your window before it would be mapped,
+# set before_map to a coderef. $window gets passed as $_ and as first argument.
+#
+# if you set both dont_map and before_map, the coderef will be called nevertheless
+#
+#
 # default values:
 #     class => WINDOW_CLASS_INPUT_OUTPUT
 #     rect => [ 0, 0, 30, 30 ]
@@ -139,10 +207,10 @@ sub wait_for_unmap {
 #     name => 'Window <n>'
 #
 sub open_window {
-    my ($x, $args) = @_;
-    my %args = ($args ? %$args : ());
+    my %args = @_ == 1 ? %{$_[0]} : @_;
 
     my $dont_map = delete $args{dont_map};
+    my $before_map = delete $args{before_map};
 
     $args{class} //= WINDOW_CLASS_INPUT_OUTPUT;
     $args{rect} //= [ 0, 0, 30, 30 ];
@@ -152,31 +220,34 @@ sub open_window {
 
     my $window = $x->root->create_child(%args);
 
+    if ($before_map) {
+        # TODO: investigate why _create is not needed
+        $window->_create;
+        $before_map->($window) for $window;
+    }
+
     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);
+    wait_for_map($window);
     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 : ());
+    my %args = @_ == 1 ? %{$_[0]} : @_;
 
     $args{window_type} = $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY');
 
-    return open_window($x, \%args);
+    return open_window(\%args);
 }
 
 sub open_empty_con {
     my ($i3) = @_;
 
     my $reply = $i3->command('open')->recv;
-    return $reply->{id};
+    return $reply->[0]->{id};
 }
 
 sub get_workspace_names {
@@ -185,6 +256,7 @@ sub get_workspace_names {
     my @outputs = @{$tree->{nodes}};
     my @cons;
     for my $output (@outputs) {
+        next if $output->{name} eq '__i3';
         # get the first CT_CON of each output
         my $content = first { $_->{type} == 2 } @{$output->{nodes}};
         @cons = (@cons, @{$content->{nodes}});
@@ -199,7 +271,34 @@ sub get_unused_workspace {
     $tmp
 }
 
+=head2 fresh_workspace(...)
+
+Switches to an unused workspace and returns the name of that workspace.
+
+Optionally switches to the specified output first.
+
+    my $ws = fresh_workspace;
+
+    # Get a fresh workspace on the second output.
+    my $ws = fresh_workspace(output => 1);
+
+=cut
 sub fresh_workspace {
+    my %args = @_;
+    if (exists($args{output})) {
+        my $i3 = i3(get_socket_path());
+        my $tree = $i3->get_tree->recv;
+        my $output = first { $_->{name} eq "xinerama-$args{output}" }
+                        @{$tree->{nodes}};
+        die "BUG: Could not find output $args{output}" unless defined($output);
+        # Get the focused workspace on that output and switch to it.
+        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+        my $focused = $content->{focus}->[0];
+        my $workspace = first { $_->{id} == $focused } @{$content->{nodes}};
+        $workspace = $workspace->{name};
+        cmd("workspace $workspace");
+    }
+
     my $unused = get_unused_workspace;
     cmd("workspace $unused");
     $unused
@@ -265,11 +364,11 @@ sub get_dock_clients {
                                 @{$output->{nodes}});
         } elsif ($which eq 'top') {
             my $first = first { $_->{type} == 5 } @{$output->{nodes}};
-            @docked = (@docked, @{$first->{nodes}});
+            @docked = (@docked, @{$first->{nodes}}) if defined($first);
         } elsif ($which eq 'bottom') {
             my @matching = grep { $_->{type} == 5 } @{$output->{nodes}};
             my $last = $matching[-1];
-            @docked = (@docked, @{$last->{nodes}});
+            @docked = (@docked, @{$last->{nodes}}) if defined($last);
         }
     }
     return @docked;
@@ -284,17 +383,19 @@ sub workspace_exists {
     ($name ~~ @{get_workspace_names()})
 }
 
+=head2 focused_ws
+
+Returns the name of the currently focused workspace.
+
+=cut
 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}
-    }
+    my $focused = $tree->{focus}->[0];
+    my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
+    my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+    my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
+    return $first->{name}
 }
 
 #
@@ -308,25 +409,22 @@ sub focused_ws {
 # See also docs/testsuite for a long explanation
 #
 sub sync_with_i3 {
-    my ($x) = @_;
+    my %args = @_ == 1 ? %{$_[0]} : @_;
 
     # 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 ),
+    if (!exists($args{window_id}) &&
+        (!defined($_sync_window) || exists($args{no_cache}))) {
+        $_sync_window = open_window(
+            rect => [ -15, -15, 10, 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 $window_id = delete $args{window_id};
+    $window_id //= $_sync_window->id;
+
     my $root = $x->get_root_window();
     # Generate a random number to identify this particular ClientMessage.
     my $myrnd = int(rand(255)) + 1;
@@ -339,7 +437,7 @@ sub sync_with_i3 {
          $root,  # destination window
          $x->atom(name => 'I3_SYNC')->id,
 
-         $_sync_window->id,    # data[0]: our own window id
+         $window_id,    # data[0]: our own window id
          $myrnd, # data[1]: a random value to identify the request
          0,
          0,
@@ -350,7 +448,7 @@ sub sync_with_i3 {
     $x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
 
     # now wait until the reply is here
-    return wait_for_event $x, 2, sub {
+    return wait_for_event 2, sub {
         my ($event) = @_;
         # TODO: const
         return 0 unless $event->{response_type} == 161;
@@ -381,12 +479,16 @@ sub exit_gracefully {
     };
 
     if (!$exited) {
-        kill(9, $pid) or die "could not kill i3";
+        kill(9, $pid)
+            or $tester->BAIL_OUT("could not kill i3");
     }
 
     if ($socketpath =~ m,^/tmp/i3-test-socket-,) {
         unlink($socketpath);
     }
+
+    waitpid $pid, 0;
+    undef $i3_pid;
 }
 
 # Gets the socket path from the I3_SOCKET_PATH atom stored on the X11 root window
@@ -398,11 +500,13 @@ sub get_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};
+    if ($socketpath eq "/tmp/nested-$ENV{DISPLAY}") {
+        $socketpath .= '-activation';
+    }
     $_cached_socket_path = $socketpath;
     return $socketpath;
 }
@@ -410,42 +514,63 @@ sub get_socket_path {
 #
 # 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) = @_;
+    my ($config, %args) = @_;
+
+    $tmp_socket_path = "/tmp/nested-$ENV{DISPLAY}";
+
+    $args{dont_create_temp_dir} //= 0;
 
-    $dont_add_socket_path //= 0;
+    my ($fh, $tmpfile) = tempfile("i3-cfg-for-$ENV{TESTNAME}-XXXXX", UNLINK => 1);
 
-    if (!defined($tmp_socket_path)) {
-        $tmp_socket_path = File::Temp::tempnam('/tmp', 'i3-test-socket-');
+    if ($config ne '-default') {
+        say $fh $config;
+    } else {
+        open(my $conf_fh, '<', './i3-test.config')
+            or $tester->BAIL_OUT("could not open default config: $!");
+        local $/;
+        say $fh scalar <$conf_fh>;
     }
 
-    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;
+    say $fh "ipc-socket $tmp_socket_path"
+        unless $args{dont_add_socket_path};
+
     close($fh);
 
     my $cv = AnyEvent->condvar;
-    my $pid = activate_i3(
+    $i3_pid = activate_i3(
         unix_socket_path => "$tmp_socket_path-activation",
         display => $ENV{DISPLAY},
         configfile => $tmpfile,
         outdir => $ENV{OUTDIR},
-        logpath => $ENV{LOGPATH},
+        testname => $ENV{TESTNAME},
         valgrind => $ENV{VALGRIND},
+        strace => $ENV{STRACE},
+        restart => $ENV{RESTART},
         cv => $cv,
+        dont_create_temp_dir => $args{dont_create_temp_dir},
     );
 
+    # force update of the cached socket path in lib/i3test
+    # as soon as i3 has started
+    $cv->cb(sub { get_socket_path(0) });
+
+    return $cv if $args{dont_block};
+
     # blockingly wait until i3 is ready
     $cv->recv;
 
-    # force update of the cached socket path in lib/i3test
-    get_socket_path(0);
+    return $i3_pid;
+}
+
+package i3test::X11;
+use parent 'X11::XCB::Connection';
+
+sub input_focus {
+    my $self = shift;
+    i3test::sync_with_i3();
 
-    return $pid;
+    return $self->SUPER::input_focus(@_);
 }
 
 1
diff --git a/testcases/restart-state.golden b/testcases/restart-state.golden
new file mode 100644 (file)
index 0000000..bca4495
--- /dev/null
@@ -0,0 +1 @@
+{"id":6752976,"type":0,"orientation":"none","percent":null,"urgent":false,"focused":false,"layout":"default","border":"none","rect":{"x":0,"y":0,"width":1280,"height":1024},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"root","window":null,"nodes":[{"id":6809472,"type":1,"orientation":"none","percent":1,"urgent":false,"focused":false,"layout":"output","border":"none","rect":{"x":0,"y":0,"width":1280,"height":1024},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"xroot-0","window":null,"nodes":[{"id":6810064,"type":5,"orientation":"vertical","percent":null,"urgent":false,"focused":false,"layout":"dockarea","border":"none","rect":{"x":0,"y":0,"width":1280,"height":0},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"topdock","window":null,"nodes":[],"floating_nodes":[],"focus":[],"fullscreen_mode":0,"swallows":[{"dock":2,"insert_where":2}]},{"id":6831664,"type":2,"orientation":"none","percent":null,"urgent":false,"focused":false,"layout":"default","border":"none","rect":{"x":0,"y":0,"width":1280,"height":1024},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"content","window":null,"nodes":[{"id":6832880,"type":4,"orientation":"horizontal","percent":null,"urgent":false,"focused":true,"layout":"default","border":"none","rect":{"x":0,"y":0,"width":1280,"height":1024},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"1","num":1,"window":null,"nodes":[],"floating_nodes":[],"focus":[],"fullscreen_mode":1,"swallows":[]}],"floating_nodes":[],"focus":[6832880],"fullscreen_mode":0,"swallows":[]},{"id":6832224,"type":5,"orientation":"vertical","percent":null,"urgent":false,"focused":false,"layout":"dockarea","border":"none","rect":{"x":0,"y":1024,"width":1280,"height":0},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"bottomdock","window":null,"nodes":[],"floating_nodes":[],"focus":[],"fullscreen_mode":0,"swallows":[{"dock":3,"insert_where":2}]}],"floating_nodes":[],"focus":[6831664,6810064,6832224],"fullscreen_mode":0,"swallows":[]}],"floating_nodes":[],"focus":[6809472],"fullscreen_mode":0,"swallows":[]}
\ No newline at end of file
index 0db3b83b3e824cb4bb241c304b02b6690c37a7ed..c13b87c4f57bef37e8e8ddd45f76c3976b78dfd9 100644 (file)
@@ -2,31 +2,18 @@
 # 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',
-);
-
+my $window = open_window(rect => $original_rect, dont_map => 1);
 isa_ok($window, 'X11::XCB::Window');
 
 is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
 
 $window->map;
-
-sleep(0.25);
+wait_for_map $window;
 
 my $new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
+ok(!eq_hash($new_rect, $original_rect), "Window got repositioned");
 
 done_testing;
index 7518c9493b466daf6109a96413a23885b6dd8d62..1377ee942407f8c615273212ea82e563b1134441 100644 (file)
@@ -3,13 +3,9 @@
 #
 # 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);
+my $result = sync_with_i3;
 ok($result, 'syncing was successful');
 
 done_testing;
index 982ece7e9a2c7f232dc530d19b4bfce63edeea7a..34359f20a7e28410378666bbd0f2c3665fa12295 100644 (file)
@@ -3,8 +3,6 @@
 
 use i3test;
 
-my $x = X11::XCB::Connection->new;
-
 fresh_workspace;
 
 #####################################################################
@@ -12,14 +10,14 @@ fresh_workspace;
 #####################################################################
 
 # Create a window so we can get a focus different from NULL
-my $window = open_window($x);
+my $window = open_window;
 
 my $focus = $x->input_focus;
 
 # Switch to another workspace
 fresh_workspace;
 
-sync_with_i3($x);
+sync_with_i3;
 my $new_focus = $x->input_focus;
 isnt($focus, $new_focus, "Focus changed");
 
index 1ef934ee5f99f3f6bdddf18e1a66c2d9050187c7..e998eb46fcddce97ec82634520abff3cad1599a0 100644 (file)
@@ -2,21 +2,13 @@
 # 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,
+my $window = open_window(
     rect => $original_rect,
     override_redirect => 1,
-    background_color => '#C0C0C0',
+    dont_map => 1,
 );
 
 isa_ok($window, 'X11::XCB::Window');
index 688de40a5634a5e0d8250ea44715e985d8d5a4dd..db5fb6db5ed564bf27a35c731724af7a32220134 100644 (file)
@@ -2,30 +2,12 @@
 # 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' ],
-);
+my $window = open_floating_window;
 
 isa_ok($window, 'X11::XCB::Window');
 
-$window->map;
-
-wait_for_map $x;
-
 my ($absolute, $top) = $window->rect;
 
 ok($window->mapped, 'Window is mapped');
@@ -36,20 +18,10 @@ 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 => [ 20, 20, 80, 90],
-    background_color => '#C0C0C0',
-    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-    event_mask => [ 'structure_notify' ],
-);
+$window = open_floating_window(rect => [ 20, 20, 80, 90 ]);
 
 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");
@@ -65,21 +37,12 @@ $window->unmap;
 # 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' ],
-);
+$window = open_window(rect => [ 1, 1, 80, 90 ]);
 
 isa_ok($window, 'X11::XCB::Window');
 
-$window->map;
-
-wait_for_map $x;
-
 cmd 'floating enable';
+sync_with_i3;
 
 ($absolute, $top) = $window->rect;
 
index 06c1411a9c625f8a5bbcb359d486397717793c17..f8fc92a24aee749c58bbd58d31c3383e6a3d8e86 100644 (file)
@@ -2,7 +2,6 @@
 # vim:ts=4:sw=4:expandtab
 
 use i3test;
-use X11::XCB qw(:all);
 use List::Util qw(first);
 
 my $i3 = i3(get_socket_path());
@@ -26,23 +25,15 @@ for my $o (@outputs) {
     }
 }
 
-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,
+my $window = open_window(
     rect => $original_rect,
-    background_color => '#C0C0C0',
-    event_mask => [ 'structure_notify' ],
+    dont_map => 1,
 );
 
 isa_ok($window, 'X11::XCB::Window');
@@ -51,21 +42,21 @@ is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
 
 $window->map;
 
-wait_for_map $x;
+wait_for_map $window;
 
 # 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");
+ok(!eq_hash($new_rect, $original_rect), "Window got repositioned");
 $original_rect = $new_rect;
 
 $window->fullscreen(1);
 
-sync_with_i3($x);
+sync_with_i3;
 
 $new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
+ok(!eq_hash($new_rect, $original_rect), "Window got repositioned after fullscreen");
 
 my $orect = $output->{rect};
 my $wrect = $new_rect;
@@ -89,11 +80,9 @@ $window->unmap;
 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,
+$window = open_window(
     rect => $original_rect,
-    background_color => 61440,
-    event_mask => [ 'structure_notify' ],
+    dont_map => 1,
 );
 
 is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
@@ -101,10 +90,10 @@ is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
 $window->fullscreen(1);
 $window->map;
 
-wait_for_map $x;
+wait_for_map $window;
 
 $new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
+ok(!eq_hash($new_rect, $original_rect), "Window got repositioned after fullscreen");
 ok($window->mapped, "Window is mapped after opening it in fullscreen mode");
 
 $wrect = $new_rect;
@@ -120,29 +109,27 @@ ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate ful
 ###############################################################################
 
 $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,
+my $swindow = open_window(
     rect => $original_rect,
-    background_color => '#C0C0C0',
-    event_mask => [ 'structure_notify' ],
+    dont_map => 1,
 );
 
 $swindow->map;
 
-sync_with_i3($x);
+sync_with_i3;
 
 ok(!$swindow->mapped, 'window not mapped while fullscreen window active');
 
 $new_rect = $swindow->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
+ok(!eq_hash($new_rect, $original_rect), "Window got repositioned");
 
 $swindow->fullscreen(1);
-sync_with_i3($x);
+sync_with_i3;
 
 is(fullscreen_windows(), 1, 'amount of fullscreen windows');
 
 $window->fullscreen(0);
-sync_with_i3($x);
+sync_with_i3;
 is(fullscreen_windows(), 0, 'amount of fullscreen windows');
 
 ok($swindow->mapped, 'window mapped after other fullscreen ended');
@@ -154,7 +141,7 @@ ok($swindow->mapped, 'window mapped after other fullscreen ended');
 ###########################################################################
 
 $swindow->fullscreen(0);
-sync_with_i3($x);
+sync_with_i3;
 
 is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling');
 
index 5ded494fbe9fd5e4894c04853d79532cb4a68cd9..8a795c46c0a3d4f8a555fa08f5700c0be00b9cf0 100644 (file)
@@ -3,8 +3,6 @@
 
 use i3test;
 
-my $x = X11::XCB::Connection->new;
-
 my $tmp = fresh_workspace;
 
 #####################################################################
@@ -15,9 +13,9 @@ my $tmp = fresh_workspace;
 cmd 'layout default';
 cmd 'split v';
 
-my $top = open_window($x);
-my $mid = open_window($x);
-my $bottom = open_window($x);
+my $top = open_window;
+my $mid = open_window;
+my $bottom = open_window;
 
 #
 # Returns the input focus after sending the given command to i3 via IPC
@@ -27,11 +25,10 @@ sub focus_after {
     my $msg = shift;
 
     cmd $msg;
-    sync_with_i3 $x;
     return $x->input_focus;
 }
 
-$focus = $x->input_focus;
+my $focus = $x->input_focus;
 is($focus, $bottom->id, "Latest window focused");
 
 $focus = focus_after('focus up');
index cad54c26c81aa17228591437ff735b08aaab7442..20acf49e033ded8426b45b9f54671b06006c618d 100644 (file)
@@ -2,15 +2,9 @@
 # vim:ts=4:sw=4:expandtab
 
 use i3test;
-use X11::XCB qw(:all);
+use X11::XCB 'PROP_MODE_REPLACE';
 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
 #####################################################################
@@ -29,7 +23,7 @@ my $screens = $x->screens;
 my $primary = first { $_->primary } @{$screens};
 
 # TODO: focus the primary screen before
-my $window = open_window($x, {
+my $window = open_window({
         window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
     });
 
@@ -58,7 +52,7 @@ is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
 
 $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 50, height => 40));
 
-sync_with_i3 $x;
+sync_with_i3;
 
 @docked = get_dock_clients('top');
 is(@docked, 1, 'one dock client found');
@@ -73,7 +67,7 @@ is($docknode->{rect}->{height}, 40, 'dock height changed');
 
 $window->destroy;
 
-wait_for_unmap $x;
+wait_for_unmap $window;
 
 @docked = get_dock_clients();
 is(@docked, 0, 'no more dock clients');
@@ -82,13 +76,13 @@ is(@docked, 0, 'no more dock clients');
 # check if it gets placed on bottom (by coordinates)
 #####################################################################
 
-$window = open_window($x, {
+$window = open_window({
         rect => [ 0, 1000, 30, 30 ],
         background_color => '#FF0000',
         window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
     });
 
-my $rect = $window->rect;
+$rect = $window->rect;
 is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
 is($rect->height, 30, 'height is unchanged');
 
@@ -97,7 +91,7 @@ is(@docked, 1, 'dock client on bottom');
 
 $window->destroy;
 
-wait_for_unmap $x;
+wait_for_unmap $window;
 
 @docked = get_dock_clients();
 is(@docked, 0, 'no more dock clients');
@@ -106,7 +100,7 @@ is(@docked, 0, 'no more dock clients');
 # check if it gets placed on bottom (by hint)
 #####################################################################
 
-$window = open_window($x, {
+$window = open_window({
         dont_map => 1,
         rect => [ 0, 1000, 30, 30 ],
         background_color => '#FF0000',
@@ -131,19 +125,19 @@ $x->change_property(
 
 $window->map;
 
-wait_for_map $x;
+wait_for_map $window;
 
 @docked = get_dock_clients('top');
 is(@docked, 1, 'dock client on top');
 
 $window->destroy;
 
-wait_for_unmap $x;
+wait_for_unmap $window;
 
 @docked = get_dock_clients();
 is(@docked, 0, 'no more dock clients');
 
-$window = open_window($x, {
+$window = open_window({
         dont_map => 1,
         rect => [ 0, 1000, 30, 30 ],
         background_color => '#FF0000',
@@ -153,8 +147,8 @@ $window = open_window($x, {
 $window->_create();
 
 # Add a _NET_WM_STRUT_PARTIAL hint
-my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
-my $atomtype = $x->atom(name => 'CARDINAL');
+$atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
+$atomtype = $x->atom(name => 'CARDINAL');
 
 $x->change_property(
     PROP_MODE_REPLACE,
@@ -168,7 +162,7 @@ $x->change_property(
 
 $window->map;
 
-wait_for_map $x;
+wait_for_map $window;
 
 @docked = get_dock_clients('bottom');
 is(@docked, 1, 'dock client on bottom');
@@ -180,7 +174,7 @@ $window->destroy;
 # regression test: transient dock client
 #####################################################################
 
-$fwindow = open_window($x, {
+my $fwindow = open_window({
         dont_map => 1,
         background_color => '#FF0000',
         window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
@@ -189,7 +183,7 @@ $fwindow = open_window($x, {
 $fwindow->transient_for($window);
 $fwindow->map;
 
-wait_for_map $x;
+wait_for_map $fwindow;
 
 does_i3_live;
 
index 6e35ebe49ee7c0ba5847b4077bc4bf2b0d81e701..040faf203edbe05f90e0f72aed1d67d46cda667f 100644 (file)
@@ -48,7 +48,7 @@ sub focus_after {
     return $x->input_focus;
 }
 
-$focus = $x->input_focus;
+my $focus = $x->input_focus;
 is($focus, $bottom->id, "Latest window focused");
 
 $focus = focus_after("ml");
index b5be284c3b0cbde3eedc9b6387643a490100d06c..3b3fe74d79f5c02a844aa053e005a2a275e327f1 100644 (file)
@@ -5,25 +5,23 @@
 
 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);
+my $tiled_left = open_window;
+my $tiled_right = open_window;
 
 # 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);
+my $window = open_floating_window;
 
 is($x->input_focus, $window->id, 'floating window focused');
 
 $window->unmap;
 
-wait_for_unmap($x);
+wait_for_unmap $window;
 
 is($x->input_focus, $focus, 'Focus correctly restored');
 
index cc285f32788162d946c1dac5a47d2830d725d8ea..ec7b8df874ab71d6b33b7742a83fda3d5cbffd12 100644 (file)
@@ -46,7 +46,7 @@ sub focus_after {
     return $x->input_focus;
 }
 
-$focus = $x->input_focus;
+my $focus = $x->input_focus;
 is($focus, $bottom->id, "Latest window focused");
 
 $focus = focus_after("s");
index 5cc20481d02a68b8ed9735c3d97a23e05a7fe6a8..078ab92c071ff8b6a55b25320262f7383997a936 100644 (file)
@@ -4,8 +4,6 @@
 use i3test;
 use File::Temp;
 
-my $x = X11::XCB::Connection->new;
-
 my $tmp = fresh_workspace;
 
 cmd 'split h';
@@ -14,9 +12,9 @@ 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);
+my $top = open_window;
+my $mid = open_window;
+my $bottom = open_window;
 
 #
 # Returns the input focus after sending the given command to i3 via IPC
@@ -26,11 +24,10 @@ sub focus_after {
     my $msg = shift;
 
     cmd $msg;
-    sync_with_i3($x);
     return $x->input_focus;
 }
 
-$focus = $x->input_focus;
+my $focus = $x->input_focus;
 is($focus, $bottom->id, "Latest window focused");
 
 $focus = focus_after('focus left');
index 931b34fb72dc013cb69bb7d55c026303c52b8801..52817d709d0ed8784cfcb98ac51d6a8de41c0612 100644 (file)
@@ -2,13 +2,6 @@
 # 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;
 
@@ -16,13 +9,13 @@ fresh_workspace;
 # Create a floating window and see if resizing works
 #####################################################################
 
-my $window = open_floating_window($x);
+my $window = open_floating_window;
 
 # 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);
+sync_with_i3;
 
 my ($na, $nt) = $window->rect;
 is_deeply($na, $a, 'Rects are equal after configurerequest');
@@ -30,7 +23,7 @@ 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);
+    sync_with_i3;
 
     my ($absolute, $top) = $window->rect;
 
@@ -41,7 +34,7 @@ sub test_resize {
 
     $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 300, height => 500));
 
-    sync_with_i3($x);
+    sync_with_i3;
 
     ($absolute, $top) = $window->rect;
 
index 7954408fa2bf30efaa30d590f0c16ef1fedf2ea2..04f72c3d8319945422d0f455d043d08b6a8f3c8a 100644 (file)
@@ -2,15 +2,8 @@
 # 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;
 
 #####################################################################
@@ -19,8 +12,8 @@ my $tmp = fresh_workspace;
 
 cmd 'split v';
 
-my $top = open_window($x);
-my $bottom = open_window($x);
+my $top = open_window;
+my $bottom = open_window;
 
 my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
 is(@urgent, 0, 'no window got the urgent flag');
@@ -31,12 +24,12 @@ is(@urgent, 0, 'no window got the urgent flag');
 # Add the urgency hint, switch to a different workspace and back again
 #####################################################################
 $top->add_hint('urgency');
-sync_with_i3($x);
+sync_with_i3;
 
-@content = @{get_ws_content($tmp)};
+my @content = @{get_ws_content($tmp)};
 @urgent = grep { $_->{urgent} } @content;
-$top_info = first { $_->{window} == $top->id } @content;
-$bottom_info = first { $_->{window} == $bottom->id } @content;
+my $top_info = first { $_->{window} == $top->id } @content;
+my $bottom_info = first { $_->{window} == $bottom->id } @content;
 
 ok($top_info->{urgent}, 'top window is marked urgent');
 ok(!$bottom_info->{urgent}, 'bottom window is not marked urgent');
@@ -48,7 +41,7 @@ cmd '[id="' . $top->id . '"] focus';
 is(@urgent, 0, 'no window got the urgent flag after focusing');
 
 $top->add_hint('urgency');
-sync_with_i3($x);
+sync_with_i3;
 
 @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
 is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint');
@@ -56,13 +49,14 @@ 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);
+sync_with_i3;
 
 $ws = get_ws($tmp);
 ok($ws->{urgent}, 'urgent flag set on workspace');
@@ -72,4 +66,70 @@ cmd "workspace $tmp";
 $ws = get_ws($tmp);
 ok(!$ws->{urgent}, 'urgent flag not set on workspace after switching');
 
+################################################################################
+# Use the 'urgent' criteria to switch to windows which have the urgency hint set.
+################################################################################
+
+# Go to a new workspace, open a different window, verify focus is on it.
+$otmp = fresh_workspace;
+my $different_window = open_window;
+is($x->input_focus, $different_window->id, 'new window focused');
+
+# Add the urgency hint on the other window.
+$top->add_hint('urgency');
+sync_with_i3;
+
+# Now try to switch to that window and see if focus changes.
+cmd '[urgent=latest] focus';
+isnt($x->input_focus, $different_window->id, 'window no longer focused');
+is($x->input_focus, $top->id, 'urgent window focused');
+
+################################################################################
+# Same thing, but with multiple windows and using the 'urgency=latest' criteria
+# (verify that it works in the correct order).
+################################################################################
+
+cmd "workspace $otmp";
+is($x->input_focus, $different_window->id, 'new window focused again');
+
+$top->add_hint('urgency');
+sync_with_i3;
+
+$bottom->add_hint('urgency');
+sync_with_i3;
+
+cmd '[urgent=latest] focus';
+is($x->input_focus, $bottom->id, 'latest urgent window focused');
+$bottom->delete_hint('urgency');
+sync_with_i3;
+
+cmd '[urgent=latest] focus';
+is($x->input_focus, $top->id, 'second urgent window focused');
+$top->delete_hint('urgency');
+sync_with_i3;
+
+################################################################################
+# Same thing, but with multiple windows and using the 'urgency=oldest' criteria
+# (verify that it works in the correct order).
+################################################################################
+
+cmd "workspace $otmp";
+is($x->input_focus, $different_window->id, 'new window focused again');
+
+$top->add_hint('urgency');
+sync_with_i3;
+
+$bottom->add_hint('urgency');
+sync_with_i3;
+
+cmd '[urgent=oldest] focus';
+is($x->input_focus, $top->id, 'oldest urgent window focused');
+$top->delete_hint('urgency');
+sync_with_i3;
+
+cmd '[urgent=oldest] focus';
+is($x->input_focus, $bottom->id, 'oldest urgent window focused');
+$bottom->delete_hint('urgency');
+sync_with_i3;
+
 done_testing;
index 6f7ffce032e3d440e99552c8e5ef5b9aa97037f5..497bad9e1aa73195bc8eb6d434650032a842fe83 100644 (file)
@@ -2,13 +2,6 @@
 # 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;
 
@@ -20,42 +13,42 @@ my $tmp = fresh_workspace;
 # 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 $left = open_window({ name => 'Left' });
+my $right = open_window({ name => 'Right' });
 
 my ($abs, $rgeom) = $right->rect;
 
-my $child = open_floating_window($x, {
+my $child = open_floating_window({
         dont_map => 1,
         name => 'Child window',
     });
 $child->client_leader($right);
 $child->map;
 
-ok(wait_for_map($x), 'child window mapped');
+ok(wait_for_map($child), '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, {
+my $child2 = open_floating_window({
         dont_map => 1,
         name => 'Child window 2',
     });
 $child2->client_leader($left);
 $child2->map;
 
-ok(wait_for_map($x), 'second child window mapped');
+ok(wait_for_map($child2), '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 });
+my $fwindow = open_window({ dont_map => 1 });
 $fwindow->transient_for($right);
 $fwindow->map;
 
-ok(wait_for_map($x), 'transient window mapped');
+ok(wait_for_map($fwindow), 'transient window mapped');
 
 my ($absolute, $top) = $fwindow->rect;
 ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
@@ -67,10 +60,10 @@ SKIP: {
 # Create a parent window
 #####################################################################
 
-my $window = open_window($x, { dont_map => 1, name => 'Parent window' });
+my $window = open_window({ dont_map => 1, name => 'Parent window' });
 $window->map;
 
-ok(wait_for_map($x), 'parent window mapped');
+ok(wait_for_map($window), 'parent window mapped');
 
 #########################################################################
 # Switch to a different workspace and open a child window. It should be opened
@@ -78,11 +71,11 @@ ok(wait_for_map($x), 'parent window mapped');
 #########################################################################
 fresh_workspace;
 
-my $child = open_window($x, { dont_map => 1, name => 'Child window' });
+my $child = open_window({ dont_map => 1, name => 'Child window' });
 $child->client_leader($window);
 $child->map;
 
-ok(wait_for_map($x), 'child window mapped');
+ok(wait_for_map($child), 'child window mapped');
 
 isnt($x->input_focus, $child->id, "Child window focused");
 
index 4b3958a1eb4ec09bc94f15802f325a363649b893..3a495e2788d5ee6842c745197fc96977511cb27e 100644 (file)
@@ -29,28 +29,46 @@ my $i3 = i3(get_socket_path());
 
 my $tree = $i3->get_tree->recv;
 
+# a unique value
+my $ignore = \"";
+
 my $expected = {
     fullscreen_mode => 0,
-    nodes => ignore(),
+    nodes => $ignore,
     window => undef,
     name => 'root',
-    orientation => ignore(),
+    orientation => $ignore,
     type => 0,
-    id => ignore(),
-    rect => ignore(),
-    window_rect => ignore(),
-    geometry => ignore(),
-    swallows => ignore(),
+    id => $ignore,
+    rect => $ignore,
+    window_rect => $ignore,
+    geometry => $ignore,
+    swallows => $ignore,
     percent => undef,
     layout => 'default',
-    focus => ignore(),
+    floating => 'auto_off',
+    scratchpad_state => 'none',
+    focus => $ignore,
     focused => JSON::XS::false,
     urgent => JSON::XS::false,
     border => 'normal',
-    'floating_nodes' => ignore(),
+    'floating_nodes' => $ignore,
 };
 
-cmp_deeply($tree, $expected, 'root node OK');
+# a shallow copy is sufficient, since we only ignore values at the root
+my $tree_copy = { %$tree };
+
+for (keys %$expected) {
+    my $val = $expected->{$_};
+
+    # delete unwanted keys, so we can use is_deeply()
+    if (ref($val) eq 'SCALAR' and $val == $ignore) {
+        delete $tree_copy->{$_};
+        delete $expected->{$_};
+    }
+}
+
+is_deeply($tree_copy, $expected, 'root node OK');
 
 my @nodes = @{$tree->{nodes}};
 
index 3c3b6cc675421a510dc8c25b723198c2e8bf5006..3c52e31eea1399fc8e569ffcbfaf4bd57674b22f 100644 (file)
@@ -108,12 +108,12 @@ 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");
+$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");
+$ws = get_ws("aa: $tmp");
 ok(defined($ws), "workspace aa: $tmp was created");
 is($ws->{num}, -1, 'workspace number is -1');
 
index 8b9d21d3e0da46fbc422bda5041cd9365fcbbb61..e6a4e832fdb3daa2a7da0d7abe565d68c9cba049 100644 (file)
@@ -4,15 +4,14 @@
 # Tests all kinds of matching methods
 #
 use i3test;
-use X11::XCB qw(:all);
+use X11::XCB qw(PROP_MODE_REPLACE);
 
 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 $window = open_window;
 my $content = get_ws_content($tmp);
 ok(@{$content} == 1, 'window mapped');
 my $win = $content->[0];
@@ -35,7 +34,7 @@ cmd 'nop now killing the window';
 my $id = $win->{id};
 cmd qq|[con_id="$id"] kill|;
 
-wait_for_unmap $x;
+wait_for_unmap $window;
 
 cmd 'nop checking if its gone';
 $content = get_ws_content($tmp);
@@ -70,31 +69,21 @@ sub set_wm_class {
     );
 }
 
-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');
+sub open_special {
+    my %args = @_;
+    my $wm_class = delete($args{wm_class}) || 'special';
+
+    return open_window(
+        %args,
+        before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
+    );
+}
+
+my $left = open_special(name => 'left');
+ok($left->mapped, 'left window mapped');
+
+my $right = open_special(name => 'right');
+ok($right->mapped, 'right window mapped');
 
 # two windows should be here
 $content = get_ws_content($tmp);
@@ -102,7 +91,7 @@ ok(@{$content} == 2, 'two windows opened');
 
 cmd '[class="special" title="left"] kill';
 
-sync_with_i3($x);
+sync_with_i3;
 
 $content = get_ws_content($tmp);
 is(@{$content}, 1, 'one window still there');
@@ -113,18 +102,8 @@ is(@{$content}, 1, 'one window still there');
 
 $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');
+$left = open_special(name => 'left', wm_class => 'special7');
+ok($left->mapped, 'left window mapped');
 
 # two windows should be here
 $content = get_ws_content($tmp);
@@ -132,7 +111,7 @@ ok(@{$content} == 1, 'window opened');
 
 cmd '[class="^special[0-9]$"] kill';
 
-wait_for_unmap $x;
+wait_for_unmap $left;
 
 $content = get_ws_content($tmp);
 is(@{$content}, 0, 'window killed');
@@ -143,18 +122,8 @@ is(@{$content}, 0, 'window killed');
 
 $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');
+$left = open_special(name => 'ä 3', wm_class => 'special7');
+ok($left->mapped, 'left window mapped');
 
 # two windows should be here
 $content = get_ws_content($tmp);
@@ -162,7 +131,7 @@ ok(@{$content} == 1, 'window opened');
 
 cmd '[title="^\w [3]$"] kill';
 
-wait_for_unmap $x;
+wait_for_unmap $left;
 
 $content = get_ws_content($tmp);
 is(@{$content}, 0, 'window killed');
index 0dfb99a37afb81f595db415c5e07b35af10bd6a7..702679ac41e734880fc2a951231b2d853c81dcfd 100644 (file)
@@ -4,7 +4,6 @@
 # Tests splitting
 #
 use i3test;
-use X11::XCB qw(:all);
 
 my $tmp = fresh_workspace;
 
index a6eb61648384d2108a3a4f1a53f769cc9fe3b091..ae989f065d0727eff7ede111aa971616312d0bc1 100644 (file)
@@ -8,9 +8,7 @@
 # 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;
@@ -138,7 +136,7 @@ is(@{$content}, 1, 'only one nodes on this workspace');
 ######################################################################
 
 $tmp = fresh_workspace;
-my $floatwin = open_floating_window($x);
+my $floatwin = open_floating_window;
 my ($absolute_before, $top_before) = $floatwin->rect;
 
 cmd 'move left';
@@ -197,7 +195,7 @@ cmd 'move left 20 px';
 
 ($absolute, $top) = $floatwin->rect;
 
-is($absolute->x, ($absolute_before->x - 20), 'moved 10 px to the left');
+is($absolute->x, ($absolute_before->x - 20), 'moved 20 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');
index 52b8b9c0624693e1ae246fd9e22b8d805b64900c..c83c0809e3c50942704be11a7b04c6f3037c4f1f 100644 (file)
@@ -16,8 +16,7 @@ cmd 'open';
 my $floating = get_focused($tmp);
 diag("focused floating: " . get_focused($tmp));
 cmd 'mode toggle';
-# TODO: eliminate this race conditition
-sleep 1;
+sync_with_i3;
 
 # kill old container
 cmd qq|[con_id="$old"] focus|;
@@ -33,7 +32,7 @@ cmd 'kill';
 cmd qq|[con_id="$floating"] focus|;
 is(get_focused($tmp), $floating, 'floating window focused');
 
-sleep 1;
+sync_with_i3;
 cmd 'mode toggle';
 
 does_i3_live;
index b638e708446606d3820b4fbfd506a744b5b1975f..ee58968f7f083166adf4c1c0eccb156670f50374 100644 (file)
@@ -27,7 +27,7 @@ isnt($first, $second, 'different container focused');
 
 cmd qq|[con_id="$first"] focus|;
 cmd 'open';
-$content = get_ws_content($tmp);
+my $content = get_ws_content($tmp);
 ok(@{$content} == 3, 'three containers opened');
 
 is($content->[0]->{id}, $first, 'first container unmodified');
index 8d22561359234e5643d3815e9fc3081e350c183f..5fc3786e02292bfdbf988053aad25a56fc95a5a0 100644 (file)
@@ -4,11 +4,8 @@
 # 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;
@@ -45,9 +42,8 @@ isnt(get_focused($tmp), $second, 'different container focused');
 ##############################################################
 
 cmd 'kill';
-# TODO: this testcase sometimes has different outcomes when the
-# sleep is missing. why?
-sleep 0.25;
+sync_with_i3;
+
 ($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');
@@ -102,12 +98,11 @@ $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' });
+my $win = open_window({ background_color => '#00ff00' });
 
 cmd qq|[con_id="$middle"] focus|;
 $win->destroy;
-
-sleep 0.25;
+sync_with_i3;
 
 is(get_focused($tmp), $middle, 'middle container focused');
 
index 57855cd5cfb4409fece6e8b5cdc5854c9e6b0b4e..ce9ebd7a16afa79e6dd79f4c0af26d51c68d41bf 100644 (file)
@@ -17,7 +17,7 @@ cmd qq|[con_id="$first"] focus|;
 
 cmd 'split v';
 
-($nodes, $focus) = get_ws_content($tmp);
+my ($nodes, $focus) = get_ws_content($tmp);
 
 is($nodes->[0]->{focused}, 0, 'split container not focused');
 
@@ -27,7 +27,7 @@ cmd 'level up';
 my $split = $focus->[0];
 cmd 'level down';
 
-my $second = open_empty_con($i3);
+$second = open_empty_con($i3);
 
 isnt($first, $second, 'different container focused');
 
@@ -62,10 +62,10 @@ 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];
+$split = $focus->[0];
 cmd 'level down';
 
-my $second = open_empty_con($i3);
+$second = open_empty_con($i3);
 
 isnt($first, $second, 'different container focused');
 
index 82e59dd11cea0f1373fe2dacc76187a2f9e0e2fb..a7c5544bd5c1b408ea273e4c016c1973ea0ece4a 100644 (file)
@@ -1,7 +1,7 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
-# Checks if the 'move workspace' command works correctly
+# Checks if the 'move [window/container] to workspace' command works correctly
 #
 use i3test;
 
@@ -9,33 +9,42 @@ 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";
+sub move_workspace_test {
+    my ($movecmd) = @_;
 
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+    my $tmp = get_unused_workspace();
+    my $tmp2 = get_unused_workspace();
+    cmd "workspace $tmp";
 
-my $first = open_empty_con($i3);
-my $second = open_empty_con($i3);
-ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws');
+    ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 
-cmd "workspace $tmp2";
-ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws 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 $tmp";
+    cmd "workspace $tmp2";
+    ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws yet');
 
-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);
+    cmd "workspace $tmp";
+
+    cmd "$movecmd $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');
 
-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');
+}
 
-($nodes, $focus) = get_ws_content($tmp);
-ok($nodes->[0]->{focused}, 'first container focused on first ws');
+move_workspace_test('move workspace');  # supported for legacy reasons
+move_workspace_test('move to workspace');
+# Those are just synonyms and more verbose ways of saying the same thing:
+move_workspace_test('move window to workspace');
+move_workspace_test('move container to workspace');
 
 ###################################################################
 # check if 'move workspace next' and 'move workspace prev' work
@@ -44,12 +53,12 @@ ok($nodes->[0]->{focused}, 'first container focused on first ws');
 # 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();
+my $tmp = get_unused_workspace();
+my $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);
+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";
index d2d77e8e0f2ae6abb6b14684776dfcf26b96c62b..d3736e3c35fee0d64db0546b4de63adcdd8da24b 100644 (file)
@@ -5,13 +5,11 @@
 #
 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 });
+my $win = open_window({ 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;
@@ -21,11 +19,11 @@ $aspect->max_num(600);
 $aspect->max_den(300);
 $win->_create;
 $win->map;
-wait_for_map $x;
+wait_for_map $win;
 $win->hints->aspect($aspect);
 $x->flush;
 
-sync_with_i3($x);
+sync_with_i3;
 
 my $rect = $win->rect;
 my $ar = $rect->width / $rect->height;
index a1ab211e501d59531bb50e8cb8d86a4bf91cc008..c7218130166f082f22a8dcd2a991df3fcc000fc7 100644 (file)
@@ -2,10 +2,6 @@
 # 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;
 
@@ -13,8 +9,8 @@ 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);
+my $first = open_window;
+my $second = open_window;
 
 is($x->input_focus, $second->id, 'second window focused');
 
@@ -30,9 +26,9 @@ is($x->input_focus, $second->id, 'second window still focused after mode toggle'
 
 $tmp = fresh_workspace;
 
-$first = open_window($x);    # window 2
-$second = open_window($x);   # window 3
-my $third = open_window($x); # window 4
+$first = open_window;    # window 2
+$second = open_window;   # window 3
+my $third = open_window; # window 4
 
 is($x->input_focus, $third->id, 'last container focused');
 
@@ -40,8 +36,6 @@ cmd 'floating enable';
 
 cmd '[id="' . $second->id . '"] focus';
 
-sync_with_i3($x);
-
 is($x->input_focus, $second->id, 'second con focused');
 
 cmd 'floating enable';
@@ -49,8 +43,7 @@ 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);
+wait_for_unmap($third);
 
 is($x->input_focus, $second->id, 'second con still focused after killing third');
 
@@ -62,9 +55,9 @@ is($x->input_focus, $second->id, 'second con still focused after killing third')
 
 $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
+$first = open_window({ background_color => '#ff0000' });    # window 5
+$second = open_window({ background_color => '#00ff00' });   # window 6
+$third = open_window({ background_color => '#0000ff' }); # window 7
 
 is($x->input_focus, $third->id, 'last container focused');
 
@@ -72,8 +65,6 @@ cmd 'floating enable';
 
 cmd '[id="' . $second->id . '"] focus';
 
-sync_with_i3($x);
-
 is($x->input_focus, $second->id, 'second con focused');
 
 cmd 'floating enable';
@@ -81,15 +72,12 @@ 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);
+wait_for_unmap($second);
 
 is($x->input_focus, $third->id, 'third con focused');
 
 cmd 'kill';
-# TODO: wait for unmapnotify
-sync_with_i3($x);
+wait_for_unmap($third);
 
 is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
 
@@ -99,11 +87,11 @@ is($x->input_focus, $first->id, 'first con focused after killing all floating co
 
 $tmp = fresh_workspace;
 
-$first = open_window($x, { background_color => '#ff0000' });    # window 5
+$first = open_window({ 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
+$second = open_window({ background_color => '#00ff00' });   # window 6
+$third = open_window({ background_color => '#0000ff' }); # window 7
 
 is($x->input_focus, $third->id, 'last container focused');
 
@@ -111,26 +99,21 @@ 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);
+sync_with_i3;
 
 # 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);
+wait_for_unmap($second);
 
 is($x->input_focus, $third->id, 'third con focused');
 
 cmd 'kill';
-# TODO: wait for unmapnotify
-sync_with_i3($x);
+wait_for_unmap($third);
 
 is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
 
@@ -140,10 +123,8 @@ is($x->input_focus, $first->id, 'first con focused after killing all floating co
 
 $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);
+$first = open_window({ background_color => '#ff0000' });    # window 8
+$second = open_window({ background_color => '#00ff00' });   # window 9
 
 is($x->input_focus, $second->id, 'second container focused');
 
@@ -153,32 +134,22 @@ 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');
 
 #############################################################################
@@ -187,42 +158,30 @@ is($x->input_focus, $second->id, 'second (floating) container focused');
 
 $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);
+$first = open_floating_window({ background_color => '#ff0000' });# window 10
+$second = open_floating_window({ background_color => '#00ff00' }); # window 11
+$third = open_floating_window({ background_color => '#0000ff' }); # window 12
 
 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;
index 72cb6f86f9fa7085549800e57a9acd03cfbb3bb5..fa747718e012d3a6506b36647dfa773bc9f2ed3e 100644 (file)
@@ -3,11 +3,6 @@
 # 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());
 
@@ -19,10 +14,8 @@ my $tmp = fresh_workspace;
 
 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);
+my $window = open_floating_window;
 ok($window->mapped, 'Window is mapped');
 
 # switch to a different workspace, see if the window is still mapped?
index ab1a33d313e7d9478476e30258a0828baf437278..e91870bc35e0ee1fd6bd1a203803f96c1355635a 100644 (file)
@@ -4,11 +4,6 @@
 # to a different workspace.
 
 use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
-    use_ok('X11::XCB::Window');
-}
 
 my $i3 = i3(get_socket_path());
 
@@ -18,17 +13,15 @@ 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);
+my $window = open_floating_window;
 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);
+sync_with_i3;
 
 ok(!$window->mapped, 'Window is not mapped after switching ws');
 
index b08190a22036ba320f40848fcb83294411917fd3..db86e1ca71e6e9c2e044ad8c94508697f2628cff 100644 (file)
@@ -4,11 +4,6 @@
 # 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());
 
@@ -18,10 +13,8 @@ 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);
+my $window = open_floating_window;
 ok($window->mapped, 'Window is mapped');
 
 my $ws = get_ws($tmp);
@@ -31,7 +24,7 @@ is(@{$ws->{floating_nodes}}, 1, 'one floating node');
 is(@{$nodes}, 0, 'no tiling nodes');
 
 # Create a tiling window
-my $twindow = open_window($x);
+my $twindow = open_window;
 
 ($nodes, $focus) = get_ws_content($tmp);
 
@@ -44,8 +37,8 @@ is(@{$nodes}, 1, 'one tiling node');
 
 $tmp = fresh_workspace;
 
-my $first = open_window($x);
-my $second = open_window($x);
+my $first = open_window;
+my $second = open_window;
 
 cmd 'layout stacked';
 
@@ -54,14 +47,14 @@ 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);
+$window = open_floating_window;
 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);
+my $third = open_window;
 
 
 $ws = get_ws($tmp);
index 31f013efd8cb2646d436f3f5d2e1bf9de13b0371..78b9191a73f278374d8b5a0a50aae230826434d9 100644 (file)
@@ -3,14 +3,8 @@
 # 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) = @_;
@@ -19,7 +13,7 @@ sub check_order {
     my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
     my @sorted = sort @nums;
 
-    cmp_deeply(\@nums, \@sorted, $msg);
+    is_deeply(\@nums, \@sorted, $msg);
 }
 
 check_order('workspace order alright before testing');
@@ -30,7 +24,7 @@ check_order('workspace order alright before testing');
 
 cmd "workspace 93";
 
-open_window($x);
+open_window;
 
 my @ws = @{$i3->get_workspaces->recv};
 my @f = grep { defined($_->{num}) && $_->{num} == 93 } @ws;
@@ -38,23 +32,23 @@ is(@f, 1, 'ws 93 found by num');
 check_order('workspace order alright after opening 93');
 
 cmd "workspace 92";
-open_window($x);
+open_window;
 check_order('workspace order alright after opening 92');
 
 cmd "workspace 94";
-open_window($x);
+open_window;
 check_order('workspace order alright after opening 94');
 
 cmd "workspace 96";
-open_window($x);
+open_window;
 check_order('workspace order alright after opening 96');
 
 cmd "workspace foo";
-open_window($x);
+open_window;
 check_order('workspace order alright after opening foo');
 
 cmd "workspace 91";
-open_window($x);
+open_window;
 check_order('workspace order alright after opening 91');
 
 done_testing;
index fb77f01e83b75ebefb943da352e4cfde2af09162..3d78b1bd566a9ee022977e80bee5890cb67dfd00 100644 (file)
@@ -3,14 +3,8 @@
 # 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) = @_;
@@ -19,16 +13,14 @@ sub check_order {
     my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
     my @sorted = sort @nums;
 
-    cmp_deeply(\@nums, \@sorted, $msg);
+    is_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);
+my $left = open_window;
+my $mid = open_window;
+my $right = open_window;
 
 diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id);
 
index e7424b5842908ff579bd6c9800560640eba9b231..c2038060a707c3fd55ae09ec951381a098cd651a 100644 (file)
@@ -2,22 +2,13 @@
 # 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);
+my $top = open_window;
+my $bottom = open_window;
 
 diag("top = " . $top->id . ", bottom = " . $bottom->id);
 
@@ -54,8 +45,8 @@ $tmp = fresh_workspace;
 
 cmd 'split v';
 
-$top = open_window($x);
-$bottom = open_window($x);
+$top = open_window;
+$bottom = open_window;
 
 cmd 'split h';
 cmd 'layout stacked';
@@ -103,7 +94,7 @@ is($nodes->[1]->{percent}, 0.75, 'right window got 75%');
 
 $tmp = fresh_workspace;
 
-$top = open_window($x);
+$top = open_window;
 
 cmd 'floating enable';
 
index babbb574f9ecbdaed413b6047f3af372b2d38d5e..03d9ec12a8e3cdd46d44d8da21e146468a970f72 100644 (file)
@@ -8,16 +8,20 @@ use i3test;
 my $tmp = fresh_workspace;
 
 cmd 'open';
-cmd 'mode toggle';
-cmd 'restart';
+cmd 'floating toggle';
 
-sleep 0.5;
+my $ws = get_ws($tmp);
+is(scalar @{$ws->{nodes}}, 0, 'no tiling nodes');
+is(scalar @{$ws->{floating_nodes}}, 1, 'precisely one floating node');
+
+cmd 'restart';
 
 diag('Checking if i3 still lives');
 
 does_i3_live;
 
-my $ws = get_ws($tmp);
-diag('ws = ' . Dumper($ws));
+$ws = get_ws($tmp);
+is(scalar @{$ws->{nodes}}, 0, 'no tiling nodes');
+is(scalar @{$ws->{floating_nodes}}, 1, 'precisely one floating node');
 
 done_testing;
index de33eeb3961e76f581d08c9a7555ca937a81aea9..03318d7afbf366ea28d216c8c30a2df82f0a8b47 100644 (file)
@@ -11,23 +11,24 @@ 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 $first = open_window;
+my $second = open_window;
+
 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;
+
+sync_with_i3;
+
+my $third = open_window;
+
 cmd 'mode toggle';
-sleep 0.5;
-cmd 'kill';
+sync_with_i3;
 
-sleep 0.5;
+cmd 'kill';
+sync_with_i3;
 
 ($nodes, $focus) = get_ws_content($tmp);
 my $new_sum = sum map { $_->{rect}->{width} } @{$nodes};
index 904252e74c16fcf503e15144892d308bdddfca95..9d22afc3d0f955c49e58096a0d7187bf39ca7932 100644 (file)
 #
 # 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);
+my $left = open_window;
+my $mid = open_window;
+my $right = open_window;
 
-cmd 'move before v';
-cmd 'move after h';
+cmd 'move up';
+cmd 'move right';
 my $ws = get_ws($tmp);
 
 is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal');
index bc1302bbe645d571107ea60719e0f636ba5fec39..ca209e1cadc1a799148aa00b4c4b9e8f0050896a 100644 (file)
@@ -1,22 +1,15 @@
 #!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);
+my $left = open_window;
+my $mid = open_window;
 
 cmd 'split v';
-my $bottom = open_window($x);
+my $bottom = open_window;
 
 my ($nodes, $focus) = get_ws_content($tmp);
 
@@ -25,7 +18,7 @@ my ($nodes, $focus) = get_ws_content($tmp);
 #############################################################################
 
 # Create a floating window
-my $window = open_floating_window($x);
+my $window = open_floating_window;
 ok($window->mapped, 'Window is mapped');
 
 ($nodes, $focus) = get_ws_content($tmp);
@@ -37,7 +30,7 @@ is(@{$nodes->[1]->{nodes}}, 2, 'two windows in split con');
 
 cmd 'floating toggle';
 
-my ($nodes, $focus) = get_ws_content($tmp);
+($nodes, $focus) = get_ws_content($tmp);
 
 is(@{$nodes->[1]->{nodes}}, 3, 'three windows in split con after floating toggle');
 
index 771ace320f59f6b1f7b9754b646ec63e41d4d15c..5a2c87d3f4c1e357fe900bc847121af56fa8c8eb 100644 (file)
@@ -4,40 +4,33 @@
 # 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');
+sub sync_cmd {
+    cmd @_;
+    sync_with_i3;
 }
 
-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);
+my $left = open_window;
+my $mid = open_window;
+my $right = open_window;
 
 # go to workspace level
-cmd 'level up';
-sleep 0.25;
+sync_cmd 'level up';
 
 # make it floating
-cmd 'mode toggle';
-sleep 0.25;
+sync_cmd 'mode toggle';
 
 # move the con outside the floating con
-cmd 'move before v';
-sleep 0.25;
+sync_cmd 'move up';
 
 does_i3_live;
 
 # move another con outside
-cmd '[id="' . $mid->id . '"] focus';
-cmd 'move before v';
-sleep 0.25;
+sync_cmd '[id="' . $mid->id . '"] focus';
+sync_cmd 'move up';
 
 does_i3_live;
 
index 6f9dfc40d47a36ceb2de6b888ec01a8fd68e8d70..3d71b5003387355344c443f2def994da406644de 100644 (file)
@@ -4,33 +4,24 @@
 # 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;
+open_window;
 my $first = get_focused($tmp);
 
 # on a different ws, open a floating window
 my $otmp = fresh_workspace;
-open_window($x);
-#sleep 0.25;
+open_window;
 my $float = get_focused($otmp);
 cmd 'mode toggle';
-#sleep 0.25;
+sync_with_i3;
 
 # move the floating con to first workspace
 cmd "move workspace $tmp";
-#sleep 0.25;
+sync_with_i3;
 
 # switch to the first ws and check focus
 is(get_focused($tmp), $float, 'floating client correctly focused');
index e294e6bd3de9fe70dc312f72bf19011577adfcc4..3cda60598db0e5aef8550870671e4d8c131886ab 100644 (file)
@@ -3,15 +3,8 @@
 #
 # 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;
 
 #####################################################################
@@ -25,7 +18,7 @@ is(@docked, 0, 'no dock clients yet');
 
 # open a dock client
 
-my $window = open_window($x, {
+my $window = open_window({
         background_color => '#FF0000',
         window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
     });
@@ -45,8 +38,6 @@ is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
 # perform an inplace-restart
 cmd 'restart';
 
-sleep 0.25;
-
 does_i3_live;
 
 
@@ -62,7 +53,7 @@ is($docknode->{rect}->{height}, 30, 'dock node has unchanged height after restar
 
 $window->destroy;
 
-wait_for_unmap $x;
+wait_for_unmap $window;
 
 @docked = get_dock_clients;
 is(@docked, 0, 'no dock clients found');
@@ -71,7 +62,7 @@ is(@docked, 0, 'no dock clients found');
 # create a dock client with a 1px border
 #####################################################################
 
-$window = open_window($x, {
+$window = open_window({
         border => 1,
         rect => [ 0, 0, 30, 20 ],
         background_color => '#00FF00',
@@ -85,7 +76,6 @@ $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');
index db0b6e9c63649bab76d70e31bc45b42dee62b9f2..83f3e85d58bf7cac5d9fe823d0c6ed07b57bda1c 100644 (file)
@@ -3,15 +3,12 @@
 #
 # 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 $window = open_window({ rect => [ 0, 0, 400, 150 ] });
 
 my ($absolute, $top) = $window->rect;
 
@@ -20,7 +17,7 @@ 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);
+sync_with_i3;
 
 ($absolute, $top) = $window->rect;
 
index 8b7f456cd3bd9bc22cd2ff478e08651019e77a8c..76577fb36bb3b7ad6148338a7449098eecf23838 100644 (file)
@@ -3,15 +3,8 @@
 #
 # 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;
 
 #####################################################################
@@ -27,7 +20,7 @@ is(@docked, 0, 'no dock clients yet');
 # open a dock client
 #####################################################################
 
-my $first = open_window($x, {
+my $first = open_window({
         background_color => '#FF0000',
         window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
     });
@@ -36,7 +29,7 @@ my $first = open_window($x, {
 # Open a second dock client
 #####################################################################
 
-my $second = open_window($x, {
+my $second = open_window({
         background_color => '#FF0000',
         window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
     });
index 5de05e8b2dceb44fb55f9becef9f9d8ab7df71d0..76c31af6b0d1a37a0f1c02ac8ee7f5220b129711 100644 (file)
@@ -4,22 +4,15 @@
 # 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, {
+my $first = open_window({
         rect => [ 0, 0, 200, 80],
         background_color => '#FF0000',
     });
@@ -28,7 +21,7 @@ my $first = open_window($x, {
 # Open a second window with 300x90
 #####################################################################
 
-my $second = open_window($x, {
+my $second = open_window({
         rect => [ 0, 0, 300, 90],
         background_color => '#00FF00',
     });
index 3f08effd4bf152292e22510a9aa55147de509d97..08de5ed1c396c16e700460d7ab490b2db932f741 100644 (file)
@@ -4,14 +4,8 @@
 # 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;
@@ -20,7 +14,7 @@ my $tmp = fresh_workspace;
 # open the left window
 #####################################################################
 
-my $left = open_window($x, { background_color => '#ff0000' });
+my $left = open_window({ background_color => '#ff0000' });
 
 is($x->input_focus, $left->id, 'left window focused');
 
@@ -30,7 +24,7 @@ diag("left = " . $left->id);
 # Open the right window
 #####################################################################
 
-my $right = open_window($x, { background_color => '#00ff00' });
+my $right = open_window({ background_color => '#00ff00' });
 
 diag("right = " . $right->id);
 
@@ -44,7 +38,7 @@ cmd 'fullscreen';
 # Open a third window
 #####################################################################
 
-my $third = open_window($x, {
+my $third = open_window({
         background_color => '#0000ff',
         name => 'Third window',
         dont_map => 1,
@@ -52,7 +46,7 @@ my $third = open_window($x, {
 
 $third->map;
 
-sync_with_i3 $x;
+sync_with_i3;
 
 diag("third = " . $third->id);
 
@@ -63,9 +57,6 @@ 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');
 
 ################################################################################
index 7a101dbc8cf0dd777602262283ac098a01ed80fe..a861117ccb94372c37a7184fde31ecebf5d3cc91 100644 (file)
@@ -3,22 +3,15 @@
 #
 # 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 $win = open_window;
 
 my $nodes = get_ws_content $tmp;
 is(@$nodes, 1, 'exactly one client');
@@ -31,7 +24,7 @@ is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen');
 cmd 'nop making fullscreen';
 cmd 'fullscreen';
 
-my $nodes = get_ws_content $tmp;
+$nodes = get_ws_content $tmp;
 is($nodes->[0]->{fullscreen_mode}, 1, 'client fullscreen now');
 
 #####################################################################
@@ -40,7 +33,7 @@ is($nodes->[0]->{fullscreen_mode}, 1, 'client fullscreen now');
 cmd 'level up';
 cmd 'fullscreen';
 
-my $nodes = get_ws_content $tmp;
+$nodes = get_ws_content $tmp;
 is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen any longer');
 
 does_i3_live;
index a90ce1c3d04641cb6e18a8167cad7a7b4931f13e..c4d305750183b9f020cc939f9740e132100a903c 100644 (file)
@@ -3,18 +3,16 @@
 #
 # 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');
+    my $window = open_window;
+    # sync_with_i3 will send a ClientMessage to i3 and receive one targeted to
+    # $window->id. If it receives WM_TAKE_FOCUS instead, it will return 0, thus
+    # the test will fail.
+    ok(sync_with_i3(window_id => $window->id), 'did not receive ClientMessage');
 
     done_testing;
 };
@@ -24,14 +22,14 @@ subtest 'Window with WM_TAKE_FOCUS', sub {
 
     my $take_focus = $x->atom(name => 'WM_TAKE_FOCUS');
 
-    my $window = open_window($x, {
+    my $window = open_window({
         dont_map => 1,
         protocols => [ $take_focus ],
     });
 
     $window->map;
 
-    ok(wait_for_event($x, 1, sub {
+    ok(wait_for_event(1, sub {
         return 0 unless $_[0]->{response_type} == 161;
         my ($data, $time) = unpack("L2", $_[0]->{data});
         return ($data == $take_focus->id);
index b9cc9b631e1549d25f9c8eb31b99f6903ad6ac37..c63bbbc4e12d6e8efa0cf31292c5b96a04809c7d 100644 (file)
@@ -1,10 +1,9 @@
 #!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 i3test i3_autostart => 0;
 use File::Temp qw(tempfile tempdir);
 use POSIX qw(getuid);
 use v5.10;
@@ -24,7 +23,7 @@ delete $ENV{XDG_RUNTIME_DIR};
 # See which files exist in /tmp before to not mistakenly check an already
 # existing tmpdir of another i3 instance.
 my @files_before = </tmp/i3-*>;
-my $pid = launch_with_config($config, 1);
+my $pid = launch_with_config($config, dont_add_socket_path => 1, dont_create_temp_dir => 1);
 my @files_after = </tmp/i3-*>;
 @files_after = grep { !($_ ~~ @files_before) } @files_after;
 
@@ -50,7 +49,7 @@ ok(! -e "$rtdir/i3", "$rtdir/i3 does not exist yet");
 
 $ENV{XDG_RUNTIME_DIR} = $rtdir;
 
-$pid = launch_with_config($config, 1);
+$pid = launch_with_config($config, dont_add_socket_path => 1, dont_create_temp_dir => 1);
 
 ok(-d "$rtdir/i3", "$rtdir/i3 exists and is a directory");
 $socketpath = "$rtdir/i3/ipc-socket." . $pid;
@@ -72,7 +71,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 ipc-socket $socketpath
 EOT
 
-$pid = launch_with_config($config, 1);
+$pid = launch_with_config($config, dont_add_socket_path => 1, dont_create_temp_dir => 1);
 
 ok(-S $socketpath, "file $socketpath exists and is a socket");
 
index c5e3ef8080c2cd2224cee9815d931f0a98e71dab..9ae677e7789e47c8e9a919b639243342824b7a61 100644 (file)
@@ -5,14 +5,12 @@
 # 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);
+my $window = open_window;
 
 sub get_border_style {
     my @content = @{get_ws_content($tmp)};
@@ -30,8 +28,6 @@ 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');
index 5fb88129ea3c6e320e1f1916f603809680a9641d..3562ba7a2708b9b537d6b6d40c4a03afc1b71628 100644 (file)
@@ -4,14 +4,8 @@
 # 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;
@@ -27,17 +21,10 @@ 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',
+my $window = open_window(
     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
 #####################################################################
@@ -52,7 +39,7 @@ is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
 
 $window->add_hint('urgency');
 
-sync_with_i3($x);
+sync_with_i3;
 
 does_i3_live;
 
index e55d86824de934d97cab3fb0b21f5773618c3e0d..6df2bcbddf0392a5c7d71dec0da1a158818d01f9 100644 (file)
@@ -4,20 +4,16 @@
 # Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
 # unmapped.
 #
-use X11::XCB qw(:all);
 use i3test;
+use X11::XCB qw(ICCCM_WM_STATE_NORMAL ICCCM_WM_STATE_WITHDRAWN);
 
-my $x = X11::XCB::Connection->new;
-
-my $window = open_window($x);
-
-sync_with_i3($x);
+my $window = open_window;
 
 is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal');
 
 $window->unmap;
 
-wait_for_unmap $x;
+wait_for_unmap $window;
 
 is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn');
 
index ef45a789923e94556bbbffd8b10aab0d6a3c693a..bce6b23b1e7e60d2a15c551d514e4bb939ec2aa6 100644 (file)
@@ -6,17 +6,13 @@
 #
 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;
+    my $first = open_window;
+    my $second = open_window;
 
     is($x->input_focus, $second->id, 'second window focused');
     ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
@@ -32,8 +28,7 @@ sub two_windows {
 my $tmp = two_windows;
 
 cmd 'kill';
-
-sleep 0.25;
+sync_with_i3;
 
 ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
 
@@ -42,11 +37,10 @@ ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
 # 'kill window'
 ##############################################################
 
-my $tmp = two_windows;
+$tmp = two_windows;
 
 cmd 'kill window';
-
-sleep 0.25;
+sync_with_i3;
 
 ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
 
@@ -55,11 +49,12 @@ ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
 # and check if both are gone
 ##############################################################
 
-my $tmp = two_windows;
+$tmp = two_windows;
 
 cmd 'kill client';
-
-sleep 0.25;
+# We need to re-establish the X11 connection which we just killed :).
+$x = i3test::X11->new;
+sync_with_i3(no_cache => 1);
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers left after killing');
 
index cc100132f8c76723bab38ae14ab42ee17f5fa37d..eb266c2b4d3c59c3c84a4a3483ca6a7b43e8395e 100644 (file)
@@ -1,13 +1,8 @@
 #!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;
+use i3test i3_autostart => 0;
+use X11::XCB qw(PROP_MODE_REPLACE);
 
 ##############################################################
 # 1: test the following directive:
@@ -28,36 +23,19 @@ 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 $window = open_window(name => 'Border window');
 
 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;
+wait_for_unmap $window;
 
-my @content = @{get_ws_content($tmp)};
+@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 {
@@ -78,17 +56,17 @@ sub set_wm_class {
     );
 }
 
-set_wm_class($window->id, 'borderless', 'borderless');
-$window->name('Borderless window');
-$window->map;
-wait_for_map $x;
+$window = open_window(
+    name => 'Borderless window',
+    before_map => sub { set_wm_class($_->id, 'borderless', 'borderless') },
+);
 
 @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;
+wait_for_unmap $window;
 
 @content = @{get_ws_content($tmp)};
 cmp_ok(@content, '==', 0, 'no more nodes');
@@ -111,29 +89,20 @@ $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;
+$window = open_window(name => 'special title');
 
 @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;
+sync_with_i3;
 
 @content = @{get_ws_content($tmp)};
 is($content[0]->{border}, 'none', 'no border');
 
 $window->name('special title');
-sync_with_i3 $x;
+sync_with_i3;
 
 cmd 'border normal';
 
@@ -141,13 +110,13 @@ cmd 'border normal';
 is($content[0]->{border}, 'normal', 'border reset to normal');
 
 $window->name('special borderless title');
-sync_with_i3 $x;
+sync_with_i3;
 
 @content = @{get_ws_content($tmp)};
 is($content[0]->{border}, 'normal', 'still normal border');
 
 $window->unmap;
-wait_for_unmap $x;
+wait_for_unmap $window;
 
 @content = @{get_ws_content($tmp)};
 cmp_ok(@content, '==', 0, 'no more nodes');
@@ -171,22 +140,13 @@ $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;
+$window = open_window(name => 'special mark title');
 
 @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);
+my $other = open_window;
 
 @content = @{get_ws_content($tmp)};
 cmp_ok(@content, '==', 2, 'two nodes');
@@ -215,28 +175,22 @@ $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 = open_window(
+    name => 'usethis',
+    before_map => sub { set_wm_class($_->id, 'borderless', 'borderless') },
 );
 
-$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;
+wait_for_unmap $window;
 $window->destroy;
 
+# give i3 a chance to delete the window from its tree
+sync_with_i3;
+
 @content = @{get_ws_content($tmp)};
 cmp_ok(@content, '==', 0, 'no nodes on this workspace now');
 
@@ -245,7 +199,7 @@ $window->_create;
 set_wm_class($window->id, 'borderless', 'borderless');
 $window->name('notthis');
 $window->map;
-wait_for_map $x;
+wait_for_map $window;
 
 @content = @{get_ws_content($tmp)};
 cmp_ok(@content, '==', 1, 'one node on this workspace now');
@@ -268,19 +222,11 @@ $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;
+$window = open_window(
+    name => 'usethis',
+    before_map => sub { set_wm_class($_->id, 'bar', 'foo') },
+);
 
 @content = @{get_ws_content($tmp)};
 cmp_ok(@content, '==', 1, 'one node on this workspace now');
@@ -303,20 +249,11 @@ $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 = open_window(
+    name => 'usethis',
+    before_map => sub { set_wm_class($_->id, 'bar', 'foo') },
 );
 
-$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');
@@ -340,20 +277,11 @@ $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 = open_window(
+    name => 'usethis',
+    before_map => sub { set_wm_class($_->id, 'bar', 'foo') },
 );
 
-$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');
@@ -377,31 +305,24 @@ $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 = open_window(
+    name => 'usethis',
+    before_map => sub {
+        my ($window) = @_;
+        my $atomname = $x->atom(name => 'WM_WINDOW_ROLE');
+        my $atomtype = $x->atom(name => 'STRING');
+        $x->change_property(
+            PROP_MODE_REPLACE,
+            $window->id,
+            $atomname->id,
+            $atomtype->id,
+            8,
+            length("i3test") + 1,
+            "i3test\x00"
+        );
+    },
 );
 
-$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)');
@@ -426,25 +347,14 @@ $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;
+$window = open_window(name => 'usethis');
 
 @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');
+my $atomname = $x->atom(name => 'WM_WINDOW_ROLE');
+my $atomtype = $x->atom(name => 'STRING');
 $x->change_property(
   PROP_MODE_REPLACE,
   $window->id,
@@ -457,7 +367,7 @@ $x->change_property(
 
 $x->flush;
 
-sync_with_i3 $x;
+sync_with_i3;
 
 @content = @{get_ws_content($tmp)};
 cmp_ok(@content, '==', 1, 'one node on this workspace now');
index 4844f5b547e28508df13c5ca29d677ba771b5949..8d05c05bf239de156f5cbd5225c1185b88a763f5 100644 (file)
@@ -1,15 +1,10 @@
 #!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;
+use i3test i3_autostart => 0;
+use X11::XCB qw(PROP_MODE_REPLACE);
 
 # TODO: move to X11::XCB
 sub set_wm_class {
@@ -30,6 +25,21 @@ sub set_wm_class {
     );
 }
 
+sub open_special {
+    my %args = @_;
+    my $wm_class = delete($args{wm_class}) || 'special';
+    $args{name} //= 'special window';
+
+    # We use dont_map because i3 will not map the window on the current
+    # workspace. Thus, open_window would time out in wait_for_map (2 seconds).
+    my $window = open_window(
+        %args,
+        before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
+        dont_map => 1,
+    );
+    $window->map;
+    return $window;
+}
 
 #####################################################################
 # start a window and see that it does not get assigned with an empty config
@@ -46,18 +56,7 @@ 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;
+my $window = open_special;
 
 ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace');
 
@@ -84,18 +83,7 @@ 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;
+$window = open_special;
 
 ok(@{get_ws_content($tmp)} == 0, 'still no containers');
 ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists');
@@ -124,22 +112,13 @@ $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;
+$window = open_special(dont_map => 1);
+$window->map;
+sync_with_i3;
 
 ok(@{get_ws_content($tmp)} == 0, 'still no containers');
 ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws');
@@ -162,21 +141,10 @@ $pid = launch_with_config($config);
 $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $workspaces = get_workspace_names;
+$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;
+$window = open_special;
 
 my $content = get_ws($tmp);
 ok(@{$content->{nodes}} == 0, 'no tiling cons');
@@ -203,23 +171,12 @@ $pid = launch_with_config($config);
 $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $workspaces = get_workspace_names;
+$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;
+$window = open_special(wm_class => 'SPEcial');
 
-my $content = get_ws($tmp);
+$content = get_ws($tmp);
 ok(@{$content->{nodes}} == 0, 'no tiling cons');
 ok(@{$content->{floating_nodes}} == 1, 'one floating con');
 
@@ -235,6 +192,22 @@ sleep 0.25;
 # ticket #501
 #####################################################################
 
+# Walks /proc to figure out whether a child process of $i3pid with the name
+# 'i3-nagbar' exists.
+sub i3nagbar_running {
+    my ($i3pid) = @_;
+
+    my @procfiles = grep { m,^/proc/[0-9]+$, } </proc/*>;
+    for my $path (@procfiles) {
+        open(my $fh, '<', "$path/stat") or next;
+        my $line = <$fh>;
+        close($fh);
+        my ($comm, $ppid) = ($line =~ /^[0-9]+ \(([^)]+)\) . ([0-9]+)/);
+        return 1 if $ppid == $i3pid && $comm eq 'i3-nagbar';
+    }
+    return 0;
+}
+
 $config = <<EOT;
 # i3 config file (v4)
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
@@ -243,9 +216,11 @@ 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;
+# Ensure that i3-nagbar is running. It should be started pretty quickly, so we
+# busy-loop with a short delay.
+while (!i3nagbar_running($pid)) {
+    sleep 0.05;
+}
 
 $tmp = fresh_workspace;
 
@@ -255,21 +230,11 @@ my @docked = get_dock_clients;
 # 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 = open_special(
     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);
+$content = get_ws($tmp);
 ok(@{$content->{nodes}} == 0, 'no tiling cons');
 ok(@{$content->{floating_nodes}} == 0, 'one floating con');
 @docked = get_dock_clients;
@@ -281,6 +246,4 @@ does_i3_live;
 
 exit_gracefully($pid);
 
-sleep 0.25;
-
 done_testing;
index 6a714124b69e943eb7a3541c681129217d1fca59..ee6c970675b2a4004ef3abc98f2aba97f04a20e9 100644 (file)
@@ -1,15 +1,10 @@
 #!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;
+use i3test i3_autostart => 0;
 
 #####################################################################
 # 1: check that with an empty config, cons are place next to each
@@ -27,13 +22,12 @@ 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);
+my $first = open_window;
+my $second = open_window;
 
 is($x->input_focus, $second->id, 'second window focused');
-ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+my @content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
 isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
 isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
 
@@ -56,13 +50,11 @@ $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 
-$first = open_window($x);
-$second = open_window($x);
-
-sync_with_i3($x);
+$first = open_window;
+$second = open_window;
 
 is($x->input_focus, $second->id, 'second window focused');
-my @content = @{get_ws_content($tmp)};
+@content = @{get_ws_content($tmp)};
 ok(@content == 1, 'one con at workspace level');
 is($content[0]->{layout}, 'stacked', 'layout stacked');
 
@@ -72,8 +64,8 @@ is($content[0]->{layout}, 'stacked', 'layout stacked');
 #####################################################################
 
 cmd 'focus parent';
-my $right_top = open_window($x);
-my $right_bot = open_window($x);
+my $right_top = open_window;
+my $right_bot = open_window;
 
 @content = @{get_ws_content($tmp)};
 is(@content, 2, 'two cons at workspace level after focus parent');
index 1418b402d38bff307f2386b13f34254e643a3b5a..ec6d48212daf83902284c25e347512d1b4fc21bc 100644 (file)
@@ -4,24 +4,19 @@
 # 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);
+open_window;
+open_window;
 
 cmd 'layout stacking';
-sleep 1;
+sync_with_i3;
 
 cmd 'fullscreen';
-sleep 1;
+sync_with_i3;
 
 cmd 'restart';
-sleep 1;
 
 does_i3_live;
 
index 8a990f23e96dc649880f306de312a2438aaf1f80..7949ce665f5ec4cc437c77c6de1bf0e2cc03ddee 100644 (file)
@@ -1,14 +1,9 @@
 #!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;
+use i3test i3_autostart => 0;
 
 #####################################################################
 # 1: test the wrapping behaviour without force_focus_wrapping
@@ -25,13 +20,13 @@ my $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 
-my $first = open_window($x);
-my $second = open_window($x);
+my $first = open_window;
+my $second = open_window;
 
 cmd 'layout tabbed';
 cmd 'focus parent';
 
-my $third = open_window($x);
+my $third = open_window;
 is($x->input_focus, $third->id, 'third window focused');
 
 cmd 'focus left';
@@ -66,15 +61,13 @@ $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 
-$first = open_window($x);
-$second = open_window($x);
+$first = open_window;
+$second = open_window;
 
 cmd 'layout tabbed';
 cmd 'focus parent';
 
-$third = open_window($x);
-
-sync_with_i3($x);
+$third = open_window;
 
 is($x->input_focus, $third->id, 'third window focused');
 
index 940afc3c427c5caf32324967503f5b5c79b85cc7..e791bb015cd10315cf306982c5189ab56bda7dc7 100644 (file)
@@ -1,11 +1,10 @@
 #!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 i3test i3_autostart => 0;
 use Cwd qw(abs_path);
 use File::Temp qw(tempfile tempdir);
 use v5.10;
@@ -115,14 +114,15 @@ 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');
+$input = <<EOT;
+    new_window bb
+    new_window bn
+    new_window bp
+EOT
+$output = migrate_config($input);
+like($output->[0], qr|^new_window none$|, 'new_window bb changed');
+like($output->[1], qr|^new_window normal$|, 'new_window bn changed');
+like($output->[2], qr|^new_window 1pixel$|, 'new_window bp changed');
 
 #####################################################################
 # check that some commands remain untouched
@@ -233,56 +233,59 @@ ok(line_exists($output, qr|^bindsym Mod1\+s \[con_mark="foo"\] focus$|), 'goto r
 # 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');
+$input = <<EOT;
+bindsym Mod1+f focus 3
+bindsym Mod1+f focus floating
+bindsym Mod1+f focus tiling
+bindsym Mod1+f focus ft
+EOT
 
-$output = migrate_config('bindsym Mod1+f focus ft');
-ok(line_exists($output, qr|^bindsym Mod1\+f focus mode_toggle$|), 'focus ft changed');
+$output = migrate_config($input);
+like($output->[0], qr|^#.*focus.*obsolete.*focus 3$|, 'focus [number] gone');
+like($output->[1], qr|^bindsym Mod1\+f focus floating$|, 'focus floating unchanged');
+like($output->[2], qr|^bindsym Mod1\+f focus tiling$|, 'focus tiling unchanged');
+like($output->[3], 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');
+$input = <<EOT;
+bindsym Mod1+f resize left +10
+bindsym Mod1+f resize top -20
+bindsym Mod1+f resize right -20
+bindsym Mod1+f resize bottom +23
+bindsym Mod1+f resize          left    \t +10
+EOT
 
-$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');
+$output = migrate_config($input);
+like($output->[0], qr|^bindsym Mod1\+f resize grow left 10 px$|, 'resize left changed');
+like($output->[1], qr|^bindsym Mod1\+f resize shrink up 20 px$|, 'resize top changed');
+like($output->[2], qr|^bindsym Mod1\+f resize shrink right 20 px$|, 'resize right changed');
+like($output->[3], 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');
+like($output->[4], 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');
+$input = <<EOT;
+bindsym Mod1+f jump 3
+bindsym Mod1+f jump 3 4 5
+bindsym Mod1+f jump "XTerm"
+bindsym Mod1+f jump "XTerm/irssi"
+EOT
 
-$output = migrate_config('bindsym Mod1+f jump "XTerm/irssi"');
-ok(line_exists($output, qr|^bindsym Mod1\+f \[class="XTerm" title="irssi"\] focus$|), 'jump changed');
+$output = migrate_config($input);
+like($output->[0], qr|^#.*obsolete.*jump 3$|, 'jump to workspace removed');
+like($output->[1], qr|^#.*obsolete.*jump 3 4 5$|, 'jump to workspace + col/row removed');
+like($output->[2], qr|^bindsym Mod1\+f \[class="XTerm"\] focus$|, 'jump changed');
+like($output->[3], qr|^bindsym Mod1\+f \[class="XTerm" title="irssi"\] focus$|, 'jump changed');
 
 #####################################################################
 # check whether workspace commands are handled correctly
@@ -316,7 +319,7 @@ $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');
+ok(line_exists($output, qr|^bindsym Mod1\+3 move container to workspace 3|), 'move workspace changed');
 
 $input = <<EOT;
     workspace 3 work
@@ -324,7 +327,7 @@ $input = <<EOT;
 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');
+ok(line_exists($output, qr|^bindsym Mod1\+3 move container to workspace work|), 'move to named workspace in bindings');
 
 #####################################################################
 # check whether an i3bar call is added if the workspace bar bar was enabled
index 4493bf8332f8e23226a312fbf725099619679765..42a444592bccb4fef7f90e7b3bea9d5d3f3e3830 100644 (file)
@@ -1,14 +1,8 @@
 #!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;
+use i3test i3_autostart => 0;
 
 ##############################################################
 # 1: i3 should start with workspace '1'
@@ -22,7 +16,7 @@ EOT
 my $pid = launch_with_config($config);
 
 my @names = @{get_workspace_names()};
-cmp_deeply(\@names, [ '1' ], 'i3 starts on workspace 1 without any configuration');
+is_deeply(\@names, [ '1' ], 'i3 starts on workspace 1 without any configuration');
 
 exit_gracefully($pid);
 
@@ -39,8 +33,8 @@ EOT
 
 $pid = launch_with_config($config);
 
-my @names = @{get_workspace_names()};
-cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
+@names = @{get_workspace_names()};
+is_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
 
 exit_gracefully($pid);
 
@@ -57,8 +51,8 @@ EOT
 
 $pid = launch_with_config($config);
 
-my @names = @{get_workspace_names()};
-cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
+@names = @{get_workspace_names()};
+is_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
 
 exit_gracefully($pid);
 
index 007d5a6d507ee1645873900eed41434a97b8fb90..e8964d307b7a8d3a7e57d208558e885d2caebf26 100644 (file)
@@ -16,7 +16,7 @@ sub get_marks {
 my $tmp = fresh_workspace;
 
 my $marks = get_marks();
-cmp_deeply($marks, [], 'no marks set so far');
+is_deeply($marks, [], 'no marks set so far');
 
 ##############################################################
 # 2: check that setting a mark is reflected in the get_marks reply
@@ -25,7 +25,7 @@ cmp_deeply($marks, [], 'no marks set so far');
 cmd 'open';
 cmd 'mark foo';
 
-cmp_deeply(get_marks(), [ 'foo' ], 'mark foo set');
+is_deeply(get_marks(), [ 'foo' ], 'mark foo set');
 
 ##############################################################
 # 3: check that the mark is gone after killing the container
@@ -33,6 +33,6 @@ cmp_deeply(get_marks(), [ 'foo' ], 'mark foo set');
 
 cmd 'kill';
 
-cmp_deeply(get_marks(), [ ], 'mark gone');
+is_deeply(get_marks(), [ ], 'mark gone');
 
 done_testing;
index 70414af398264f56752025861277a58bd3497549..22306db60aa9bb11d6323bdb00a8c79e7efee7f2 100644 (file)
@@ -1,16 +1,11 @@
 #!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;
+use i3test i3_autostart => 0;
+use X11::XCB qw(PROP_MODE_REPLACE);
 
 # TODO: move to X11::XCB
 sub set_wm_class {
@@ -31,6 +26,21 @@ sub set_wm_class {
     );
 }
 
+sub open_special {
+    my %args = @_;
+    my $wm_class = delete($args{wm_class}) || 'special';
+    $args{name} //= 'special window';
+
+    # We use dont_map because i3 will not map the window on the current
+    # workspace. Thus, open_window would time out in wait_for_map (2 seconds).
+    my $window = open_window(
+        %args,
+        before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
+        dont_map => 1,
+    );
+    $window->map;
+    return $window;
+}
 
 #####################################################################
 # start a window and see that it does not get assigned with an empty config
@@ -49,18 +59,7 @@ 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;
-
+my $window = open_special;
 
 ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
 ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
@@ -70,20 +69,10 @@ 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 = open_special(
     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');
index cc1f5c90298137a0d3dc10928f0229c567061d9f..2586657b8f6b97f47fde6e0e3531d30b0a2a9f3f 100644 (file)
@@ -1,13 +1,10 @@
 #!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;
+use i3test i3_autostart => 0;
 
 #####################################################################
 # 1: check that new windows start with 'normal' border unless configured
@@ -25,7 +22,7 @@ my $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 
-my $first = open_window($x);
+my $first = open_window;
 
 my @content = @{get_ws_content($tmp)};
 ok(@content == 1, 'one container opened');
@@ -51,7 +48,7 @@ $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 
-$first = open_window($x);
+$first = open_window;
 
 @content = @{get_ws_content($tmp)};
 ok(@content == 1, 'one container opened');
@@ -75,7 +72,7 @@ $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 
-$first = open_floating_window($x);
+$first = open_floating_window;
 
 my $wscontent = get_ws($tmp);
 my @floating = @{$wscontent->{floating_nodes}};
@@ -103,7 +100,7 @@ $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 
-$first = open_floating_window($x);
+$first = open_floating_window;
 
 $wscontent = get_ws($tmp);
 @floating = @{$wscontent->{floating_nodes}};
index 55df41429f75705e7b144be60f2e81ce91a57857..3a4dbc81f3ed5d4a305f725acaab5cc516c3bacd 100644 (file)
@@ -8,7 +8,6 @@ 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
@@ -98,13 +97,13 @@ 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 });
+my $win = open_window({ 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);
+sync_with_i3;
 
 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');
@@ -113,12 +112,12 @@ 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 });
+my $leader = open_window({ dont_map => 1 });
 mark_window($leader->id);
 
-$win = open_window($x, { dont_map => 1, client_leader => $leader });
+$win = open_window({ dont_map => 1, client_leader => $leader });
 $win->map;
-sync_with_i3($x);
+sync_with_i3;
 
 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');
@@ -129,9 +128,9 @@ is(@{get_ws_content($first_ws)}, 2, 'two containers on the first workspace');
 ######################################################################
 
 complete_startup();
-sync_with_i3($x);
+sync_with_i3;
 
-my $otherwin = open_window($x);
+my $otherwin = open_window;
 is(@{get_ws_content($second_ws)}, 1, 'one container on the second workspace');
 
 ######################################################################
index 48ea948ddba518f2276d7866eab5ca85417865f9..f01a2bc7a90d3e2be0c116e0e7632f919f38f805 100644 (file)
@@ -1,15 +1,10 @@
 #!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;
+use i3test i3_autostart => 0;
 
 my $config = <<EOT;
 # i3 config file (v4)
index 0bf287b72af7dd9cd1fbb40003c7b8eb0cfa6529..3caa6696fe1b4e7fe51e1aa84ff51291a69d403d 100644 (file)
@@ -1,11 +1,10 @@
 #!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;
+use i3test i3_autostart => 0;
 
 #####################################################################
 # test a config without any bars
@@ -93,10 +92,10 @@ bar {
         background #ff0000
         statusline   #00ff00
 
-        focused_workspace   #ffffff #285577
-        active_workspace    #888888 #222222
-        inactive_workspace  #888888 #222222
-        urgent_workspace    #ffffff #900000
+        focused_workspace   #4c7899 #285577 #ffffff
+        active_workspace    #333333 #222222 #888888
+        inactive_workspace  #333333 #222222 #888888
+        urgent_workspace    #2f343a #900000 #ffffff
     }
 }
 EOT
@@ -123,12 +122,16 @@ is_deeply($bar_config->{colors},
     {
         background => '#ff0000',
         statusline => '#00ff00',
+        focused_workspace_border => '#4c7899',
         focused_workspace_text => '#ffffff',
         focused_workspace_bg => '#285577',
+        active_workspace_border => '#333333',
         active_workspace_text => '#888888',
         active_workspace_bg => '#222222',
+        inactive_workspace_border => '#333333',
         inactive_workspace_text => '#888888',
         inactive_workspace_bg => '#222222',
+        urgent_workspace_border => '#2f343a',
         urgent_workspace_text => '#ffffff',
         urgent_workspace_bg => '#900000',
     }, 'colors ok');
@@ -205,4 +208,77 @@ is($bar_config->{colors}->{background}, '#000000', 'background color ok');
 
 exit_gracefully($pid);
 
+#####################################################################
+# verify that the old syntax still works
+#####################################################################
+
+$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;
+
+cmd 'nop yeah';
+$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',
+    }, '(old) colors ok');
+
+exit_gracefully($pid);
+
+
 done_testing;
diff --git a/testcases/t/180-fd-leaks.t b/testcases/t/180-fd-leaks.t
new file mode 100644 (file)
index 0000000..487803c
--- /dev/null
@@ -0,0 +1,49 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Verifies that i3 does not leak any file descriptors in 'exec'.
+#
+use i3test;
+use POSIX qw(mkfifo);
+use File::Temp qw(:POSIX tempfile);
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = tmpnam();
+mkfifo($tmp, 0600) or die "Could not create FIFO in $tmp";
+my ($outfh, $outname) = tempfile('/tmp/i3-ls-output.XXXXXX', UNLINK => 1);
+
+cmd qq|exec ls -l /proc/self/fd >$outname && echo done >$tmp|;
+
+open(my $fh, '<', $tmp);
+# Block on the FIFO, this will return exactly when the command is done.
+<$fh>;
+close($fh);
+unlink($tmp);
+
+# Get the ls /proc/self/fd output
+my $output;
+{
+    local $/;
+    $output = <$outfh>;
+}
+close($outfh);
+
+# Split lines, keep only those which are symlinks.
+my @lines = grep { /->/ } split("\n", $output);
+
+my %fds = map { /([0-9]+) -> (.+)$/; ($1, $2) } @lines;
+
+# Filter out 0, 1, 2 (stdin, stdout, stderr).
+delete $fds{0};
+delete $fds{1};
+delete $fds{2};
+
+# Filter out the fd which is caused by ls calling readdir().
+for my $fd (keys %fds) {
+    delete $fds{$fd} if $fds{$fd} =~ m,^/proc/\d+/fd$,;
+}
+
+is(scalar keys %fds, 0, 'No file descriptors leaked');
+
+done_testing;
diff --git a/testcases/t/181-regress-float-border.t b/testcases/t/181-regress-float-border.t
new file mode 100644 (file)
index 0000000..f77f780
--- /dev/null
@@ -0,0 +1,26 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test: Changing border style should not have an impact on the size
+# (geometry) of the child window. See ticket http://bugs.i3wm.org/561
+# Wrong behaviour manifested itself up to (including) commit
+# d805d1bbeaf89e11f67c981f94c9f55bbb4b89d9
+#
+use i3test;
+use Data::Dumper;
+
+fresh_workspace;
+
+my $win = open_floating_window(rect => [10, 10, 200, 100]);
+
+my $geometry = $win->rect;
+is($geometry->{width}, 200, 'width correct');
+is($geometry->{height}, 100, 'height correct');
+
+cmd 'border 1pixel';
+
+$geometry = $win->rect;
+is($geometry->{width}, 200, 'width correct');
+is($geometry->{height}, 100, 'height correct');
+
+done_testing;
diff --git a/testcases/t/183-config-variables.t b/testcases/t/183-config-variables.t
new file mode 100644 (file)
index 0000000..1da25a6
--- /dev/null
@@ -0,0 +1,72 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Checks that variables are parsed correctly by using for_window rules with
+# variables in it.
+#
+
+use i3test i3_autostart => 0;
+
+# starts i3 with the given config, opens a window, returns its border style
+sub launch_get_border {
+    my ($config) = @_;
+
+    my $pid = launch_with_config($config);
+
+    my $i3 = i3(get_socket_path(0));
+    my $tmp = fresh_workspace;
+
+    my $window = open_window(name => 'special title');
+
+    my @content = @{get_ws_content($tmp)};
+    cmp_ok(@content, '==', 1, 'one node on this workspace now');
+    my $border = $content[0]->{border};
+
+    exit_gracefully($pid);
+
+    return $border;
+}
+
+#####################################################################
+# test thet windows get the default border
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+is(launch_get_border($config), 'normal', 'normal border');
+
+#####################################################################
+# now use a variable and for_window
+#####################################################################
+
+$config = <<'EOT';
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+set $vartest special title
+for_window [title="$vartest"] border none
+EOT
+
+is(launch_get_border($config), 'none', 'no border');
+
+#####################################################################
+# check that whitespaces and tabs are ignored
+#####################################################################
+
+$config = <<'EOT';
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+set               $vartest             special title
+for_window [title="$vartest"] border none
+EOT
+
+is(launch_get_border($config), 'none', 'no border');
+
+
+
+done_testing;
+
diff --git a/testcases/t/185-scratchpad.t b/testcases/t/185-scratchpad.t
new file mode 100644 (file)
index 0000000..06debab
--- /dev/null
@@ -0,0 +1,349 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests for the scratchpad functionality.
+#
+use i3test;
+use List::Util qw(first);
+
+my $i3 = i3(get_socket_path());
+my $tmp = fresh_workspace;
+
+################################################################################
+# 1: Verify that the __i3 output contains the __i3_scratch workspace and that
+# it’s empty initially. Also, __i3 should not show up in GET_OUTPUTS so that
+# tools like i3bar will not handle it. Similarly, __i3_scratch should not show
+# up in GET_WORKSPACES. After all, you should not be able to switch to it.
+################################################################################
+
+my $tree = $i3->get_tree->recv;
+is($tree->{name}, 'root', 'root node is the first thing we get');
+
+my @__i3 = grep { $_->{name} eq '__i3' } @{$tree->{nodes}};
+is(scalar @__i3, 1, 'output __i3 found');
+
+my $content = first { $_->{type} == 2 } @{$__i3[0]->{nodes}};
+my @workspaces = @{$content->{nodes}};
+my @workspace_names = map { $_->{name} } @workspaces;
+ok('__i3_scratch' ~~ @workspace_names, '__i3_scratch workspace found');
+
+my $get_outputs = $i3->get_outputs->recv;
+my $get_ws = $i3->get_workspaces->recv;
+my @output_names = map { $_->{name} } @$get_outputs;
+my @ws_names = map { $_->{name} } @$get_ws;
+
+ok(!('__i3' ~~ @output_names), '__i3 not in GET_OUTPUTS');
+ok(!('__i3_scratch' ~~ @ws_names), '__i3_scratch ws not in GET_WORKSPACES');
+
+################################################################################
+# 2: Verify that you cannot switch to the __i3_scratch workspace and moving
+# windows to __i3_scratch does not work (users should be aware of the different
+# behavior and acknowledge that by using the scratchpad commands).
+################################################################################
+
+# Try focusing the workspace.
+my $__i3_scratch = get_ws('__i3_scratch');
+is($__i3_scratch->{focused}, 0, '__i3_scratch ws not focused');
+
+cmd 'workspace __i3_scratch';
+
+$__i3_scratch = get_ws('__i3_scratch');
+is($__i3_scratch->{focused}, 0, '__i3_scratch ws still not focused');
+
+
+# Try moving a window to it.
+is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');
+
+my $window = open_window;
+cmd 'move workspace __i3_scratch';
+
+$__i3_scratch = get_ws('__i3_scratch');
+is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');
+
+
+# Try moving the window with the 'output <direction>' command.
+# We hardcode output left since the pseudo-output will be initialized before
+# every other output, so it will always be the first one.
+cmd 'move output left';
+
+$__i3_scratch = get_ws('__i3_scratch');
+is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');
+
+
+# Try moving the window with the 'output <name>' command.
+cmd 'move output __i3';
+
+$__i3_scratch = get_ws('__i3_scratch');
+is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');
+
+
+################################################################################
+# 3: Verify that 'scratchpad toggle' sends a window to the __i3_scratch
+# workspace and sets the scratchpad flag to SCRATCHPAD_FRESH. The window’s size
+# and position will be changed (once!) on the next 'scratchpad show' and the
+# flag will be changed to SCRATCHPAD_CHANGED.
+################################################################################
+
+my ($nodes, $focus) = get_ws_content($tmp);
+is(scalar @$nodes, 1, 'precisely one window on current ws');
+is($nodes->[0]->{scratchpad_state}, 'none', 'scratchpad_state none');
+
+cmd 'move scratchpad';
+
+$__i3_scratch = get_ws('__i3_scratch');
+my @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
+is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
+($nodes, $focus) = get_ws_content($tmp);
+is(scalar @$nodes, 0, 'no window on current ws anymore');
+
+is($scratch_nodes[0]->{scratchpad_state}, 'fresh', 'scratchpad_state fresh');
+
+$tree = $i3->get_tree->recv;
+my $__i3 = first { $_->{name} eq '__i3' } @{$tree->{nodes}};
+isnt($tree->{focus}->[0], $__i3->{id}, '__i3 output not focused');
+
+$get_outputs = $i3->get_outputs->recv;
+$get_ws = $i3->get_workspaces->recv;
+@output_names = map { $_->{name} } @$get_outputs;
+@ws_names = map { $_->{name} } @$get_ws;
+
+ok(!('__i3' ~~ @output_names), '__i3 not in GET_OUTPUTS');
+ok(!('__i3_scratch' ~~ @ws_names), '__i3_scratch ws not in GET_WORKSPACES');
+
+################################################################################
+# 4: Verify that 'scratchpad show' makes the window visible.
+################################################################################
+
+# Open another window so that we can check if focus is on the scratchpad window
+# after showing it.
+my $second_window = open_window;
+my $old_focus = get_focused($tmp);
+
+cmd 'scratchpad show';
+
+isnt(get_focused($tmp), $old_focus, 'focus changed');
+
+$__i3_scratch = get_ws('__i3_scratch');
+@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
+is(scalar @scratch_nodes, 0, '__i3_scratch is now empty');
+
+my $ws = get_ws($tmp);
+my $output = $tree->{nodes}->[1];
+my $scratchrect = $ws->{floating_nodes}->[0]->{rect};
+my $outputrect = $output->{rect};
+
+is($scratchrect->{width}, $outputrect->{width} * 0.5, 'scratch width is 50%');
+is($scratchrect->{height}, $outputrect->{height} * 0.75, 'scratch height is 75%');
+is($scratchrect->{x},
+   ($outputrect->{width} / 2) - ($scratchrect->{width} / 2),
+   'scratch window centered horizontally');
+is($scratchrect->{y},
+   ($outputrect->{height} / 2 ) - ($scratchrect->{height} / 2),
+   'scratch window centered vertically');
+
+################################################################################
+# 5: Another 'scratchpad show' should make that window go to the scratchpad
+# again.
+################################################################################
+
+cmd 'scratchpad show';
+
+$__i3_scratch = get_ws('__i3_scratch');
+@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
+is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
+
+is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed');
+
+################################################################################
+# 6: Verify that repeated 'scratchpad show' cycle through the stack, that is,
+# toggling a visible window should insert it at the bottom of the stack of the
+# __i3_scratch workspace.
+################################################################################
+
+my $third_window = open_window(name => 'scratch-match');
+cmd 'move scratchpad';
+
+$__i3_scratch = get_ws('__i3_scratch');
+@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
+is(scalar @scratch_nodes, 2, '__i3_scratch contains both windows');
+
+is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'changed window first');
+is($scratch_nodes[1]->{scratchpad_state}, 'fresh', 'fresh window is second');
+
+my $changed_id = $scratch_nodes[0]->{nodes}->[0]->{id};
+my $fresh_id = $scratch_nodes[1]->{nodes}->[0]->{id};
+is($scratch_nodes[0]->{id}, $__i3_scratch->{focus}->[0], 'changed window first');
+is($scratch_nodes[1]->{id}, $__i3_scratch->{focus}->[1], 'fresh window second');
+
+# Repeatedly use 'scratchpad show' and check that the windows are different.
+cmd 'scratchpad show';
+
+is(get_focused($tmp), $changed_id, 'focus changed');
+
+$ws = get_ws($tmp);
+$scratchrect = $ws->{floating_nodes}->[0]->{rect};
+is($scratchrect->{width}, $outputrect->{width} * 0.5, 'scratch width is 50%');
+is($scratchrect->{height}, $outputrect->{height} * 0.75, 'scratch height is 75%');
+is($scratchrect->{x},
+   ($outputrect->{width} / 2) - ($scratchrect->{width} / 2),
+   'scratch window centered horizontally');
+is($scratchrect->{y},
+   ($outputrect->{height} / 2 ) - ($scratchrect->{height} / 2),
+   'scratch window centered vertically');
+
+cmd 'scratchpad show';
+
+isnt(get_focused($tmp), $changed_id, 'focus changed');
+
+cmd 'scratchpad show';
+
+is(get_focused($tmp), $fresh_id, 'focus changed');
+
+cmd 'scratchpad show';
+
+isnt(get_focused($tmp), $fresh_id, 'focus changed');
+
+################################################################################
+# 7: Verify that using scratchpad show with criteria works as expected:
+# When matching a scratchpad window which is visible, it should hide it.
+# When matching a scratchpad window which is on __i3_scratch, it should show it.
+# When matching a non-scratchpad window, it should be a no-op.
+################################################################################
+
+# Verify that using 'scratchpad show' without any matching windows is a no-op.
+$old_focus = get_focused($tmp);
+
+cmd '[title="nomatch"] scratchpad show';
+
+is(get_focused($tmp), $old_focus, 'non-matching criteria have no effect');
+
+# Verify that we can use criteria to show a scratchpad window.
+cmd '[title="scratch-match"] scratchpad show';
+
+my $scratch_focus = get_focused($tmp);
+isnt($scratch_focus, $old_focus, 'matching criteria works');
+
+cmd '[title="scratch-match"] scratchpad show';
+
+isnt(get_focused($tmp), $scratch_focus, 'matching criteria works');
+is(get_focused($tmp), $old_focus, 'focus restored');
+
+# Verify that we cannot use criteria to show a non-scratchpad window.
+my $tmp2 = fresh_workspace;
+my $non_scratch_window = open_window(name => 'non-scratch');
+cmd "workspace $tmp";
+is(get_focused($tmp), $old_focus, 'focus still ok');
+cmd '[title="non-match"] scratchpad show';
+is(get_focused($tmp), $old_focus, 'focus unchanged');
+
+################################################################################
+# 8: Show it, move it around, hide it. Verify that the position is retained
+# when showing it again.
+################################################################################
+
+cmd '[title="scratch-match"] scratchpad show';
+
+isnt(get_focused($tmp), $old_focus, 'scratchpad window shown');
+
+my $oldrect = get_ws($tmp)->{floating_nodes}->[0]->{rect};
+
+cmd 'move left';
+
+$scratchrect = get_ws($tmp)->{floating_nodes}->[0]->{rect};
+isnt($scratchrect->{x}, $oldrect->{x}, 'x position changed');
+$oldrect = $scratchrect;
+
+# hide it, then show it again
+cmd '[title="scratch-match"] scratchpad show';
+cmd '[title="scratch-match"] scratchpad show';
+
+# verify the position is still the same
+$scratchrect = get_ws($tmp)->{floating_nodes}->[0]->{rect};
+
+is_deeply($scratchrect, $oldrect, 'position/size the same');
+
+# hide it again for the next test
+cmd '[title="scratch-match"] scratchpad show';
+
+is(get_focused($tmp), $old_focus, 'scratchpad window hidden');
+
+is(scalar @{get_ws($tmp)->{nodes}}, 1, 'precisely one window on current ws');
+
+################################################################################
+# 9: restart i3 and verify that the scratchpad show still works
+################################################################################
+
+$__i3_scratch = get_ws('__i3_scratch');
+my $old_nodes = scalar @{$__i3_scratch->{nodes}};
+my $old_floating_nodes = scalar @{$__i3_scratch->{floating_nodes}};
+
+cmd 'restart';
+
+does_i3_live;
+
+$__i3_scratch = get_ws('__i3_scratch');
+is(scalar @{$__i3_scratch->{nodes}}, $old_nodes, "number of nodes matches ($old_nodes)");
+is(scalar @{$__i3_scratch->{floating_nodes}}, $old_floating_nodes, "number of floating nodes matches ($old_floating_nodes)");
+
+is(scalar @{get_ws($tmp)->{nodes}}, 1, 'still precisely one window on current ws');
+is(scalar @{get_ws($tmp)->{floating_nodes}}, 0, 'still no floating windows on current ws');
+
+# verify that we can display the scratchpad window
+cmd '[title="scratch-match"] scratchpad show';
+
+$ws = get_ws($tmp);
+is(scalar @{$ws->{nodes}}, 1, 'still precisely one window on current ws');
+is(scalar @{$ws->{floating_nodes}}, 1, 'precisely one floating windows on current ws');
+is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'changed', 'scratchpad_state is "changed"');
+
+################################################################################
+# 10: on an empty workspace, ensure the 'move scratchpad' command does nothing
+################################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'move scratchpad';
+
+does_i3_live;
+
+################################################################################
+# 11: focus a workspace and move all of its children to the scratchpad area
+################################################################################
+
+$tmp = fresh_workspace;
+
+my $first = open_window;
+my $second = open_window;
+
+cmd 'focus parent';
+cmd 'move scratchpad';
+
+does_i3_live;
+
+$ws = get_ws($tmp);
+is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
+is(scalar @{$ws->{floating_nodes}}, 0, 'no floating windows on ws');
+
+# show the first window.
+cmd 'scratchpad show';
+
+$ws = get_ws($tmp);
+is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
+is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
+
+$old_focus = get_focused($tmp);
+
+cmd 'scratchpad show';
+
+# show the second window.
+cmd 'scratchpad show';
+
+$ws = get_ws($tmp);
+is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
+is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
+
+isnt(get_focused($tmp), $old_focus, 'focus changed');
+
+# TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint
+
+done_testing;
diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t
new file mode 100644 (file)
index 0000000..b0e543b
--- /dev/null
@@ -0,0 +1,157 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests the standalone parser binary to see if it calls the right code when
+# confronted with various commands, if it prints proper error messages for
+# wrong commands and if it terminates in every case.
+#
+use i3test i3_autostart => 0;
+
+sub parser_calls {
+    my ($command) = @_;
+
+    # TODO: use a timeout, so that we can error out if it doesn’t terminate
+    # TODO: better way of passing arguments
+    my $stdout = qx(../test.commands_parser '$command' 2>&-);
+
+    # Filter out all debugging output.
+    my @lines = split("\n", $stdout);
+    @lines = grep { not /^# / } @lines;
+
+    # The criteria management calls are irrelevant and not what we want to test
+    # in the first place.
+    @lines = grep { !(/cmd_criteria_init()/ || /cmd_criteria_match_windows/) } @lines;
+    return join("\n", @lines);
+}
+
+################################################################################
+# 1: First that the parser properly recognizes commands which are ok.
+################################################################################
+
+# The first call has only a single command, the following ones are consolidated
+# for performance.
+is(parser_calls('move workspace 3'),
+   'cmd_move_con_to_workspace_name(3)',
+   'single number (move workspace 3) ok');
+
+is(parser_calls(
+   'move to workspace 3; ' .
+   'move window to workspace 3; ' .
+   'move container to workspace 3; ' .
+   'move workspace foobar; ' .
+   'move workspace 3: foobar; ' .
+   'move workspace "3: foobar"; ' .
+   'move workspace "3: foobar, baz"; '),
+   "cmd_move_con_to_workspace_name(3)\n" .
+   "cmd_move_con_to_workspace_name(3)\n" .
+   "cmd_move_con_to_workspace_name(3)\n" .
+   "cmd_move_con_to_workspace_name(foobar)\n" .
+   "cmd_move_con_to_workspace_name(3: foobar)\n" .
+   "cmd_move_con_to_workspace_name(3: foobar)\n" .
+   "cmd_move_con_to_workspace_name(3: foobar, baz)",
+   'move ok');
+
+is(parser_calls('move workspace 3: foobar, nop foo'),
+   "cmd_move_con_to_workspace_name(3: foobar)\n" .
+   "cmd_nop(foo)",
+   'multiple ops (move workspace 3: foobar, nop foo) ok');
+
+is(parser_calls(
+   'exec i3-sensible-terminal; ' .
+   'exec --no-startup-id i3-sensible-terminal'),
+   "cmd_exec((null), i3-sensible-terminal)\n" .
+   "cmd_exec(--no-startup-id, i3-sensible-terminal)",
+   'exec ok');
+
+is(parser_calls(
+   'resize shrink left; ' .
+   'resize shrink left 25 px; ' .
+   'resize shrink left 25 px or 33 ppt; ' .
+   'resize shrink left 25'),
+   "cmd_resize(shrink, left, 10, 10)\n" .
+   "cmd_resize(shrink, left, 25, 10)\n" .
+   "cmd_resize(shrink, left, 25, 33)\n" .
+   "cmd_resize(shrink, left, 25, 10)",
+   'simple resize ok');
+
+is(parser_calls('resize shrink left 25 px or 33 ppt,'),
+   'cmd_resize(shrink, left, 25, 33)',
+   'trailing comma resize ok');
+
+is(parser_calls('resize shrink left 25 px or 33 ppt;'),
+   'cmd_resize(shrink, left, 25, 33)',
+   'trailing semicolon resize ok');
+
+is(parser_calls('[con_mark=yay] focus'),
+   "cmd_criteria_add(con_mark, yay)\n" .
+   "cmd_focus()",
+   'criteria focus ok');
+
+is(parser_calls("[con_mark=yay con_mark=bar] focus"),
+   "cmd_criteria_add(con_mark, yay)\n" .
+   "cmd_criteria_add(con_mark, bar)\n" .
+   "cmd_focus()",
+   'criteria focus ok');
+
+is(parser_calls("[con_mark=yay\tcon_mark=bar] focus"),
+   "cmd_criteria_add(con_mark, yay)\n" .
+   "cmd_criteria_add(con_mark, bar)\n" .
+   "cmd_focus()",
+   'criteria focus ok');
+
+is(parser_calls("[con_mark=yay\tcon_mark=bar]\tfocus"),
+   "cmd_criteria_add(con_mark, yay)\n" .
+   "cmd_criteria_add(con_mark, bar)\n" .
+   "cmd_focus()",
+   'criteria focus ok');
+
+is(parser_calls('[con_mark="yay"] focus'),
+   "cmd_criteria_add(con_mark, yay)\n" .
+   "cmd_focus()",
+   'quoted criteria focus ok');
+
+# Make sure trailing whitespace is stripped off: While this is not an issue for
+# commands being parsed due to the configuration, people might send IPC
+# commands with leading or trailing newlines.
+is(parser_calls("workspace test\n"),
+   'cmd_workspace_name(test)',
+   'trailing whitespace stripped off ok');
+
+is(parser_calls("\nworkspace test"),
+   'cmd_workspace_name(test)',
+   'trailing whitespace stripped off ok');
+
+################################################################################
+# 2: Verify that the parser spits out the right error message on commands which
+# are not ok.
+################################################################################
+
+is(parser_calls('unknown_literal'),
+   "Expected one of these tokens: <end>, '[', 'move', 'exec', 'exit', 'restart', 'reload', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'resize', 'nop', 'scratchpad', 'mode'\n" .
+   "Your command: unknown_literal\n" .
+   "              ^^^^^^^^^^^^^^^",
+   'error for unknown literal ok');
+
+is(parser_calls('move something to somewhere'),
+   "Expected one of these tokens: 'window', 'container', 'to', 'workspace', 'output', 'scratchpad', 'left', 'right', 'up', 'down'\n" .
+   "Your command: move something to somewhere\n" .
+   "                   ^^^^^^^^^^^^^^^^^^^^^^",
+   'error for unknown literal ok');
+
+################################################################################
+# 3: Verify that escaping of double quotes works correctly
+################################################################################
+
+is(parser_calls('workspace "foo"'),
+   'cmd_workspace_name(foo)',
+   'Command with simple double quotes ok');
+
+is(parser_calls('workspace "foo'),
+   'cmd_workspace_name(foo)',
+   'Command without ending double quotes ok');
+
+is(parser_calls('workspace "foo \"bar"'),
+   'cmd_workspace_name(foo "bar)',
+   'Command with escaped double quotes ok');
+
+done_testing;
diff --git a/testcases/t/188-regress-focus-restart.t b/testcases/t/188-regress-focus-restart.t
new file mode 100644 (file)
index 0000000..1de9f36
--- /dev/null
@@ -0,0 +1,33 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Verifies that i3 survives inplace restarts with fullscreen containers
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+open_window(name => 'first');
+open_window(name => 'second');
+
+cmd 'focus left';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+is(scalar @$nodes, 2, 'two tiling nodes on workspace');
+is($nodes->[0]->{name}, 'first', 'first node name ok');
+is($nodes->[1]->{name}, 'second', 'second node name ok');
+is($focus->[0], $nodes->[0]->{id}, 'first node focused');
+is($focus->[1], $nodes->[1]->{id}, 'second node second in focus stack');
+
+cmd 'restart';
+
+does_i3_live;
+
+($nodes, $focus) = get_ws_content($tmp);
+is(scalar @$nodes, 2, 'still two tiling nodes on workspace');
+is($nodes->[0]->{name}, 'first', 'first node name ok');
+is($nodes->[1]->{name}, 'second', 'second node name ok');
+is($focus->[0], $nodes->[0]->{id}, 'first node focused');
+is($focus->[1], $nodes->[1]->{id}, 'second node second in focus stack');
+
+done_testing;
diff --git a/testcases/t/189-floating-constraints.t b/testcases/t/189-floating-constraints.t
new file mode 100644 (file)
index 0000000..9b6fb15
--- /dev/null
@@ -0,0 +1,118 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests the floating_{minimum,maximum}_size config options.
+#
+# Note that the minimum floating window size is already verified in
+# t/005-floating.t.
+#
+
+use i3test i3_autostart => 0;
+
+################################################################################
+# 1: check floating_minimum_size (with non-default limits)
+################################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+# Test with different dimensions than the i3 default.
+floating_minimum_size 60 x 40
+EOT
+
+my $pid = launch_with_config($config);
+
+my $window = open_floating_window(rect => [ 0, 0, 20, 20 ]);
+my $rect = $window->rect;
+
+is($rect->{width}, 60, 'width = 60');
+is($rect->{height}, 40, 'height = 40');
+
+exit_gracefully($pid);
+
+################################################################################
+# 2: check floating_minimum_size with -1 (unlimited)
+################################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+floating_minimum_size -1 x -1
+EOT
+
+$pid = launch_with_config($config);
+
+cmd 'nop MEH';
+$window = open_floating_window(rect => [ 0, 0, 50, 40 ]);
+$rect = $window->rect;
+
+is($rect->{width}, 50, 'width = 50');
+is($rect->{height}, 40, 'height = 40');
+
+exit_gracefully($pid);
+
+################################################################################
+# 3: check floating_maximum_size
+################################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+# Test with different dimensions than the i3 default.
+floating_maximum_size 100 x 100
+EOT
+
+$pid = launch_with_config($config);
+
+$window = open_floating_window(rect => [ 0, 0, 150, 150 ]);
+$rect = $window->rect;
+
+is($rect->{width}, 100, 'width = 100');
+is($rect->{height}, 100, 'height = 100');
+
+exit_gracefully($pid);
+
+# Test that the feature works at all (without explicit configuration) by
+# opening a window which is bigger than the testsuite screen (1280x1024).
+
+$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);
+
+$window = open_floating_window(rect => [ 0, 0, 2048, 2048 ]);
+$rect = $window->rect;
+
+cmp_ok($rect->{width}, '<', 2048, 'width < 2048');
+cmp_ok($rect->{height}, '<', 2048, 'height < 2048');
+
+exit_gracefully($pid);
+
+################################################################################
+# 4: check floating_maximum_size
+################################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+# Test with different dimensions than the i3 default.
+floating_maximum_size -1 x -1
+EOT
+
+$pid = launch_with_config($config);
+
+$window = open_floating_window(rect => [ 0, 0, 2048, 2048 ]);
+$rect = $window->rect;
+
+is($rect->{width}, 2048, 'width = 2048');
+is($rect->{height}, 2048, 'height = 2048');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/500-multi-monitor.t b/testcases/t/500-multi-monitor.t
new file mode 100644 (file)
index 0000000..1f42f0b
--- /dev/null
@@ -0,0 +1,21 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests that the provided X-Server to the t/5??-*.t tests is actually providing
+# multiple monitors.
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+####################
+# Request tree
+####################
+
+my $tree = $i3->get_tree->recv;
+
+my @outputs = map { $_->{name} } @{$tree->{nodes}};
+is_deeply(\@outputs, [ '__i3', 'xinerama-0', 'xinerama-1' ],
+          'multi-monitor outputs ok');
+
+done_testing;
diff --git a/testcases/t/501-scratchpad.t b/testcases/t/501-scratchpad.t
new file mode 100644 (file)
index 0000000..ef57ffa
--- /dev/null
@@ -0,0 +1,95 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Verifies that scratchpad windows show up on the proper output.
+# ticket #596, bug present until up to commit
+# 89dded044b4fffe78f9d70778748fabb7ac533e9.
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+################################################################################
+# Open a workspace on the second output, put a window to scratchpad, display
+# it, verify it’s on the same workspace.
+################################################################################
+
+sub verify_scratchpad_on_same_ws {
+    my ($ws) = @_;
+
+    is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws');
+
+    my $window = open_window;
+
+    is(scalar @{get_ws($ws)->{nodes}}, 1, 'one nodes on this ws');
+
+    cmd 'move scratchpad';
+
+    is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws');
+
+    cmd 'scratchpad show';
+    is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws');
+    is(scalar @{get_ws($ws)->{floating_nodes}}, 1, 'one floating node on this ws');
+}
+
+my $second = fresh_workspace(output => 1);
+
+verify_scratchpad_on_same_ws($second);
+
+################################################################################
+# The same thing, but on the first output.
+################################################################################
+
+my $first = fresh_workspace(output => 0);
+
+verify_scratchpad_on_same_ws($first);
+
+################################################################################
+# Now open the scratchpad on one output and switch to another.
+################################################################################
+
+sub verify_scratchpad_switch {
+    my ($first, $second) = @_;
+
+    cmd "workspace $first";
+
+    is(scalar @{get_ws($first)->{nodes}}, 0, 'no nodes on this ws');
+
+    my $window = open_window;
+
+    is(scalar @{get_ws($first)->{nodes}}, 1, 'one nodes on this ws');
+
+    cmd 'move scratchpad';
+
+    is(scalar @{get_ws($first)->{nodes}}, 0, 'no nodes on this ws');
+
+    cmd "workspace $second";
+
+    cmd 'scratchpad show';
+    my $ws = get_ws($second);
+    is(scalar @{$ws->{nodes}}, 0, 'no nodes on this ws');
+    is(scalar @{$ws->{floating_nodes}}, 1, 'one floating node on this ws');
+
+    # Verify that the coordinates are within bounds.
+    my $srect = $ws->{floating_nodes}->[0]->{rect};
+    my $rect = $ws->{rect};
+    cmd 'nop before bounds check';
+    cmp_ok($srect->{x}, '>=', $rect->{x}, 'x within bounds');
+    cmp_ok($srect->{y}, '>=', $rect->{y}, 'y within bounds');
+    cmp_ok($srect->{x} + $srect->{width}, '<=', $rect->{x} + $rect->{width},
+           'width within bounds');
+    cmp_ok($srect->{y} + $srect->{height}, '<=', $rect->{y} + $rect->{height},
+           'height within bounds');
+}
+
+$first = fresh_workspace(output => 0);
+$second = fresh_workspace(output => 1);
+
+verify_scratchpad_switch($first, $second);
+
+$first = fresh_workspace(output => 1);
+$second = fresh_workspace(output => 0);
+
+verify_scratchpad_switch($first, $second);
+
+done_testing;
diff --git a/testcases/t/502-focus-output.t b/testcases/t/502-focus-output.t
new file mode 100644 (file)
index 0000000..139b670
--- /dev/null
@@ -0,0 +1,50 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Verifies the 'focus output' command works properly.
+
+use i3test;
+use List::Util qw(first);
+
+my $tmp = fresh_workspace;
+my $i3 = i3(get_socket_path());
+
+################################################################################
+# use 'focus output' and verify that focus gets changed appropriately
+################################################################################
+
+sub focused_output {
+    my $tree = $i3->get_tree->recv;
+    my $focused = $tree->{focus}->[0];
+    my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
+    return $output->{name};
+}
+
+is(focused_output, 'xinerama-0', 'focus on first output');
+
+cmd 'focus output right';
+is(focused_output, 'xinerama-1', 'focus on second output');
+
+# focus should wrap when we focus to the right again.
+cmd 'focus output right';
+is(focused_output, 'xinerama-0', 'focus on first output again');
+
+cmd 'focus output left';
+is(focused_output, 'xinerama-1', 'focus back on second output');
+
+cmd 'focus output left';
+is(focused_output, 'xinerama-0', 'focus on first output again');
+
+cmd 'focus output up';
+is(focused_output, 'xinerama-0', 'focus still on first output');
+
+cmd 'focus output down';
+is(focused_output, 'xinerama-0', 'focus still on first output');
+
+cmd 'focus output xinerama-1';
+is(focused_output, 'xinerama-1', 'focus on second output');
+
+cmd 'focus output xinerama-0';
+is(focused_output, 'xinerama-0', 'focus on first output');
+
+done_testing;
diff --git a/testcases/t/503-workspace.t b/testcases/t/503-workspace.t
new file mode 100644 (file)
index 0000000..7122cb3
--- /dev/null
@@ -0,0 +1,64 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests whether 'workspace next_on_output' and the like work correctly.
+#
+use List::Util qw(first);
+use i3test;
+
+################################################################################
+# Setup workspaces so that they stay open (with an empty container).
+################################################################################
+
+is(focused_ws, '1', 'starting on workspace 1');
+# ensure workspace 1 stays open
+open_window;
+
+cmd 'focus output right';
+is(focused_ws, '2', 'workspace 2 on second output');
+# ensure workspace 2 stays open
+open_window;
+
+cmd 'focus output right';
+is(focused_ws, '1', 'back on workspace 1');
+
+# We don’t use fresh_workspace with named workspaces here since they come last
+# when using 'workspace next'.
+cmd 'workspace 5';
+# ensure workspace 5 stays open
+open_window;
+
+################################################################################
+# Use workspace next and verify the correct order.
+################################################################################
+
+# The current order should be:
+# output 1: 1, 5
+# output 2: 2
+cmd 'workspace 1';
+cmd 'workspace next';
+is(focused_ws, '2', 'workspace 2 focused');
+cmd 'workspace next';
+is(focused_ws, '5', 'workspace 5 focused');
+
+################################################################################
+# Now try the same with workspace next_on_output.
+################################################################################
+
+cmd 'workspace 1';
+cmd 'workspace next_on_output';
+is(focused_ws, '5', 'workspace 5 focused');
+cmd 'workspace next_on_output';
+is(focused_ws, '1', 'workspace 1 focused');
+
+cmd 'workspace prev_on_output';
+is(focused_ws, '5', 'workspace 5 focused');
+cmd 'workspace prev_on_output';
+is(focused_ws, '1', 'workspace 1 focused');
+
+cmd 'workspace 2';
+
+cmd 'workspace prev_on_output';
+is(focused_ws, '2', 'workspace 2 focused');
+
+done_testing;
diff --git a/testcases/t/504-move-workspace-to-output.t b/testcases/t/504-move-workspace-to-output.t
new file mode 100644 (file)
index 0000000..7f6f990
--- /dev/null
@@ -0,0 +1,111 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests whether the 'move workspace <ws> to [output] <output>' command works
+#
+use List::Util qw(first);
+use i3test;
+
+# TODO:
+# introduce 'move workspace 3 to output <output>' with synonym 'move workspace 3 to <output>'
+
+################################################################################
+# Setup workspaces so that they stay open (with an empty container).
+################################################################################
+
+is(focused_ws, '1', 'starting on workspace 1');
+# ensure workspace 1 stays open
+open_window;
+
+cmd 'focus output right';
+is(focused_ws, '2', 'workspace 2 on second output');
+# ensure workspace 2 stays open
+open_window;
+
+cmd 'focus output right';
+is(focused_ws, '1', 'back on workspace 1');
+
+# We don’t use fresh_workspace with named workspaces here since they come last
+# when using 'workspace next'.
+cmd 'workspace 5';
+# ensure workspace 5 stays open
+open_window;
+
+################################################################################
+# Move a workspace over and verify that it is on the right output.
+################################################################################
+
+# The current order should be:
+# output 1: 1, 5
+# output 2: 2
+cmd 'workspace 5';
+is(focused_ws, '5', 'workspace 5 focused');
+
+my ($x0, $x1) = workspaces_per_screen();
+ok('5' ~~ @$x0, 'workspace 5 on xinerama-0');
+
+cmd 'move workspace to output xinerama-1';
+
+sub workspaces_per_screen {
+    my $i3 = i3(get_socket_path());
+    my $tree = $i3->get_tree->recv;
+    my @outputs = @{$tree->{nodes}};
+
+    my $xinerama0 = first { $_->{name} eq 'xinerama-0' } @outputs;
+    my $xinerama0_content = first { $_->{type} == 2 } @{$xinerama0->{nodes}};
+
+    my $xinerama1 = first { $_->{name} eq 'xinerama-1' } @outputs;
+    my $xinerama1_content = first { $_->{type} == 2 } @{$xinerama1->{nodes}};
+
+    my @xinerama0_workspaces = map { $_->{name} } @{$xinerama0_content->{nodes}};
+    my @xinerama1_workspaces = map { $_->{name} } @{$xinerama1_content->{nodes}};
+
+    return \@xinerama0_workspaces, \@xinerama1_workspaces;
+}
+
+($x0, $x1) = workspaces_per_screen();
+ok('5' ~~ @$x1, 'workspace 5 now on xinerama-1');
+
+################################################################################
+# Verify that a new workspace will be created when moving the last workspace.
+################################################################################
+
+is_deeply($x0, [ '1' ], 'only workspace 1 remaining on xinerama-0');
+
+cmd 'workspace 1';
+cmd 'move workspace to output xinerama-1';
+
+($x0, $x1) = workspaces_per_screen();
+ok('1' ~~ @$x1, 'workspace 1 now on xinerama-1');
+is_deeply($x0, [ '3' ], 'workspace 2 created on xinerama-0');
+
+################################################################################
+# Verify that 'move workspace to output <direction>' works
+################################################################################
+
+cmd 'workspace 5';
+cmd 'move workspace to output left';
+
+($x0, $x1) = workspaces_per_screen();
+ok('5' ~~ @$x0, 'workspace 5 back on xinerama-0');
+
+################################################################################
+# Verify that coordinates of floating windows are fixed correctly when moving a
+# workspace to a different output.
+################################################################################
+
+cmd 'workspace 5';
+my $floating_window = open_floating_window;
+
+my $old_rect = $floating_window->rect;
+
+cmd 'move workspace to output right';
+
+my $new_rect = $floating_window->rect;
+
+isnt($old_rect->{x}, $new_rect->{x}, 'x coordinate changed');
+is($old_rect->{y}, $new_rect->{y}, 'y coordinate unchanged');
+is($old_rect->{width}, $new_rect->{width}, 'width unchanged');
+is($old_rect->{height}, $new_rect->{height}, 'height unchanged');
+
+done_testing;